diff --git a/action_text-trix/app/assets/javascripts/trix.js b/action_text-trix/app/assets/javascripts/trix.js index 0a56d27e2..ec738d552 100644 --- a/action_text-trix/app/assets/javascripts/trix.js +++ b/action_text-trix/app/assets/javascripts/trix.js @@ -10240,766 +10240,155 @@ $\ } } - /* eslint-disable - */ - class SelectionManager extends BasicObject { + const mutableAttributeName = "data-trix-mutable"; + const mutableSelector = "[".concat(mutableAttributeName, "]"); + const options = { + attributes: true, + childList: true, + characterData: true, + characterDataOldValue: true, + subtree: true + }; + class MutationObserver extends BasicObject { constructor(element) { - super(...arguments); - this.didMouseDown = this.didMouseDown.bind(this); - this.selectionDidChange = this.selectionDidChange.bind(this); + super(element); + this.didMutate = this.didMutate.bind(this); this.element = element; - this.locationMapper = new LocationMapper(this.element); - this.pointMapper = new PointMapper(); - this.lockCount = 0; - handleEvent("mousedown", { - onElement: this.element, - withCallback: this.didMouseDown - }); - } - getLocationRange() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - if (options.strict === false) { - return this.createLocationRangeFromDOMRange(getDOMRange()); - } else if (options.ignoreLock) { - return this.currentLocationRange; - } else if (this.lockedLocationRange) { - return this.lockedLocationRange; - } else { - return this.currentLocationRange; - } + this.observer = new window.MutationObserver(this.didMutate); + this.start(); } - setLocationRange(locationRange) { - if (this.lockedLocationRange) return; - locationRange = normalizeRange(locationRange); - const domRange = this.createDOMRangeFromLocationRange(locationRange); - if (domRange) { - setDOMRange(domRange); - this.updateCurrentLocationRange(locationRange); - } + start() { + this.reset(); + return this.observer.observe(this.element, options); } - setLocationRangeFromPointRange(pointRange) { - pointRange = normalizeRange(pointRange); - const startLocation = this.getLocationAtPoint(pointRange[0]); - const endLocation = this.getLocationAtPoint(pointRange[1]); - this.setLocationRange([startLocation, endLocation]); + stop() { + return this.observer.disconnect(); } - getClientRectAtLocationRange(locationRange) { - const domRange = this.createDOMRangeFromLocationRange(locationRange); - if (domRange) { - return this.getClientRectsForDOMRange(domRange)[1]; + didMutate(mutations) { + this.mutations.push(...Array.from(this.findSignificantMutations(mutations) || [])); + if (this.mutations.length) { + var _this$delegate, _this$delegate$elemen; + (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$elemen = _this$delegate.elementDidMutate) === null || _this$delegate$elemen === void 0 || _this$delegate$elemen.call(_this$delegate, this.getMutationSummary()); + return this.reset(); } } - locationIsCursorTarget(location) { - const node = Array.from(this.findNodeAndOffsetFromLocation(location))[0]; - return nodeIsCursorTarget(node); + + // Private + + reset() { + this.mutations = []; } - lock() { - if (this.lockCount++ === 0) { - this.updateCurrentLocationRange(); - this.lockedLocationRange = this.getLocationRange(); - } + findSignificantMutations(mutations) { + return mutations.filter(mutation => { + return this.mutationIsSignificant(mutation); + }); } - unlock() { - if (--this.lockCount === 0) { - const { - lockedLocationRange - } = this; - this.lockedLocationRange = null; - if (lockedLocationRange != null) { - return this.setLocationRange(lockedLocationRange); - } + mutationIsSignificant(mutation) { + if (this.nodeIsMutable(mutation.target)) { + return false; } + for (const node of Array.from(this.nodesModifiedByMutation(mutation))) { + if (this.nodeIsSignificant(node)) return true; + } + return false; } - clearSelection() { - var _getDOMSelection; - return (_getDOMSelection = getDOMSelection()) === null || _getDOMSelection === void 0 ? void 0 : _getDOMSelection.removeAllRanges(); - } - selectionIsCollapsed() { - var _getDOMRange; - return ((_getDOMRange = getDOMRange()) === null || _getDOMRange === void 0 ? void 0 : _getDOMRange.collapsed) === true; + nodeIsSignificant(node) { + return node !== this.element && !this.nodeIsMutable(node) && !nodeIsEmptyTextNode(node); } - selectionIsExpanded() { - return !this.selectionIsCollapsed(); + nodeIsMutable(node) { + return findClosestElementFromNode(node, { + matchingSelector: mutableSelector + }); } - createLocationRangeFromDOMRange(domRange, options) { - if (domRange == null || !this.domRangeWithinElement(domRange)) return; - const start = this.findLocationFromContainerAndOffset(domRange.startContainer, domRange.startOffset, options); - if (!start) return; - const end = domRange.collapsed ? undefined : this.findLocationFromContainerAndOffset(domRange.endContainer, domRange.endOffset, options); - return normalizeRange([start, end]); + nodesModifiedByMutation(mutation) { + const nodes = []; + switch (mutation.type) { + case "attributes": + if (mutation.attributeName !== mutableAttributeName) { + nodes.push(mutation.target); + } + break; + case "characterData": + // Changes to text nodes should consider the parent element + nodes.push(mutation.target.parentNode); + nodes.push(mutation.target); + break; + case "childList": + // Consider each added or removed node + nodes.push(...Array.from(mutation.addedNodes || [])); + nodes.push(...Array.from(mutation.removedNodes || [])); + break; + } + return nodes; } - didMouseDown() { - return this.pauseTemporarily(); + getMutationSummary() { + return this.getTextMutationSummary(); } - pauseTemporarily() { - let resumeHandlers; - this.paused = true; - const resume = () => { - this.paused = false; - clearTimeout(resumeTimeout); - Array.from(resumeHandlers).forEach(handler => { - handler.destroy(); - }); - if (elementContainsNode(document, this.element)) { - return this.selectionDidChange(); + getTextMutationSummary() { + const { + additions, + deletions + } = this.getTextChangesFromCharacterData(); + const textChanges = this.getTextChangesFromChildList(); + Array.from(textChanges.additions).forEach(addition => { + if (!Array.from(additions).includes(addition)) { + additions.push(addition); } - }; - const resumeTimeout = setTimeout(resume, 200); - resumeHandlers = ["mousemove", "keydown"].map(eventName => handleEvent(eventName, { - onElement: document, - withCallback: resume - })); - } - selectionDidChange() { - if (!this.paused && !innerElementIsActive(this.element)) { - return this.updateCurrentLocationRange(); + }); + deletions.push(...Array.from(textChanges.deletions || [])); + const summary = {}; + const added = additions.join(""); + if (added) { + summary.textAdded = added; } - } - updateCurrentLocationRange(locationRange) { - if (locationRange != null ? locationRange : locationRange = this.createLocationRangeFromDOMRange(getDOMRange())) { - if (!rangesAreEqual(locationRange, this.currentLocationRange)) { - var _this$delegate, _this$delegate$locati; - this.currentLocationRange = locationRange; - return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$locati = _this$delegate.locationRangeDidChange) === null || _this$delegate$locati === void 0 ? void 0 : _this$delegate$locati.call(_this$delegate, this.currentLocationRange.slice(0)); - } + const deleted = deletions.join(""); + if (deleted) { + summary.textDeleted = deleted; } + return summary; } - createDOMRangeFromLocationRange(locationRange) { - const rangeStart = this.findContainerAndOffsetFromLocation(locationRange[0]); - const rangeEnd = rangeIsCollapsed(locationRange) ? rangeStart : this.findContainerAndOffsetFromLocation(locationRange[1]) || rangeStart; - if (rangeStart != null && rangeEnd != null) { - const domRange = document.createRange(); - domRange.setStart(...Array.from(rangeStart || [])); - domRange.setEnd(...Array.from(rangeEnd || [])); - return domRange; - } + getMutationsByType(type) { + return Array.from(this.mutations).filter(mutation => mutation.type === type); } - getLocationAtPoint(point) { - const domRange = this.createDOMRangeFromPoint(point); - if (domRange) { - var _this$createLocationR; - return (_this$createLocationR = this.createLocationRangeFromDOMRange(domRange)) === null || _this$createLocationR === void 0 ? void 0 : _this$createLocationR[0]; + getTextChangesFromChildList() { + let textAdded, textRemoved; + const addedNodes = []; + const removedNodes = []; + Array.from(this.getMutationsByType("childList")).forEach(mutation => { + addedNodes.push(...Array.from(mutation.addedNodes || [])); + removedNodes.push(...Array.from(mutation.removedNodes || [])); + }); + const singleBlockCommentRemoved = addedNodes.length === 0 && removedNodes.length === 1 && nodeIsBlockStartComment(removedNodes[0]); + if (singleBlockCommentRemoved) { + textAdded = []; + textRemoved = ["\n"]; + } else { + textAdded = getTextForNodes(addedNodes); + textRemoved = getTextForNodes(removedNodes); } + const additions = textAdded.filter((text, index) => text !== textRemoved[index]).map(normalizeSpaces); + const deletions = textRemoved.filter((text, index) => text !== textAdded[index]).map(normalizeSpaces); + return { + additions, + deletions + }; } - domRangeWithinElement(domRange) { - if (domRange.collapsed) { - return elementContainsNode(this.element, domRange.startContainer); - } else { - return elementContainsNode(this.element, domRange.startContainer) && elementContainsNode(this.element, domRange.endContainer); + getTextChangesFromCharacterData() { + let added, removed; + const characterMutations = this.getMutationsByType("characterData"); + if (characterMutations.length) { + const startMutation = characterMutations[0], + endMutation = characterMutations[characterMutations.length - 1]; + const oldString = normalizeSpaces(startMutation.oldValue); + const newString = normalizeSpaces(endMutation.target.data); + const summarized = summarizeStringChange(oldString, newString); + added = summarized.added; + removed = summarized.removed; } - } - } - SelectionManager.proxyMethod("locationMapper.findLocationFromContainerAndOffset"); - SelectionManager.proxyMethod("locationMapper.findContainerAndOffsetFromLocation"); - SelectionManager.proxyMethod("locationMapper.findNodeAndOffsetFromLocation"); - SelectionManager.proxyMethod("pointMapper.createDOMRangeFromPoint"); - SelectionManager.proxyMethod("pointMapper.getClientRectsForDOMRange"); - - var models = /*#__PURE__*/Object.freeze({ - __proto__: null, - Attachment: Attachment, - AttachmentManager: AttachmentManager, - AttachmentPiece: AttachmentPiece, - Block: Block, - Composition: Composition, - Document: Document, - Editor: Editor, - HTMLParser: HTMLParser, - HTMLSanitizer: HTMLSanitizer, - LineBreakInsertion: LineBreakInsertion, - LocationMapper: LocationMapper, - ManagedAttachment: ManagedAttachment, - Piece: Piece, - PointMapper: PointMapper, - SelectionManager: SelectionManager, - SplittableList: SplittableList, - StringPiece: StringPiece, - Text: Text, - UndoManager: UndoManager - }); - - var views = /*#__PURE__*/Object.freeze({ - __proto__: null, - ObjectView: ObjectView, - AttachmentView: AttachmentView, - BlockView: BlockView, - DocumentView: DocumentView, - PieceView: PieceView, - PreviewableAttachmentView: PreviewableAttachmentView, - TextView: TextView - }); - - const { - lang, - css, - keyNames: keyNames$1 - } = config; - const undoable = function (fn) { - return function () { - const commands = fn.apply(this, arguments); - commands.do(); - if (!this.undos) { - this.undos = []; - } - this.undos.push(commands.undo); - }; - }; - class AttachmentEditorController extends BasicObject { - constructor(attachmentPiece, _element, container) { - let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - super(...arguments); - // Installing and uninstalling - _defineProperty(this, "makeElementMutable", undoable(() => { - return { - do: () => { - this.element.dataset.trixMutable = true; - }, - undo: () => delete this.element.dataset.trixMutable - }; - })); - _defineProperty(this, "addToolbar", undoable(() => { - //
- const element = makeElement({ - tagName: "div", - className: css.attachmentToolbar, - data: { - trixMutable: true - }, - childNodes: makeElement({ - tagName: "div", - className: "trix-button-row", - childNodes: makeElement({ - tagName: "span", - className: "trix-button-group trix-button-group--actions", - childNodes: makeElement({ - tagName: "button", - className: "trix-button trix-button--remove", - textContent: lang.remove, - attributes: { - title: lang.remove - }, - data: { - trixAction: "remove" - } - }) - }) - }) - }); - if (this.attachment.isPreviewable()) { - // - element.appendChild(makeElement({ - tagName: "div", - className: css.attachmentMetadataContainer, - childNodes: makeElement({ - tagName: "span", - className: css.attachmentMetadata, - childNodes: [makeElement({ - tagName: "span", - className: css.attachmentName, - textContent: this.attachment.getFilename(), - attributes: { - title: this.attachment.getFilename() - } - }), makeElement({ - tagName: "span", - className: css.attachmentSize, - textContent: this.attachment.getFormattedFilesize() - })] - }) - })); - } - handleEvent("click", { - onElement: element, - withCallback: this.didClickToolbar - }); - handleEvent("click", { - onElement: element, - matchingSelector: "[data-trix-action]", - withCallback: this.didClickActionButton - }); - triggerEvent("trix-attachment-before-toolbar", { - onElement: this.element, - attributes: { - toolbar: element, - attachment: this.attachment - } - }); - return { - do: () => this.element.appendChild(element), - undo: () => removeNode(element) - }; - })); - _defineProperty(this, "installCaptionEditor", undoable(() => { - const textarea = makeElement({ - tagName: "textarea", - className: css.attachmentCaptionEditor, - attributes: { - placeholder: lang.captionPlaceholder - }, - data: { - trixMutable: true - } - }); - textarea.value = this.attachmentPiece.getCaption(); - const textareaClone = textarea.cloneNode(); - textareaClone.classList.add("trix-autoresize-clone"); - textareaClone.tabIndex = -1; - const autoresize = function () { - textareaClone.value = textarea.value; - textarea.style.height = textareaClone.scrollHeight + "px"; - }; - handleEvent("input", { - onElement: textarea, - withCallback: autoresize - }); - handleEvent("input", { - onElement: textarea, - withCallback: this.didInputCaption - }); - handleEvent("keydown", { - onElement: textarea, - withCallback: this.didKeyDownCaption - }); - handleEvent("change", { - onElement: textarea, - withCallback: this.didChangeCaption - }); - handleEvent("blur", { - onElement: textarea, - withCallback: this.didBlurCaption - }); - const figcaption = this.element.querySelector("figcaption"); - const editingFigcaption = figcaption.cloneNode(); - return { - do: () => { - figcaption.style.display = "none"; - editingFigcaption.appendChild(textarea); - editingFigcaption.appendChild(textareaClone); - editingFigcaption.classList.add("".concat(css.attachmentCaption, "--editing")); - figcaption.parentElement.insertBefore(editingFigcaption, figcaption); - autoresize(); - if (this.options.editCaption) { - return defer(() => textarea.focus()); - } - }, - undo() { - removeNode(editingFigcaption); - figcaption.style.display = null; - } - }; - })); - this.didClickToolbar = this.didClickToolbar.bind(this); - this.didClickActionButton = this.didClickActionButton.bind(this); - this.didKeyDownCaption = this.didKeyDownCaption.bind(this); - this.didInputCaption = this.didInputCaption.bind(this); - this.didChangeCaption = this.didChangeCaption.bind(this); - this.didBlurCaption = this.didBlurCaption.bind(this); - this.attachmentPiece = attachmentPiece; - this.element = _element; - this.container = container; - this.options = options; - this.attachment = this.attachmentPiece.attachment; - if (tagName(this.element) === "a") { - this.element = this.element.firstChild; - } - this.install(); - } - install() { - this.makeElementMutable(); - this.addToolbar(); - if (this.attachment.isPreviewable()) { - this.installCaptionEditor(); - } - } - uninstall() { - var _this$delegate; - let undo = this.undos.pop(); - this.savePendingCaption(); - while (undo) { - undo(); - undo = this.undos.pop(); - } - (_this$delegate = this.delegate) === null || _this$delegate === void 0 || _this$delegate.didUninstallAttachmentEditor(this); - } - - // Private - - savePendingCaption() { - if (this.pendingCaption != null) { - const caption = this.pendingCaption; - this.pendingCaption = null; - if (caption) { - var _this$delegate2, _this$delegate2$attac; - (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$attac = _this$delegate2.attachmentEditorDidRequestUpdatingAttributesForAttachment) === null || _this$delegate2$attac === void 0 || _this$delegate2$attac.call(_this$delegate2, { - caption - }, this.attachment); - } else { - var _this$delegate3, _this$delegate3$attac; - (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$attac = _this$delegate3.attachmentEditorDidRequestRemovingAttributeForAttachment) === null || _this$delegate3$attac === void 0 || _this$delegate3$attac.call(_this$delegate3, "caption", this.attachment); - } - } - } - // Event handlers - - didClickToolbar(event) { - event.preventDefault(); - return event.stopPropagation(); - } - didClickActionButton(event) { - var _this$delegate4; - const action = event.target.getAttribute("data-trix-action"); - switch (action) { - case "remove": - return (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 ? void 0 : _this$delegate4.attachmentEditorDidRequestRemovalOfAttachment(this.attachment); - } - } - didKeyDownCaption(event) { - if (keyNames$1[event.keyCode] === "return") { - var _this$delegate5, _this$delegate5$attac; - event.preventDefault(); - this.savePendingCaption(); - return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$attac = _this$delegate5.attachmentEditorDidRequestDeselectingAttachment) === null || _this$delegate5$attac === void 0 ? void 0 : _this$delegate5$attac.call(_this$delegate5, this.attachment); - } - } - didInputCaption(event) { - this.pendingCaption = event.target.value.replace(/\s/g, " ").trim(); - } - didChangeCaption(event) { - return this.savePendingCaption(); - } - didBlurCaption(event) { - return this.savePendingCaption(); - } - } - - class CompositionController extends BasicObject { - constructor(element, composition) { - super(...arguments); - this.didFocus = this.didFocus.bind(this); - this.didBlur = this.didBlur.bind(this); - this.didClickAttachment = this.didClickAttachment.bind(this); - this.element = element; - this.composition = composition; - this.documentView = new DocumentView(this.composition.document, { - element: this.element - }); - handleEvent("focus", { - onElement: this.element, - withCallback: this.didFocus - }); - handleEvent("blur", { - onElement: this.element, - withCallback: this.didBlur - }); - handleEvent("click", { - onElement: this.element, - matchingSelector: "a[contenteditable=false]", - preventDefault: true - }); - handleEvent("mousedown", { - onElement: this.element, - matchingSelector: attachmentSelector, - withCallback: this.didClickAttachment - }); - handleEvent("click", { - onElement: this.element, - matchingSelector: "a".concat(attachmentSelector), - preventDefault: true - }); - } - didFocus(event) { - var _this$blurPromise; - const perform = () => { - if (!this.focused) { - var _this$delegate, _this$delegate$compos; - this.focused = true; - return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$compos = _this$delegate.compositionControllerDidFocus) === null || _this$delegate$compos === void 0 ? void 0 : _this$delegate$compos.call(_this$delegate); - } - }; - return ((_this$blurPromise = this.blurPromise) === null || _this$blurPromise === void 0 ? void 0 : _this$blurPromise.then(perform)) || perform(); - } - didBlur(event) { - this.blurPromise = new Promise(resolve => { - return defer(() => { - if (!innerElementIsActive(this.element)) { - var _this$delegate2, _this$delegate2$compo; - this.focused = null; - (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$compo = _this$delegate2.compositionControllerDidBlur) === null || _this$delegate2$compo === void 0 || _this$delegate2$compo.call(_this$delegate2); - } - this.blurPromise = null; - return resolve(); - }); - }); - } - didClickAttachment(event, target) { - var _this$delegate3, _this$delegate3$compo; - const attachment = this.findAttachmentForElement(target); - const editCaption = !!findClosestElementFromNode(event.target, { - matchingSelector: "figcaption" - }); - return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$compo = _this$delegate3.compositionControllerDidSelectAttachment) === null || _this$delegate3$compo === void 0 ? void 0 : _this$delegate3$compo.call(_this$delegate3, attachment, { - editCaption - }); - } - getSerializableElement() { - if (this.isEditingAttachment()) { - return this.documentView.shadowElement; - } else { - return this.element; - } - } - render() { - var _this$delegate6, _this$delegate6$compo; - if (this.revision !== this.composition.revision) { - this.documentView.setDocument(this.composition.document); - this.documentView.render(); - this.revision = this.composition.revision; - } - if (this.canSyncDocumentView() && !this.documentView.isSynced()) { - var _this$delegate4, _this$delegate4$compo, _this$delegate5, _this$delegate5$compo; - (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || (_this$delegate4$compo = _this$delegate4.compositionControllerWillSyncDocumentView) === null || _this$delegate4$compo === void 0 || _this$delegate4$compo.call(_this$delegate4); - this.documentView.sync(); - (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$compo = _this$delegate5.compositionControllerDidSyncDocumentView) === null || _this$delegate5$compo === void 0 || _this$delegate5$compo.call(_this$delegate5); - } - return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$compo = _this$delegate6.compositionControllerDidRender) === null || _this$delegate6$compo === void 0 ? void 0 : _this$delegate6$compo.call(_this$delegate6); - } - rerenderViewForObject(object) { - this.invalidateViewForObject(object); - return this.render(); - } - invalidateViewForObject(object) { - return this.documentView.invalidateViewForObject(object); - } - isViewCachingEnabled() { - return this.documentView.isViewCachingEnabled(); - } - enableViewCaching() { - return this.documentView.enableViewCaching(); - } - disableViewCaching() { - return this.documentView.disableViewCaching(); - } - refreshViewCache() { - return this.documentView.garbageCollectCachedViews(); - } - - // Attachment editor management - - isEditingAttachment() { - return !!this.attachmentEditor; - } - installAttachmentEditorForAttachment(attachment, options) { - var _this$attachmentEdito; - if (((_this$attachmentEdito = this.attachmentEditor) === null || _this$attachmentEdito === void 0 ? void 0 : _this$attachmentEdito.attachment) === attachment) return; - const element = this.documentView.findElementForObject(attachment); - if (!element) return; - this.uninstallAttachmentEditor(); - const attachmentPiece = this.composition.document.getAttachmentPieceForAttachment(attachment); - this.attachmentEditor = new AttachmentEditorController(attachmentPiece, element, this.element, options); - this.attachmentEditor.delegate = this; - } - uninstallAttachmentEditor() { - var _this$attachmentEdito2; - return (_this$attachmentEdito2 = this.attachmentEditor) === null || _this$attachmentEdito2 === void 0 ? void 0 : _this$attachmentEdito2.uninstall(); - } - - // Attachment controller delegate - - didUninstallAttachmentEditor() { - this.attachmentEditor = null; - return this.render(); - } - attachmentEditorDidRequestUpdatingAttributesForAttachment(attributes, attachment) { - var _this$delegate7, _this$delegate7$compo; - (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$compo = _this$delegate7.compositionControllerWillUpdateAttachment) === null || _this$delegate7$compo === void 0 || _this$delegate7$compo.call(_this$delegate7, attachment); - return this.composition.updateAttributesForAttachment(attributes, attachment); - } - attachmentEditorDidRequestRemovingAttributeForAttachment(attribute, attachment) { - var _this$delegate8, _this$delegate8$compo; - (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || (_this$delegate8$compo = _this$delegate8.compositionControllerWillUpdateAttachment) === null || _this$delegate8$compo === void 0 || _this$delegate8$compo.call(_this$delegate8, attachment); - return this.composition.removeAttributeForAttachment(attribute, attachment); - } - attachmentEditorDidRequestRemovalOfAttachment(attachment) { - var _this$delegate9, _this$delegate9$compo; - return (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || (_this$delegate9$compo = _this$delegate9.compositionControllerDidRequestRemovalOfAttachment) === null || _this$delegate9$compo === void 0 ? void 0 : _this$delegate9$compo.call(_this$delegate9, attachment); - } - attachmentEditorDidRequestDeselectingAttachment(attachment) { - var _this$delegate10, _this$delegate10$comp; - return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || (_this$delegate10$comp = _this$delegate10.compositionControllerDidRequestDeselectingAttachment) === null || _this$delegate10$comp === void 0 ? void 0 : _this$delegate10$comp.call(_this$delegate10, attachment); - } - - // Private - - canSyncDocumentView() { - return !this.isEditingAttachment(); - } - findAttachmentForElement(element) { - return this.composition.document.getAttachmentById(parseInt(element.dataset.trixId, 10)); - } - } - - class Controller extends BasicObject {} - - const mutableAttributeName = "data-trix-mutable"; - const mutableSelector = "[".concat(mutableAttributeName, "]"); - const options = { - attributes: true, - childList: true, - characterData: true, - characterDataOldValue: true, - subtree: true - }; - class MutationObserver extends BasicObject { - constructor(element) { - super(element); - this.didMutate = this.didMutate.bind(this); - this.element = element; - this.observer = new window.MutationObserver(this.didMutate); - this.start(); - } - start() { - this.reset(); - return this.observer.observe(this.element, options); - } - stop() { - return this.observer.disconnect(); - } - didMutate(mutations) { - this.mutations.push(...Array.from(this.findSignificantMutations(mutations) || [])); - if (this.mutations.length) { - var _this$delegate, _this$delegate$elemen; - (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$elemen = _this$delegate.elementDidMutate) === null || _this$delegate$elemen === void 0 || _this$delegate$elemen.call(_this$delegate, this.getMutationSummary()); - return this.reset(); - } - } - - // Private - - reset() { - this.mutations = []; - } - findSignificantMutations(mutations) { - return mutations.filter(mutation => { - return this.mutationIsSignificant(mutation); - }); - } - mutationIsSignificant(mutation) { - if (this.nodeIsMutable(mutation.target)) { - return false; - } - for (const node of Array.from(this.nodesModifiedByMutation(mutation))) { - if (this.nodeIsSignificant(node)) return true; - } - return false; - } - nodeIsSignificant(node) { - return node !== this.element && !this.nodeIsMutable(node) && !nodeIsEmptyTextNode(node); - } - nodeIsMutable(node) { - return findClosestElementFromNode(node, { - matchingSelector: mutableSelector - }); - } - nodesModifiedByMutation(mutation) { - const nodes = []; - switch (mutation.type) { - case "attributes": - if (mutation.attributeName !== mutableAttributeName) { - nodes.push(mutation.target); - } - break; - case "characterData": - // Changes to text nodes should consider the parent element - nodes.push(mutation.target.parentNode); - nodes.push(mutation.target); - break; - case "childList": - // Consider each added or removed node - nodes.push(...Array.from(mutation.addedNodes || [])); - nodes.push(...Array.from(mutation.removedNodes || [])); - break; - } - return nodes; - } - getMutationSummary() { - return this.getTextMutationSummary(); - } - getTextMutationSummary() { - const { - additions, - deletions - } = this.getTextChangesFromCharacterData(); - const textChanges = this.getTextChangesFromChildList(); - Array.from(textChanges.additions).forEach(addition => { - if (!Array.from(additions).includes(addition)) { - additions.push(addition); - } - }); - deletions.push(...Array.from(textChanges.deletions || [])); - const summary = {}; - const added = additions.join(""); - if (added) { - summary.textAdded = added; - } - const deleted = deletions.join(""); - if (deleted) { - summary.textDeleted = deleted; - } - return summary; - } - getMutationsByType(type) { - return Array.from(this.mutations).filter(mutation => mutation.type === type); - } - getTextChangesFromChildList() { - let textAdded, textRemoved; - const addedNodes = []; - const removedNodes = []; - Array.from(this.getMutationsByType("childList")).forEach(mutation => { - addedNodes.push(...Array.from(mutation.addedNodes || [])); - removedNodes.push(...Array.from(mutation.removedNodes || [])); - }); - const singleBlockCommentRemoved = addedNodes.length === 0 && removedNodes.length === 1 && nodeIsBlockStartComment(removedNodes[0]); - if (singleBlockCommentRemoved) { - textAdded = []; - textRemoved = ["\n"]; - } else { - textAdded = getTextForNodes(addedNodes); - textRemoved = getTextForNodes(removedNodes); - } - const additions = textAdded.filter((text, index) => text !== textRemoved[index]).map(normalizeSpaces); - const deletions = textRemoved.filter((text, index) => text !== textAdded[index]).map(normalizeSpaces); - return { - additions, - deletions - }; - } - getTextChangesFromCharacterData() { - let added, removed; - const characterMutations = this.getMutationsByType("characterData"); - if (characterMutations.length) { - const startMutation = characterMutations[0], - endMutation = characterMutations[characterMutations.length - 1]; - const oldString = normalizeSpaces(startMutation.oldValue); - const newString = normalizeSpaces(endMutation.target.data); - const summarized = summarizeStringChange(oldString, newString); - added = summarized.added; - removed = summarized.removed; - } - return { - additions: added ? [added] : [], - deletions: removed ? [removed] : [] - }; + return { + additions: added ? [added] : [], + deletions: removed ? [removed] : [] + }; } } const getTextForNodes = function () { @@ -11173,1268 +10562,1968 @@ $\ } _defineProperty(InputController, "events", {}); - var _$codePointAt, _; - const { - browser, - keyNames - } = config; - let pastedFileCount = 0; - class Level0InputController extends InputController { + // Safari Smart Quotes bug workaround constants + // Maximum character distance from cursor to consider a replacement "at cursor" vs "before cursor" + const AT_CURSOR_DISTANCE_THRESHOLD = 2; + + // Number of input events to ignore after Smart Quotes replacement + // Safari fires multiple input events that would corrupt our cursor position + const INPUT_EVENTS_TO_IGNORE_COUNT = 3; + + // WeakMap to associate editor elements with their Smart Quotes workaround state + const editorStates = new WeakMap(); + + // Export function for SelectionManager to check if it should skip syncing + const shouldPreventSelectionSync = element => { + const state = editorStates.get(element); + return (state === null || state === void 0 ? void 0 : state.preventSelectionSync) || false; + }; + class Level2InputController extends InputController { constructor() { super(...arguments); - this.resetInputSummary(); + this.render = this.render.bind(this); + // Initialize state for this editor instance + editorStates.set(this.element, { + pendingInputEventsToIgnore: 0, + preventSelectionSync: false + }); } - setInputSummary() { - let summary = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - this.inputSummary.eventName = this.eventName; - for (const key in summary) { - const value = summary[key]; - this.inputSummary[key] = value; + elementDidMutate() { + if (this.scheduledRender) { + if (this.composing) { + var _this$delegate, _this$delegate$inputC; + return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidAllowUnhandledInput) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate); + } + } else { + return this.reparse(); } - return this.inputSummary; } - resetInputSummary() { - this.inputSummary = {}; + scheduleRender() { + return this.scheduledRender ? this.scheduledRender : this.scheduledRender = requestAnimationFrame(this.render); } - reset() { - this.resetInputSummary(); - return selectionChangeObserver.reset(); + render() { + var _this$afterRender; + cancelAnimationFrame(this.scheduledRender); + this.scheduledRender = null; + if (!this.composing) { + var _this$delegate2; + (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || _this$delegate2.render(); + } + (_this$afterRender = this.afterRender) === null || _this$afterRender === void 0 || _this$afterRender.call(this); + this.afterRender = null; + } + reparse() { + var _this$delegate3; + return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 ? void 0 : _this$delegate3.reparse(); } - // Mutation observer delegate + // Responder helpers - elementDidMutate(mutationSummary) { - if (this.isComposing()) { - var _this$delegate, _this$delegate$inputC; - return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidAllowUnhandledInput) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate); - } else { - return this.handleInput(function () { - if (this.mutationIsSignificant(mutationSummary)) { - if (this.mutationIsExpected(mutationSummary)) { - this.requestRender(); - } else { - this.requestReparse(); - } - } - return this.reset(); + insertString() { + var _this$delegate4; + let string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; + let options = arguments.length > 1 ? arguments[1] : undefined; + (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillPerformTyping(); + return this.withTargetDOMRange(function () { + var _this$responder; + return (_this$responder = this.responder) === null || _this$responder === void 0 ? void 0 : _this$responder.insertString(string, options); + }); + } + toggleAttributeIfSupported(attributeName) { + if (getAllAttributeNames().includes(attributeName)) { + var _this$delegate5; + (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || _this$delegate5.inputControllerWillPerformFormatting(attributeName); + return this.withTargetDOMRange(function () { + var _this$responder2; + return (_this$responder2 = this.responder) === null || _this$responder2 === void 0 ? void 0 : _this$responder2.toggleCurrentAttribute(attributeName); }); } } - mutationIsExpected(_ref) { + activateAttributeIfSupported(attributeName, value) { + if (getAllAttributeNames().includes(attributeName)) { + var _this$delegate6; + (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || _this$delegate6.inputControllerWillPerformFormatting(attributeName); + return this.withTargetDOMRange(function () { + var _this$responder3; + return (_this$responder3 = this.responder) === null || _this$responder3 === void 0 ? void 0 : _this$responder3.setCurrentAttribute(attributeName, value); + }); + } + } + deleteInDirection(direction) { let { - textAdded, - textDeleted - } = _ref; - if (this.inputSummary.preferDocument) { - return true; + recordUndoEntry + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + recordUndoEntry: true + }; + if (recordUndoEntry) { + var _this$delegate7; + (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || _this$delegate7.inputControllerWillPerformTyping(); } - const mutationAdditionMatchesSummary = textAdded != null ? textAdded === this.inputSummary.textAdded : !this.inputSummary.textAdded; - const mutationDeletionMatchesSummary = textDeleted != null ? this.inputSummary.didDelete : !this.inputSummary.didDelete; - const unexpectedNewlineAddition = ["\n", " \n"].includes(textAdded) && !mutationAdditionMatchesSummary; - const unexpectedNewlineDeletion = textDeleted === "\n" && !mutationDeletionMatchesSummary; - const singleUnexpectedNewline = unexpectedNewlineAddition && !unexpectedNewlineDeletion || unexpectedNewlineDeletion && !unexpectedNewlineAddition; - if (singleUnexpectedNewline) { - const range = this.getSelectedRange(); - if (range) { - var _this$responder; - const offset = unexpectedNewlineAddition ? textAdded.replace(/\n$/, "").length || -1 : (textAdded === null || textAdded === void 0 ? void 0 : textAdded.length) || 1; - if ((_this$responder = this.responder) !== null && _this$responder !== void 0 && _this$responder.positionIsBlockBreak(range[1] + offset)) { - return true; - } - } + const perform = () => { + var _this$responder4; + return (_this$responder4 = this.responder) === null || _this$responder4 === void 0 ? void 0 : _this$responder4.deleteInDirection(direction); + }; + const domRange = this.getTargetDOMRange({ + minLength: this.composing ? 1 : 2 + }); + if (domRange) { + return this.withTargetDOMRange(domRange, perform); + } else { + return perform(); } - return mutationAdditionMatchesSummary && mutationDeletionMatchesSummary; } - mutationIsSignificant(mutationSummary) { - var _this$compositionInpu; - const textChanged = Object.keys(mutationSummary).length > 0; - const composedEmptyString = ((_this$compositionInpu = this.compositionInput) === null || _this$compositionInpu === void 0 ? void 0 : _this$compositionInpu.getEndData()) === ""; - return textChanged || !composedEmptyString; + + // Replacement text helpers + + // Determines if replacement is at cursor (Smart Quotes) vs before cursor (autocorrect) + isReplacementAtCursor(domRange) { + var _this$responder5, _this$responder6, _this$responder6$crea, _this$responder7, _this$responder8; + const cursorPosition = (_this$responder5 = this.responder) === null || _this$responder5 === void 0 || (_this$responder5 = _this$responder5.getSelectedRange()) === null || _this$responder5 === void 0 ? void 0 : _this$responder5[1]; + const targetLocationRange = (_this$responder6 = this.responder) === null || _this$responder6 === void 0 || (_this$responder6$crea = _this$responder6.createLocationRangeFromDOMRange) === null || _this$responder6$crea === void 0 ? void 0 : _this$responder6$crea.call(_this$responder6, domRange, { + strict: false + }); + const targetStart = targetLocationRange ? (_this$responder7 = this.responder) === null || _this$responder7 === void 0 || (_this$responder7 = _this$responder7.document) === null || _this$responder7 === void 0 ? void 0 : _this$responder7.positionFromLocation(targetLocationRange[0]) : null; + const targetEnd = targetLocationRange ? (_this$responder8 = this.responder) === null || _this$responder8 === void 0 || (_this$responder8 = _this$responder8.document) === null || _this$responder8 === void 0 ? void 0 : _this$responder8.positionFromLocation(targetLocationRange[1]) : null; + if (cursorPosition == null || targetEnd == null) { + return { + isAtCursor: false, + cursorPosition: null, + targetStart: null, + targetEnd: null + }; + } + const distanceFromCursor = Math.abs(cursorPosition - targetEnd); + const isAtCursor = distanceFromCursor <= AT_CURSOR_DISTANCE_THRESHOLD; + return { + isAtCursor, + cursorPosition, + targetStart, + targetEnd + }; } - // Private + // Selection helpers - getCompositionInput() { - if (this.isComposing()) { - return this.compositionInput; + withTargetDOMRange(domRange, fn) { + if (typeof domRange === "function") { + fn = domRange; + domRange = this.getTargetDOMRange(); + } + if (domRange) { + var _this$responder9; + return (_this$responder9 = this.responder) === null || _this$responder9 === void 0 ? void 0 : _this$responder9.withTargetDOMRange(domRange, fn.bind(this)); } else { - this.compositionInput = new CompositionInput(this); + selectionChangeObserver.reset(); + return fn.call(this); } } - isComposing() { - return this.compositionInput && !this.compositionInput.isEnded(); - } - deleteInDirection(direction, event) { - var _this$responder2; - if (((_this$responder2 = this.responder) === null || _this$responder2 === void 0 ? void 0 : _this$responder2.deleteInDirection(direction)) === false) { - if (event) { - event.preventDefault(); - return this.requestRender(); + getTargetDOMRange() { + var _this$event$getTarget, _this$event; + let { + minLength + } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { + minLength: 0 + }; + const targetRanges = (_this$event$getTarget = (_this$event = this.event).getTargetRanges) === null || _this$event$getTarget === void 0 ? void 0 : _this$event$getTarget.call(_this$event); + if (targetRanges) { + if (targetRanges.length) { + const domRange = staticRangeToRange(targetRanges[0]); + if (minLength === 0 || domRange.toString().length >= minLength) { + return domRange; + } } - } else { - return this.setInputSummary({ - didDelete: true - }); } } - serializeSelectionToDataTransfer(dataTransfer) { - var _this$responder3; - if (!dataTransferIsWritable(dataTransfer)) return; - const document = (_this$responder3 = this.responder) === null || _this$responder3 === void 0 ? void 0 : _this$responder3.getSelectedDocument().toSerializableDocument(); - dataTransfer.setData("application/x-trix-document", JSON.stringify(document)); - dataTransfer.setData("text/html", DocumentView.render(document).innerHTML); - dataTransfer.setData("text/plain", document.toString().replace(/\n$/, "")); - return true; - } - canAcceptDataTransfer(dataTransfer) { - const types = {}; - Array.from((dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.types) || []).forEach(type => { - types[type] = true; - }); - return types.Files || types["application/x-trix-document"] || types["text/html"] || types["text/plain"]; - } - getPastedHTMLUsingHiddenElement(callback) { - const selectedRange = this.getSelectedRange(); - const style = { - position: "absolute", - left: "".concat(window.pageXOffset, "px"), - top: "".concat(window.pageYOffset, "px"), - opacity: 0 - }; - const element = makeElement({ - style, - tagName: "div", - editable: true - }); - document.body.appendChild(element); - element.focus(); - return requestAnimationFrame(() => { - const html = element.innerHTML; - removeNode(element); - this.setSelectedRange(selectedRange); - return callback(html); - }); + withEvent(event, fn) { + let result; + this.event = event; + try { + result = fn.call(this); + } finally { + this.event = null; + } + return result; } } - _defineProperty(Level0InputController, "events", { + _defineProperty(Level2InputController, "events", { keydown(event) { - if (!this.isComposing()) { - this.resetInputSummary(); + if (keyEventIsKeyboardCommand(event)) { + var _this$delegate8; + const command = keyboardCommandFromKeyEvent(event); + if ((_this$delegate8 = this.delegate) !== null && _this$delegate8 !== void 0 && _this$delegate8.inputControllerDidReceiveKeyboardCommand(command)) { + event.preventDefault(); + } + } else { + let name = event.key; + if (event.altKey) { + name += "+Alt"; + } + if (event.shiftKey) { + name += "+Shift"; + } + const handler = this.constructor.keys[name]; + if (handler) { + return this.withEvent(event, handler); + } } - this.inputSummary.didInput = true; - const keyName = keyNames[event.keyCode]; - if (keyName) { - var _context2; - let context = this.keys; - ["ctrl", "alt", "shift", "meta"].forEach(modifier => { - if (event["".concat(modifier, "Key")]) { - var _context; - if (modifier === "ctrl") { - modifier = "control"; - } - context = (_context = context) === null || _context === void 0 ? void 0 : _context[modifier]; - } - }); - if (((_context2 = context) === null || _context2 === void 0 ? void 0 : _context2[keyName]) != null) { - this.setInputSummary({ - keyName - }); - selectionChangeObserver.reset(); - context[keyName].call(this, event); + }, + // Handle paste event to work around beforeinput.insertFromPaste browser bugs. + // Safe to remove each condition once fixed upstream. + paste(event) { + var _event$clipboardData; + // https://bugs.webkit.org/show_bug.cgi?id=194921 + let paste; + const href = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData("URL"); + if (pasteEventHasFilesOnly(event)) { + event.preventDefault(); + return this.attachFiles(event.clipboardData.files); + + // https://bugs.chromium.org/p/chromium/issues/detail?id=934448 + } else if (pasteEventHasPlainTextOnly(event)) { + var _this$delegate9, _this$responder10, _this$delegate10; + event.preventDefault(); + paste = { + type: "text/plain", + string: event.clipboardData.getData("text/plain") + }; + (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || _this$delegate9.inputControllerWillPaste(paste); + (_this$responder10 = this.responder) === null || _this$responder10 === void 0 || _this$responder10.insertString(paste.string); + this.render(); + return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 ? void 0 : _this$delegate10.inputControllerDidPaste(paste); + + // https://bugs.webkit.org/show_bug.cgi?id=196702 + } else if (href) { + var _this$delegate11, _this$responder11, _this$delegate12; + event.preventDefault(); + paste = { + type: "text/html", + html: this.createLinkHTML(href) + }; + (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 || _this$delegate11.inputControllerWillPaste(paste); + (_this$responder11 = this.responder) === null || _this$responder11 === void 0 || _this$responder11.insertHTML(paste.html); + this.render(); + return (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 ? void 0 : _this$delegate12.inputControllerDidPaste(paste); + } + }, + beforeinput(event) { + const handler = this.constructor.inputTypes[event.inputType]; + const immmediateRender = shouldRenderInmmediatelyToDealWithIOSDictation(event); + if (handler) { + this.withEvent(event, handler); + if (!immmediateRender) { + this.scheduleRender(); } } - if (keyEventIsKeyboardCommand(event)) { - const character = String.fromCharCode(event.keyCode).toLowerCase(); - if (character) { - var _this$delegate3; - const keys = ["alt", "shift"].map(modifier => { - if (event["".concat(modifier, "Key")]) { - return modifier; - } - }).filter(key => key); - keys.push(character); - if ((_this$delegate3 = this.delegate) !== null && _this$delegate3 !== void 0 && _this$delegate3.inputControllerDidReceiveKeyboardCommand(keys)) { - event.preventDefault(); - } + if (immmediateRender) { + this.render(); + } + }, + input(event) { + const state = editorStates.get(this.element); + if (state.pendingInputEventsToIgnore > 0) { + state.pendingInputEventsToIgnore--; + return; + } + selectionChangeObserver.reset(); + }, + dragstart(event) { + var _this$responder12; + if ((_this$responder12 = this.responder) !== null && _this$responder12 !== void 0 && _this$responder12.selectionContainsAttachments()) { + var _this$responder13; + event.dataTransfer.setData("application/x-trix-dragging", true); + this.dragging = { + range: (_this$responder13 = this.responder) === null || _this$responder13 === void 0 ? void 0 : _this$responder13.getSelectedRange(), + point: pointFromEvent(event) + }; + } + }, + dragenter(event) { + if (dragEventHasFiles(event)) { + event.preventDefault(); + } + }, + dragover(event) { + if (this.dragging) { + event.preventDefault(); + const point = pointFromEvent(event); + if (!objectsAreEqual(point, this.dragging.point)) { + var _this$responder14; + this.dragging.point = point; + return (_this$responder14 = this.responder) === null || _this$responder14 === void 0 ? void 0 : _this$responder14.setLocationRangeFromPointRange(point); } + } else if (dragEventHasFiles(event)) { + event.preventDefault(); + } + }, + drop(event) { + if (this.dragging) { + var _this$delegate13, _this$responder15; + event.preventDefault(); + (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || _this$delegate13.inputControllerWillMoveText(); + (_this$responder15 = this.responder) === null || _this$responder15 === void 0 || _this$responder15.moveTextFromRange(this.dragging.range); + this.dragging = null; + return this.scheduleRender(); + } else if (dragEventHasFiles(event)) { + var _this$responder16; + event.preventDefault(); + const point = pointFromEvent(event); + (_this$responder16 = this.responder) === null || _this$responder16 === void 0 || _this$responder16.setLocationRangeFromPointRange(point); + return this.attachFiles(event.dataTransfer.files); + } + }, + dragend() { + if (this.dragging) { + var _this$responder17; + (_this$responder17 = this.responder) === null || _this$responder17 === void 0 || _this$responder17.setSelectedRange(this.dragging.range); + this.dragging = null; + } + }, + compositionend(event) { + if (this.composing) { + this.composing = false; + if (!browser$1.recentAndroid) this.scheduleRender(); + } + } + }); + _defineProperty(Level2InputController, "keys", { + ArrowLeft() { + var _this$responder18; + if ((_this$responder18 = this.responder) !== null && _this$responder18 !== void 0 && _this$responder18.shouldManageMovingCursorInDirection("backward")) { + var _this$responder19; + this.event.preventDefault(); + return (_this$responder19 = this.responder) === null || _this$responder19 === void 0 ? void 0 : _this$responder19.moveCursorInDirection("backward"); + } + }, + ArrowRight() { + var _this$responder20; + if ((_this$responder20 = this.responder) !== null && _this$responder20 !== void 0 && _this$responder20.shouldManageMovingCursorInDirection("forward")) { + var _this$responder21; + this.event.preventDefault(); + return (_this$responder21 = this.responder) === null || _this$responder21 === void 0 ? void 0 : _this$responder21.moveCursorInDirection("forward"); + } + }, + Backspace() { + var _this$responder22; + if ((_this$responder22 = this.responder) !== null && _this$responder22 !== void 0 && _this$responder22.shouldManageDeletingInDirection("backward")) { + var _this$delegate14, _this$responder23; + this.event.preventDefault(); + (_this$delegate14 = this.delegate) === null || _this$delegate14 === void 0 || _this$delegate14.inputControllerWillPerformTyping(); + (_this$responder23 = this.responder) === null || _this$responder23 === void 0 || _this$responder23.deleteInDirection("backward"); + return this.render(); } }, - keypress(event) { - if (this.inputSummary.eventName != null) return; - if (event.metaKey) return; - if (event.ctrlKey && !event.altKey) return; - const string = stringFromKeyEvent(event); - if (string) { - var _this$delegate4, _this$responder9; - (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillPerformTyping(); - (_this$responder9 = this.responder) === null || _this$responder9 === void 0 || _this$responder9.insertString(string); - return this.setInputSummary({ - textAdded: string, - didDelete: this.selectionIsExpanded() - }); + Tab() { + var _this$responder24; + if ((_this$responder24 = this.responder) !== null && _this$responder24 !== void 0 && _this$responder24.canIncreaseNestingLevel()) { + var _this$responder25; + this.event.preventDefault(); + (_this$responder25 = this.responder) === null || _this$responder25 === void 0 || _this$responder25.increaseNestingLevel(); + return this.render(); } }, - textInput(event) { - // Handle autocapitalization - const { - data - } = event; - const { - textAdded - } = this.inputSummary; - if (textAdded && textAdded !== data && textAdded.toUpperCase() === data) { - var _this$responder10; - const range = this.getSelectedRange(); - this.setSelectedRange([range[0], range[1] + textAdded.length]); - (_this$responder10 = this.responder) === null || _this$responder10 === void 0 || _this$responder10.insertString(data); - this.setInputSummary({ - textAdded: data - }); - return this.setSelectedRange(range); + "Tab+Shift"() { + var _this$responder26; + if ((_this$responder26 = this.responder) !== null && _this$responder26 !== void 0 && _this$responder26.canDecreaseNestingLevel()) { + var _this$responder27; + this.event.preventDefault(); + (_this$responder27 = this.responder) === null || _this$responder27 === void 0 || _this$responder27.decreaseNestingLevel(); + return this.render(); } + } + }); + _defineProperty(Level2InputController, "inputTypes", { + deleteByComposition() { + return this.deleteInDirection("backward", { + recordUndoEntry: false + }); }, - dragenter(event) { - event.preventDefault(); + deleteByCut() { + return this.deleteInDirection("backward"); }, - dragstart(event) { - var _this$delegate5, _this$delegate5$input; - this.serializeSelectionToDataTransfer(event.dataTransfer); - this.draggedRange = this.getSelectedRange(); - return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$input = _this$delegate5.inputControllerDidStartDrag) === null || _this$delegate5$input === void 0 ? void 0 : _this$delegate5$input.call(_this$delegate5); + deleteByDrag() { + this.event.preventDefault(); + return this.withTargetDOMRange(function () { + var _this$responder28; + this.deleteByDragRange = (_this$responder28 = this.responder) === null || _this$responder28 === void 0 ? void 0 : _this$responder28.getSelectedRange(); + }); }, - dragover(event) { - if (this.draggedRange || this.canAcceptDataTransfer(event.dataTransfer)) { - event.preventDefault(); - const draggingPoint = { - x: event.clientX, - y: event.clientY - }; - if (!objectsAreEqual(draggingPoint, this.draggingPoint)) { - var _this$delegate6, _this$delegate6$input; - this.draggingPoint = draggingPoint; - return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$input = _this$delegate6.inputControllerDidReceiveDragOverPoint) === null || _this$delegate6$input === void 0 ? void 0 : _this$delegate6$input.call(_this$delegate6, this.draggingPoint); - } - } + deleteCompositionText() { + return this.deleteInDirection("backward", { + recordUndoEntry: false + }); }, - dragend(event) { - var _this$delegate7, _this$delegate7$input; - (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$input = _this$delegate7.inputControllerDidCancelDrag) === null || _this$delegate7$input === void 0 || _this$delegate7$input.call(_this$delegate7); - this.draggedRange = null; - this.draggingPoint = null; + deleteContent() { + return this.deleteInDirection("backward"); }, - drop(event) { - var _event$dataTransfer, _this$responder11; - event.preventDefault(); - const files = (_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.files; - const documentJSON = event.dataTransfer.getData("application/x-trix-document"); - const point = { - x: event.clientX, - y: event.clientY - }; - (_this$responder11 = this.responder) === null || _this$responder11 === void 0 || _this$responder11.setLocationRangeFromPointRange(point); - if (files !== null && files !== void 0 && files.length) { - this.attachFiles(files); - } else if (this.draggedRange) { - var _this$delegate8, _this$responder12; - (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || _this$delegate8.inputControllerWillMoveText(); - (_this$responder12 = this.responder) === null || _this$responder12 === void 0 || _this$responder12.moveTextFromRange(this.draggedRange); - this.draggedRange = null; - this.requestRender(); - } else if (documentJSON) { - var _this$responder13; - const document = Document.fromJSONString(documentJSON); - (_this$responder13 = this.responder) === null || _this$responder13 === void 0 || _this$responder13.insertDocument(document); - this.requestRender(); - } - this.draggedRange = null; - this.draggingPoint = null; + deleteContentBackward() { + return this.deleteInDirection("backward"); }, - cut(event) { - var _this$responder14; - if ((_this$responder14 = this.responder) !== null && _this$responder14 !== void 0 && _this$responder14.selectionIsExpanded()) { - var _this$delegate9; - if (this.serializeSelectionToDataTransfer(event.clipboardData)) { - event.preventDefault(); - } - (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || _this$delegate9.inputControllerWillCutText(); - this.deleteInDirection("backward"); - if (event.defaultPrevented) { - return this.requestRender(); - } - } + deleteContentForward() { + return this.deleteInDirection("forward"); }, - copy(event) { - var _this$responder15; - if ((_this$responder15 = this.responder) !== null && _this$responder15 !== void 0 && _this$responder15.selectionIsExpanded()) { - if (this.serializeSelectionToDataTransfer(event.clipboardData)) { - event.preventDefault(); - } + deleteEntireSoftLine() { + return this.deleteInDirection("forward"); + }, + deleteHardLineBackward() { + return this.deleteInDirection("backward"); + }, + deleteHardLineForward() { + return this.deleteInDirection("forward"); + }, + deleteSoftLineBackward() { + return this.deleteInDirection("backward"); + }, + deleteSoftLineForward() { + return this.deleteInDirection("forward"); + }, + deleteWordBackward() { + return this.deleteInDirection("backward"); + }, + deleteWordForward() { + return this.deleteInDirection("forward"); + }, + formatBackColor() { + return this.activateAttributeIfSupported("backgroundColor", this.event.data); + }, + formatBold() { + return this.toggleAttributeIfSupported("bold"); + }, + formatFontColor() { + return this.activateAttributeIfSupported("color", this.event.data); + }, + formatFontName() { + return this.activateAttributeIfSupported("font", this.event.data); + }, + formatIndent() { + var _this$responder29; + if ((_this$responder29 = this.responder) !== null && _this$responder29 !== void 0 && _this$responder29.canIncreaseNestingLevel()) { + return this.withTargetDOMRange(function () { + var _this$responder30; + return (_this$responder30 = this.responder) === null || _this$responder30 === void 0 ? void 0 : _this$responder30.increaseNestingLevel(); + }); } }, - paste(event) { - const clipboard = event.clipboardData || event.testClipboardData; - const paste = { - clipboard - }; - if (!clipboard || pasteEventIsCrippledSafariHTMLPaste(event)) { - this.getPastedHTMLUsingHiddenElement(html => { - var _this$delegate10, _this$responder16, _this$delegate11; - paste.type = "text/html"; - paste.html = html; - (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || _this$delegate10.inputControllerWillPaste(paste); - (_this$responder16 = this.responder) === null || _this$responder16 === void 0 || _this$responder16.insertHTML(paste.html); - this.requestRender(); - return (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 ? void 0 : _this$delegate11.inputControllerDidPaste(paste); + formatItalic() { + return this.toggleAttributeIfSupported("italic"); + }, + formatJustifyCenter() { + return this.toggleAttributeIfSupported("justifyCenter"); + }, + formatJustifyFull() { + return this.toggleAttributeIfSupported("justifyFull"); + }, + formatJustifyLeft() { + return this.toggleAttributeIfSupported("justifyLeft"); + }, + formatJustifyRight() { + return this.toggleAttributeIfSupported("justifyRight"); + }, + formatOutdent() { + var _this$responder31; + if ((_this$responder31 = this.responder) !== null && _this$responder31 !== void 0 && _this$responder31.canDecreaseNestingLevel()) { + return this.withTargetDOMRange(function () { + var _this$responder32; + return (_this$responder32 = this.responder) === null || _this$responder32 === void 0 ? void 0 : _this$responder32.decreaseNestingLevel(); }); - return; } - const href = clipboard.getData("URL"); - const html = clipboard.getData("text/html"); - const name = clipboard.getData("public.url-name"); + }, + formatRemove() { + this.withTargetDOMRange(function () { + for (const attributeName in (_this$responder33 = this.responder) === null || _this$responder33 === void 0 ? void 0 : _this$responder33.getCurrentAttributes()) { + var _this$responder33, _this$responder34; + (_this$responder34 = this.responder) === null || _this$responder34 === void 0 || _this$responder34.removeCurrentAttribute(attributeName); + } + }); + }, + formatSetBlockTextDirection() { + return this.activateAttributeIfSupported("blockDir", this.event.data); + }, + formatSetInlineTextDirection() { + return this.activateAttributeIfSupported("textDir", this.event.data); + }, + formatStrikeThrough() { + return this.toggleAttributeIfSupported("strike"); + }, + formatSubscript() { + return this.toggleAttributeIfSupported("sub"); + }, + formatSuperscript() { + return this.toggleAttributeIfSupported("sup"); + }, + formatUnderline() { + return this.toggleAttributeIfSupported("underline"); + }, + historyRedo() { + var _this$delegate15; + return (_this$delegate15 = this.delegate) === null || _this$delegate15 === void 0 ? void 0 : _this$delegate15.inputControllerWillPerformRedo(); + }, + historyUndo() { + var _this$delegate16; + return (_this$delegate16 = this.delegate) === null || _this$delegate16 === void 0 ? void 0 : _this$delegate16.inputControllerWillPerformUndo(); + }, + insertCompositionText() { + this.composing = true; + return this.insertString(this.event.data); + }, + insertFromComposition() { + this.composing = false; + return this.insertString(this.event.data); + }, + insertFromDrop() { + const range = this.deleteByDragRange; + if (range) { + var _this$delegate17; + this.deleteByDragRange = null; + (_this$delegate17 = this.delegate) === null || _this$delegate17 === void 0 || _this$delegate17.inputControllerWillMoveText(); + return this.withTargetDOMRange(function () { + var _this$responder35; + return (_this$responder35 = this.responder) === null || _this$responder35 === void 0 ? void 0 : _this$responder35.moveTextFromRange(range); + }); + } + }, + insertFromPaste() { + const { + dataTransfer + } = this.event; + const paste = { + dataTransfer + }; + const href = dataTransfer.getData("URL"); + const html = dataTransfer.getData("text/html"); if (href) { - var _this$delegate12, _this$responder17, _this$delegate13; + var _this$delegate18; let string; + this.event.preventDefault(); paste.type = "text/html"; + const name = dataTransfer.getData("public.url-name"); if (name) { string = squishBreakableWhitespace(name).trim(); } else { string = href; } paste.html = this.createLinkHTML(href, string); - (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 || _this$delegate12.inputControllerWillPaste(paste); - this.setInputSummary({ - textAdded: string, - didDelete: this.selectionIsExpanded() + (_this$delegate18 = this.delegate) === null || _this$delegate18 === void 0 || _this$delegate18.inputControllerWillPaste(paste); + this.withTargetDOMRange(function () { + var _this$responder36; + return (_this$responder36 = this.responder) === null || _this$responder36 === void 0 ? void 0 : _this$responder36.insertHTML(paste.html); }); - (_this$responder17 = this.responder) === null || _this$responder17 === void 0 || _this$responder17.insertHTML(paste.html); - this.requestRender(); - (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || _this$delegate13.inputControllerDidPaste(paste); - } else if (dataTransferIsPlainText(clipboard)) { - var _this$delegate14, _this$responder18, _this$delegate15; + this.afterRender = () => { + var _this$delegate19; + return (_this$delegate19 = this.delegate) === null || _this$delegate19 === void 0 ? void 0 : _this$delegate19.inputControllerDidPaste(paste); + }; + } else if (dataTransferIsPlainText(dataTransfer)) { + var _this$delegate20; paste.type = "text/plain"; - paste.string = clipboard.getData("text/plain"); - (_this$delegate14 = this.delegate) === null || _this$delegate14 === void 0 || _this$delegate14.inputControllerWillPaste(paste); - this.setInputSummary({ - textAdded: paste.string, - didDelete: this.selectionIsExpanded() + paste.string = dataTransfer.getData("text/plain"); + (_this$delegate20 = this.delegate) === null || _this$delegate20 === void 0 || _this$delegate20.inputControllerWillPaste(paste); + this.withTargetDOMRange(function () { + var _this$responder37; + return (_this$responder37 = this.responder) === null || _this$responder37 === void 0 ? void 0 : _this$responder37.insertString(paste.string); + }); + this.afterRender = () => { + var _this$delegate21; + return (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 ? void 0 : _this$delegate21.inputControllerDidPaste(paste); + }; + } else if (processableFilePaste(this.event)) { + var _this$delegate22; + paste.type = "File"; + paste.file = dataTransfer.files[0]; + (_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPaste(paste); + this.withTargetDOMRange(function () { + var _this$responder38; + return (_this$responder38 = this.responder) === null || _this$responder38 === void 0 ? void 0 : _this$responder38.insertFile(paste.file); + }); + this.afterRender = () => { + var _this$delegate23; + return (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 ? void 0 : _this$delegate23.inputControllerDidPaste(paste); + }; + } else if (html) { + var _this$delegate24; + this.event.preventDefault(); + paste.type = "text/html"; + paste.html = html; + (_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPaste(paste); + this.withTargetDOMRange(function () { + var _this$responder39; + return (_this$responder39 = this.responder) === null || _this$responder39 === void 0 ? void 0 : _this$responder39.insertHTML(paste.html); + }); + this.afterRender = () => { + var _this$delegate25; + return (_this$delegate25 = this.delegate) === null || _this$delegate25 === void 0 ? void 0 : _this$delegate25.inputControllerDidPaste(paste); + }; + } + }, + insertFromYank() { + return this.insertString(this.event.data); + }, + insertLineBreak() { + return this.insertString("\n"); + }, + insertLink() { + return this.activateAttributeIfSupported("href", this.event.data); + }, + insertOrderedList() { + return this.toggleAttributeIfSupported("number"); + }, + insertParagraph() { + var _this$delegate26; + (_this$delegate26 = this.delegate) === null || _this$delegate26 === void 0 || _this$delegate26.inputControllerWillPerformTyping(); + return this.withTargetDOMRange(function () { + var _this$responder40; + return (_this$responder40 = this.responder) === null || _this$responder40 === void 0 ? void 0 : _this$responder40.insertLineBreak(); + }); + }, + insertReplacementText() { + var _this$event$getTarget2; + const replacement = this.event.dataTransfer.getData("text/plain"); + const domRange = (_this$event$getTarget2 = this.event.getTargetRanges()) === null || _this$event$getTarget2 === void 0 ? void 0 : _this$event$getTarget2[0]; + if (!domRange) return; + const { + isAtCursor, + cursorPosition, + targetStart, + targetEnd + } = this.isReplacementAtCursor(domRange); + this.withTargetDOMRange(domRange, () => { + this.insertString(replacement, { + updatePosition: false + }); + }); + + // Safari has a bug where it positions the cursor incorrectly after Smart Quotes and similar + // text replacement operations. We need to manually calculate and set the correct cursor position, + // then prevent Safari from overwriting it. + if (isAtCursor) { + var _this$responder41; + const oldLength = targetEnd - targetStart; + const newLength = replacement.length; + // +1 accounts for the triggering character (e.g., space that triggered Smart Quotes) + const newCursor = cursorPosition + (newLength - oldLength) + 1; + (_this$responder41 = this.responder) === null || _this$responder41 === void 0 || _this$responder41.setSelectedRange([newCursor, newCursor]); + + // Prevent selection syncing until Safari has finished its buggy cursor positioning + const state = editorStates.get(this.element); + state.preventSelectionSync = true; + state.pendingInputEventsToIgnore = INPUT_EVENTS_TO_IGNORE_COUNT; + + // Use requestAnimationFrame to restore cursor after Safari's events complete + requestAnimationFrame(() => { + var _this$responder42; + (_this$responder42 = this.responder) === null || _this$responder42 === void 0 || _this$responder42.setSelectedRange([newCursor, newCursor]); + state.preventSelectionSync = false; + }); + } + }, + insertText() { + var _this$event$dataTrans; + return this.insertString(this.event.data || ((_this$event$dataTrans = this.event.dataTransfer) === null || _this$event$dataTrans === void 0 ? void 0 : _this$event$dataTrans.getData("text/plain"))); + }, + insertTranspose() { + return this.insertString(this.event.data); + }, + insertUnorderedList() { + return this.toggleAttributeIfSupported("bullet"); + } + }); + const staticRangeToRange = function (staticRange) { + const range = document.createRange(); + range.setStart(staticRange.startContainer, staticRange.startOffset); + range.setEnd(staticRange.endContainer, staticRange.endOffset); + return range; + }; + + // Event helpers + + const dragEventHasFiles = event => { + var _event$dataTransfer; + return Array.from(((_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.types) || []).includes("Files"); + }; + const processableFilePaste = event => { + var _event$dataTransfer$f; + // Paste events that only have files are handled by the paste event handler, + // to work around Safari not supporting beforeinput.insertFromPaste for files. + + // MS Office text pastes include a file with a screenshot of the text, but we should + // handle them as text pastes. + return ((_event$dataTransfer$f = event.dataTransfer.files) === null || _event$dataTransfer$f === void 0 ? void 0 : _event$dataTransfer$f[0]) && !pasteEventHasFilesOnly(event) && !dataTransferIsMsOfficePaste(event); + }; + const pasteEventHasFilesOnly = function (event) { + const clipboard = event.clipboardData; + if (clipboard) { + const fileTypes = Array.from(clipboard.types).filter(type => type.match(/file/i)); // "Files", "application/x-moz-file" + return fileTypes.length === clipboard.types.length && clipboard.files.length >= 1; + } + }; + const pasteEventHasPlainTextOnly = function (event) { + const clipboard = event.clipboardData; + if (clipboard) { + return clipboard.types.includes("text/plain") && clipboard.types.length === 1; + } + }; + const keyboardCommandFromKeyEvent = function (event) { + const command = []; + if (event.altKey) { + command.push("alt"); + } + if (event.shiftKey) { + command.push("shift"); + } + command.push(event.key); + return command; + }; + const pointFromEvent = event => ({ + x: event.clientX, + y: event.clientY + }); + + /* eslint-disable + */ + class SelectionManager extends BasicObject { + constructor(element) { + super(...arguments); + this.didMouseDown = this.didMouseDown.bind(this); + this.selectionDidChange = this.selectionDidChange.bind(this); + this.element = element; + this.locationMapper = new LocationMapper(this.element); + this.pointMapper = new PointMapper(); + this.lockCount = 0; + handleEvent("mousedown", { + onElement: this.element, + withCallback: this.didMouseDown + }); + } + getLocationRange() { + let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + if (options.strict === false) { + return this.createLocationRangeFromDOMRange(getDOMRange()); + } else if (options.ignoreLock) { + return this.currentLocationRange; + } else if (this.lockedLocationRange) { + return this.lockedLocationRange; + } else { + return this.currentLocationRange; + } + } + setLocationRange(locationRange) { + if (this.lockedLocationRange) return; + locationRange = normalizeRange(locationRange); + const domRange = this.createDOMRangeFromLocationRange(locationRange); + if (domRange) { + setDOMRange(domRange); + this.updateCurrentLocationRange(locationRange); + } + } + setLocationRangeFromPointRange(pointRange) { + pointRange = normalizeRange(pointRange); + const startLocation = this.getLocationAtPoint(pointRange[0]); + const endLocation = this.getLocationAtPoint(pointRange[1]); + this.setLocationRange([startLocation, endLocation]); + } + getClientRectAtLocationRange(locationRange) { + const domRange = this.createDOMRangeFromLocationRange(locationRange); + if (domRange) { + return this.getClientRectsForDOMRange(domRange)[1]; + } + } + locationIsCursorTarget(location) { + const node = Array.from(this.findNodeAndOffsetFromLocation(location))[0]; + return nodeIsCursorTarget(node); + } + lock() { + if (this.lockCount++ === 0) { + this.updateCurrentLocationRange(); + this.lockedLocationRange = this.getLocationRange(); + } + } + unlock() { + if (--this.lockCount === 0) { + const { + lockedLocationRange + } = this; + this.lockedLocationRange = null; + if (lockedLocationRange != null) { + return this.setLocationRange(lockedLocationRange); + } + } + } + clearSelection() { + var _getDOMSelection; + return (_getDOMSelection = getDOMSelection()) === null || _getDOMSelection === void 0 ? void 0 : _getDOMSelection.removeAllRanges(); + } + selectionIsCollapsed() { + var _getDOMRange; + return ((_getDOMRange = getDOMRange()) === null || _getDOMRange === void 0 ? void 0 : _getDOMRange.collapsed) === true; + } + selectionIsExpanded() { + return !this.selectionIsCollapsed(); + } + createLocationRangeFromDOMRange(domRange, options) { + if (domRange == null || !this.domRangeWithinElement(domRange)) return; + const start = this.findLocationFromContainerAndOffset(domRange.startContainer, domRange.startOffset, options); + if (!start) return; + const end = domRange.collapsed ? undefined : this.findLocationFromContainerAndOffset(domRange.endContainer, domRange.endOffset, options); + return normalizeRange([start, end]); + } + didMouseDown() { + return this.pauseTemporarily(); + } + pauseTemporarily() { + let resumeHandlers; + this.paused = true; + const resume = () => { + this.paused = false; + clearTimeout(resumeTimeout); + Array.from(resumeHandlers).forEach(handler => { + handler.destroy(); }); - (_this$responder18 = this.responder) === null || _this$responder18 === void 0 || _this$responder18.insertString(paste.string); - this.requestRender(); - (_this$delegate15 = this.delegate) === null || _this$delegate15 === void 0 || _this$delegate15.inputControllerDidPaste(paste); - } else if (html) { - var _this$delegate16, _this$responder19, _this$delegate17; - paste.type = "text/html"; - paste.html = html; - (_this$delegate16 = this.delegate) === null || _this$delegate16 === void 0 || _this$delegate16.inputControllerWillPaste(paste); - (_this$responder19 = this.responder) === null || _this$responder19 === void 0 || _this$responder19.insertHTML(paste.html); - this.requestRender(); - (_this$delegate17 = this.delegate) === null || _this$delegate17 === void 0 || _this$delegate17.inputControllerDidPaste(paste); - } else if (Array.from(clipboard.types).includes("Files")) { - var _clipboard$items, _clipboard$items$getA; - const file = (_clipboard$items = clipboard.items) === null || _clipboard$items === void 0 || (_clipboard$items = _clipboard$items[0]) === null || _clipboard$items === void 0 || (_clipboard$items$getA = _clipboard$items.getAsFile) === null || _clipboard$items$getA === void 0 ? void 0 : _clipboard$items$getA.call(_clipboard$items); - if (file) { - var _this$delegate18, _this$responder20, _this$delegate19; - const extension = extensionForFile(file); - if (!file.name && extension) { - file.name = "pasted-file-".concat(++pastedFileCount, ".").concat(extension); - } - paste.type = "File"; - paste.file = file; - (_this$delegate18 = this.delegate) === null || _this$delegate18 === void 0 || _this$delegate18.inputControllerWillAttachFiles(); - (_this$responder20 = this.responder) === null || _this$responder20 === void 0 || _this$responder20.insertFile(paste.file); - this.requestRender(); - (_this$delegate19 = this.delegate) === null || _this$delegate19 === void 0 || _this$delegate19.inputControllerDidPaste(paste); + if (elementContainsNode(document, this.element)) { + return this.selectionDidChange(); } + }; + const resumeTimeout = setTimeout(resume, 200); + resumeHandlers = ["mousemove", "keydown"].map(eventName => handleEvent(eventName, { + onElement: document, + withCallback: resume + })); + } + selectionDidChange() { + // Skip selection sync if we're working around Safari Smart Quotes bug + if (shouldPreventSelectionSync !== null && shouldPreventSelectionSync !== void 0 && shouldPreventSelectionSync(this.element)) return; + if (!this.paused && !innerElementIsActive(this.element)) { + return this.updateCurrentLocationRange(); } - event.preventDefault(); - }, - compositionstart(event) { - return this.getCompositionInput().start(event.data); - }, - compositionupdate(event) { - return this.getCompositionInput().update(event.data); - }, - compositionend(event) { - return this.getCompositionInput().end(event.data); - }, - beforeinput(event) { - this.inputSummary.didInput = true; - }, - input(event) { - this.inputSummary.didInput = true; - return event.stopPropagation(); } - }); - _defineProperty(Level0InputController, "keys", { - backspace(event) { - var _this$delegate20; - (_this$delegate20 = this.delegate) === null || _this$delegate20 === void 0 || _this$delegate20.inputControllerWillPerformTyping(); - return this.deleteInDirection("backward", event); - }, - delete(event) { - var _this$delegate21; - (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 || _this$delegate21.inputControllerWillPerformTyping(); - return this.deleteInDirection("forward", event); - }, - return(event) { - var _this$delegate22, _this$responder21; - this.setInputSummary({ - preferDocument: true - }); - (_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPerformTyping(); - return (_this$responder21 = this.responder) === null || _this$responder21 === void 0 ? void 0 : _this$responder21.insertLineBreak(); - }, - tab(event) { - var _this$responder22; - if ((_this$responder22 = this.responder) !== null && _this$responder22 !== void 0 && _this$responder22.canIncreaseNestingLevel()) { - var _this$responder23; - (_this$responder23 = this.responder) === null || _this$responder23 === void 0 || _this$responder23.increaseNestingLevel(); - this.requestRender(); - event.preventDefault(); + updateCurrentLocationRange(locationRange) { + if (locationRange != null ? locationRange : locationRange = this.createLocationRangeFromDOMRange(getDOMRange())) { + if (!rangesAreEqual(locationRange, this.currentLocationRange)) { + var _this$delegate, _this$delegate$locati; + this.currentLocationRange = locationRange; + return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$locati = _this$delegate.locationRangeDidChange) === null || _this$delegate$locati === void 0 ? void 0 : _this$delegate$locati.call(_this$delegate, this.currentLocationRange.slice(0)); + } } - }, - left(event) { - if (this.selectionIsInCursorTarget()) { - var _this$responder24; - event.preventDefault(); - return (_this$responder24 = this.responder) === null || _this$responder24 === void 0 ? void 0 : _this$responder24.moveCursorInDirection("backward"); + } + createDOMRangeFromLocationRange(locationRange) { + const rangeStart = this.findContainerAndOffsetFromLocation(locationRange[0]); + const rangeEnd = rangeIsCollapsed(locationRange) ? rangeStart : this.findContainerAndOffsetFromLocation(locationRange[1]) || rangeStart; + if (rangeStart != null && rangeEnd != null) { + const domRange = document.createRange(); + domRange.setStart(...Array.from(rangeStart || [])); + domRange.setEnd(...Array.from(rangeEnd || [])); + return domRange; } - }, - right(event) { - if (this.selectionIsInCursorTarget()) { - var _this$responder25; - event.preventDefault(); - return (_this$responder25 = this.responder) === null || _this$responder25 === void 0 ? void 0 : _this$responder25.moveCursorInDirection("forward"); + } + getLocationAtPoint(point) { + const domRange = this.createDOMRangeFromPoint(point); + if (domRange) { + var _this$createLocationR; + return (_this$createLocationR = this.createLocationRangeFromDOMRange(domRange)) === null || _this$createLocationR === void 0 ? void 0 : _this$createLocationR[0]; } - }, - control: { - d(event) { - var _this$delegate23; - (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 || _this$delegate23.inputControllerWillPerformTyping(); - return this.deleteInDirection("forward", event); - }, - h(event) { - var _this$delegate24; - (_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPerformTyping(); - return this.deleteInDirection("backward", event); - }, - o(event) { - var _this$delegate25, _this$responder26; - event.preventDefault(); - (_this$delegate25 = this.delegate) === null || _this$delegate25 === void 0 || _this$delegate25.inputControllerWillPerformTyping(); - (_this$responder26 = this.responder) === null || _this$responder26 === void 0 || _this$responder26.insertString("\n", { - updatePosition: false - }); - return this.requestRender(); + } + domRangeWithinElement(domRange) { + if (domRange.collapsed) { + return elementContainsNode(this.element, domRange.startContainer); + } else { + return elementContainsNode(this.element, domRange.startContainer) && elementContainsNode(this.element, domRange.endContainer); } - }, - shift: { - return(event) { - var _this$delegate26, _this$responder27; - (_this$delegate26 = this.delegate) === null || _this$delegate26 === void 0 || _this$delegate26.inputControllerWillPerformTyping(); - (_this$responder27 = this.responder) === null || _this$responder27 === void 0 || _this$responder27.insertString("\n"); - this.requestRender(); - event.preventDefault(); - }, - tab(event) { - var _this$responder28; - if ((_this$responder28 = this.responder) !== null && _this$responder28 !== void 0 && _this$responder28.canDecreaseNestingLevel()) { - var _this$responder29; - (_this$responder29 = this.responder) === null || _this$responder29 === void 0 || _this$responder29.decreaseNestingLevel(); - this.requestRender(); - event.preventDefault(); - } - }, - left(event) { - if (this.selectionIsInCursorTarget()) { - event.preventDefault(); - return this.expandSelectionInDirection("backward"); - } - }, - right(event) { - if (this.selectionIsInCursorTarget()) { - event.preventDefault(); - return this.expandSelectionInDirection("forward"); + } + } + SelectionManager.proxyMethod("locationMapper.findLocationFromContainerAndOffset"); + SelectionManager.proxyMethod("locationMapper.findContainerAndOffsetFromLocation"); + SelectionManager.proxyMethod("locationMapper.findNodeAndOffsetFromLocation"); + SelectionManager.proxyMethod("pointMapper.createDOMRangeFromPoint"); + SelectionManager.proxyMethod("pointMapper.getClientRectsForDOMRange"); + + var models = /*#__PURE__*/Object.freeze({ + __proto__: null, + Attachment: Attachment, + AttachmentManager: AttachmentManager, + AttachmentPiece: AttachmentPiece, + Block: Block, + Composition: Composition, + Document: Document, + Editor: Editor, + HTMLParser: HTMLParser, + HTMLSanitizer: HTMLSanitizer, + LineBreakInsertion: LineBreakInsertion, + LocationMapper: LocationMapper, + ManagedAttachment: ManagedAttachment, + Piece: Piece, + PointMapper: PointMapper, + SelectionManager: SelectionManager, + SplittableList: SplittableList, + StringPiece: StringPiece, + Text: Text, + UndoManager: UndoManager + }); + + var views = /*#__PURE__*/Object.freeze({ + __proto__: null, + ObjectView: ObjectView, + AttachmentView: AttachmentView, + BlockView: BlockView, + DocumentView: DocumentView, + PieceView: PieceView, + PreviewableAttachmentView: PreviewableAttachmentView, + TextView: TextView + }); + + const { + lang, + css, + keyNames: keyNames$1 + } = config; + const undoable = function (fn) { + return function () { + const commands = fn.apply(this, arguments); + commands.do(); + if (!this.undos) { + this.undos = []; + } + this.undos.push(commands.undo); + }; + }; + class AttachmentEditorController extends BasicObject { + constructor(attachmentPiece, _element, container) { + let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + super(...arguments); + // Installing and uninstalling + _defineProperty(this, "makeElementMutable", undoable(() => { + return { + do: () => { + this.element.dataset.trixMutable = true; + }, + undo: () => delete this.element.dataset.trixMutable + }; + })); + _defineProperty(this, "addToolbar", undoable(() => { + // + const element = makeElement({ + tagName: "div", + className: css.attachmentToolbar, + data: { + trixMutable: true + }, + childNodes: makeElement({ + tagName: "div", + className: "trix-button-row", + childNodes: makeElement({ + tagName: "span", + className: "trix-button-group trix-button-group--actions", + childNodes: makeElement({ + tagName: "button", + className: "trix-button trix-button--remove", + textContent: lang.remove, + attributes: { + title: lang.remove + }, + data: { + trixAction: "remove" + } + }) + }) + }) + }); + if (this.attachment.isPreviewable()) { + // + element.appendChild(makeElement({ + tagName: "div", + className: css.attachmentMetadataContainer, + childNodes: makeElement({ + tagName: "span", + className: css.attachmentMetadata, + childNodes: [makeElement({ + tagName: "span", + className: css.attachmentName, + textContent: this.attachment.getFilename(), + attributes: { + title: this.attachment.getFilename() + } + }), makeElement({ + tagName: "span", + className: css.attachmentSize, + textContent: this.attachment.getFormattedFilesize() + })] + }) + })); } - } - }, - alt: { - backspace(event) { - var _this$delegate27; - this.setInputSummary({ - preferDocument: false + handleEvent("click", { + onElement: element, + withCallback: this.didClickToolbar }); - return (_this$delegate27 = this.delegate) === null || _this$delegate27 === void 0 ? void 0 : _this$delegate27.inputControllerWillPerformTyping(); - } - }, - meta: { - backspace(event) { - var _this$delegate28; - this.setInputSummary({ - preferDocument: false + handleEvent("click", { + onElement: element, + matchingSelector: "[data-trix-action]", + withCallback: this.didClickActionButton }); - return (_this$delegate28 = this.delegate) === null || _this$delegate28 === void 0 ? void 0 : _this$delegate28.inputControllerWillPerformTyping(); - } - } - }); - Level0InputController.proxyMethod("responder?.getSelectedRange"); - Level0InputController.proxyMethod("responder?.setSelectedRange"); - Level0InputController.proxyMethod("responder?.expandSelectionInDirection"); - Level0InputController.proxyMethod("responder?.selectionIsInCursorTarget"); - Level0InputController.proxyMethod("responder?.selectionIsExpanded"); - const extensionForFile = file => { - var _file$type; - return (_file$type = file.type) === null || _file$type === void 0 || (_file$type = _file$type.match(/\/(\w+)$/)) === null || _file$type === void 0 ? void 0 : _file$type[1]; - }; - const hasStringCodePointAt = !!((_$codePointAt = (_ = " ").codePointAt) !== null && _$codePointAt !== void 0 && _$codePointAt.call(_, 0)); - const stringFromKeyEvent = function (event) { - if (event.key && hasStringCodePointAt && event.key.codePointAt(0) === event.keyCode) { - return event.key; - } else { - let code; - if (event.which === null) { - code = event.keyCode; - } else if (event.which !== 0 && event.charCode !== 0) { - code = event.charCode; - } - if (code != null && keyNames[code] !== "escape") { - return UTF16String.fromCodepoints([code]).toString(); - } - } - }; - const pasteEventIsCrippledSafariHTMLPaste = function (event) { - const paste = event.clipboardData; - if (paste) { - if (paste.types.includes("text/html")) { - // Answer is yes if there's any possibility of Paste and Match Style in Safari, - // which is nearly impossible to detect confidently: https://bugs.webkit.org/show_bug.cgi?id=174165 - for (const type of paste.types) { - const hasPasteboardFlavor = /^CorePasteboardFlavorType/.test(type); - const hasReadableDynamicData = /^dyn\./.test(type) && paste.getData(type); - const mightBePasteAndMatchStyle = hasPasteboardFlavor || hasReadableDynamicData; - if (mightBePasteAndMatchStyle) { - return true; + triggerEvent("trix-attachment-before-toolbar", { + onElement: this.element, + attributes: { + toolbar: element, + attachment: this.attachment } - } - return false; - } else { - const isExternalHTMLPaste = paste.types.includes("com.apple.webarchive"); - const isExternalRichTextPaste = paste.types.includes("com.apple.flat-rtfd"); - return isExternalHTMLPaste || isExternalRichTextPaste; + }); + return { + do: () => this.element.appendChild(element), + undo: () => removeNode(element) + }; + })); + _defineProperty(this, "installCaptionEditor", undoable(() => { + const textarea = makeElement({ + tagName: "textarea", + className: css.attachmentCaptionEditor, + attributes: { + placeholder: lang.captionPlaceholder + }, + data: { + trixMutable: true + } + }); + textarea.value = this.attachmentPiece.getCaption(); + const textareaClone = textarea.cloneNode(); + textareaClone.classList.add("trix-autoresize-clone"); + textareaClone.tabIndex = -1; + const autoresize = function () { + textareaClone.value = textarea.value; + textarea.style.height = textareaClone.scrollHeight + "px"; + }; + handleEvent("input", { + onElement: textarea, + withCallback: autoresize + }); + handleEvent("input", { + onElement: textarea, + withCallback: this.didInputCaption + }); + handleEvent("keydown", { + onElement: textarea, + withCallback: this.didKeyDownCaption + }); + handleEvent("change", { + onElement: textarea, + withCallback: this.didChangeCaption + }); + handleEvent("blur", { + onElement: textarea, + withCallback: this.didBlurCaption + }); + const figcaption = this.element.querySelector("figcaption"); + const editingFigcaption = figcaption.cloneNode(); + return { + do: () => { + figcaption.style.display = "none"; + editingFigcaption.appendChild(textarea); + editingFigcaption.appendChild(textareaClone); + editingFigcaption.classList.add("".concat(css.attachmentCaption, "--editing")); + figcaption.parentElement.insertBefore(editingFigcaption, figcaption); + autoresize(); + if (this.options.editCaption) { + return defer(() => textarea.focus()); + } + }, + undo() { + removeNode(editingFigcaption); + figcaption.style.display = null; + } + }; + })); + this.didClickToolbar = this.didClickToolbar.bind(this); + this.didClickActionButton = this.didClickActionButton.bind(this); + this.didKeyDownCaption = this.didKeyDownCaption.bind(this); + this.didInputCaption = this.didInputCaption.bind(this); + this.didChangeCaption = this.didChangeCaption.bind(this); + this.didBlurCaption = this.didBlurCaption.bind(this); + this.attachmentPiece = attachmentPiece; + this.element = _element; + this.container = container; + this.options = options; + this.attachment = this.attachmentPiece.attachment; + if (tagName(this.element) === "a") { + this.element = this.element.firstChild; } + this.install(); } - }; - class CompositionInput extends BasicObject { - constructor(inputController) { - super(...arguments); - this.inputController = inputController; - this.responder = this.inputController.responder; - this.delegate = this.inputController.delegate; - this.inputSummary = this.inputController.inputSummary; - this.data = {}; + install() { + this.makeElementMutable(); + this.addToolbar(); + if (this.attachment.isPreviewable()) { + this.installCaptionEditor(); + } } - start(data) { - this.data.start = data; - if (this.isSignificant()) { - var _this$responder5; - if (this.inputSummary.eventName === "keypress" && this.inputSummary.textAdded) { - var _this$responder4; - (_this$responder4 = this.responder) === null || _this$responder4 === void 0 || _this$responder4.deleteInDirection("left"); - } - if (!this.selectionIsExpanded()) { - this.insertPlaceholder(); - this.requestRender(); - } - this.range = (_this$responder5 = this.responder) === null || _this$responder5 === void 0 ? void 0 : _this$responder5.getSelectedRange(); + uninstall() { + var _this$delegate; + let undo = this.undos.pop(); + this.savePendingCaption(); + while (undo) { + undo(); + undo = this.undos.pop(); } + (_this$delegate = this.delegate) === null || _this$delegate === void 0 || _this$delegate.didUninstallAttachmentEditor(this); } - update(data) { - this.data.update = data; - if (this.isSignificant()) { - const range = this.selectPlaceholder(); - if (range) { - this.forgetPlaceholder(); - this.range = range; + + // Private + + savePendingCaption() { + if (this.pendingCaption != null) { + const caption = this.pendingCaption; + this.pendingCaption = null; + if (caption) { + var _this$delegate2, _this$delegate2$attac; + (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$attac = _this$delegate2.attachmentEditorDidRequestUpdatingAttributesForAttachment) === null || _this$delegate2$attac === void 0 || _this$delegate2$attac.call(_this$delegate2, { + caption + }, this.attachment); + } else { + var _this$delegate3, _this$delegate3$attac; + (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$attac = _this$delegate3.attachmentEditorDidRequestRemovingAttributeForAttachment) === null || _this$delegate3$attac === void 0 || _this$delegate3$attac.call(_this$delegate3, "caption", this.attachment); } } } - end(data) { - this.data.end = data; - if (this.isSignificant()) { - this.forgetPlaceholder(); - if (this.canApplyToDocument()) { - var _this$delegate2, _this$responder6, _this$responder7, _this$responder8; - this.setInputSummary({ - preferDocument: true, - didInput: false - }); - (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || _this$delegate2.inputControllerWillPerformTyping(); - (_this$responder6 = this.responder) === null || _this$responder6 === void 0 || _this$responder6.setSelectedRange(this.range); - (_this$responder7 = this.responder) === null || _this$responder7 === void 0 || _this$responder7.insertString(this.data.end); - return (_this$responder8 = this.responder) === null || _this$responder8 === void 0 ? void 0 : _this$responder8.setSelectedRange(this.range[0] + this.data.end.length); - } else if (this.data.start != null || this.data.update != null) { - this.requestReparse(); - return this.inputController.reset(); - } - } else { - return this.inputController.reset(); + // Event handlers + + didClickToolbar(event) { + event.preventDefault(); + return event.stopPropagation(); + } + didClickActionButton(event) { + var _this$delegate4; + const action = event.target.getAttribute("data-trix-action"); + switch (action) { + case "remove": + return (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 ? void 0 : _this$delegate4.attachmentEditorDidRequestRemovalOfAttachment(this.attachment); } } - getEndData() { - return this.data.end; + didKeyDownCaption(event) { + if (keyNames$1[event.keyCode] === "return") { + var _this$delegate5, _this$delegate5$attac; + event.preventDefault(); + this.savePendingCaption(); + return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$attac = _this$delegate5.attachmentEditorDidRequestDeselectingAttachment) === null || _this$delegate5$attac === void 0 ? void 0 : _this$delegate5$attac.call(_this$delegate5, this.attachment); + } } - isEnded() { - return this.getEndData() != null; + didInputCaption(event) { + this.pendingCaption = event.target.value.replace(/\s/g, " ").trim(); } - isSignificant() { - if (browser.composesExistingText) { - return this.inputSummary.didInput; - } else { - return true; - } + didChangeCaption(event) { + return this.savePendingCaption(); } - - // Private - - canApplyToDocument() { - var _this$data$start, _this$data$end; - return ((_this$data$start = this.data.start) === null || _this$data$start === void 0 ? void 0 : _this$data$start.length) === 0 && ((_this$data$end = this.data.end) === null || _this$data$end === void 0 ? void 0 : _this$data$end.length) > 0 && this.range; + didBlurCaption(event) { + return this.savePendingCaption(); } } - CompositionInput.proxyMethod("inputController.setInputSummary"); - CompositionInput.proxyMethod("inputController.requestRender"); - CompositionInput.proxyMethod("inputController.requestReparse"); - CompositionInput.proxyMethod("responder?.selectionIsExpanded"); - CompositionInput.proxyMethod("responder?.insertPlaceholder"); - CompositionInput.proxyMethod("responder?.selectPlaceholder"); - CompositionInput.proxyMethod("responder?.forgetPlaceholder"); - class Level2InputController extends InputController { - constructor() { + class CompositionController extends BasicObject { + constructor(element, composition) { super(...arguments); - this.render = this.render.bind(this); + this.didFocus = this.didFocus.bind(this); + this.didBlur = this.didBlur.bind(this); + this.didClickAttachment = this.didClickAttachment.bind(this); + this.element = element; + this.composition = composition; + this.documentView = new DocumentView(this.composition.document, { + element: this.element + }); + handleEvent("focus", { + onElement: this.element, + withCallback: this.didFocus + }); + handleEvent("blur", { + onElement: this.element, + withCallback: this.didBlur + }); + handleEvent("click", { + onElement: this.element, + matchingSelector: "a[contenteditable=false]", + preventDefault: true + }); + handleEvent("mousedown", { + onElement: this.element, + matchingSelector: attachmentSelector, + withCallback: this.didClickAttachment + }); + handleEvent("click", { + onElement: this.element, + matchingSelector: "a".concat(attachmentSelector), + preventDefault: true + }); } - elementDidMutate() { - if (this.scheduledRender) { - if (this.composing) { - var _this$delegate, _this$delegate$inputC; - return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidAllowUnhandledInput) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate); + didFocus(event) { + var _this$blurPromise; + const perform = () => { + if (!this.focused) { + var _this$delegate, _this$delegate$compos; + this.focused = true; + return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$compos = _this$delegate.compositionControllerDidFocus) === null || _this$delegate$compos === void 0 ? void 0 : _this$delegate$compos.call(_this$delegate); } + }; + return ((_this$blurPromise = this.blurPromise) === null || _this$blurPromise === void 0 ? void 0 : _this$blurPromise.then(perform)) || perform(); + } + didBlur(event) { + this.blurPromise = new Promise(resolve => { + return defer(() => { + if (!innerElementIsActive(this.element)) { + var _this$delegate2, _this$delegate2$compo; + this.focused = null; + (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || (_this$delegate2$compo = _this$delegate2.compositionControllerDidBlur) === null || _this$delegate2$compo === void 0 || _this$delegate2$compo.call(_this$delegate2); + } + this.blurPromise = null; + return resolve(); + }); + }); + } + didClickAttachment(event, target) { + var _this$delegate3, _this$delegate3$compo; + const attachment = this.findAttachmentForElement(target); + const editCaption = !!findClosestElementFromNode(event.target, { + matchingSelector: "figcaption" + }); + return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 || (_this$delegate3$compo = _this$delegate3.compositionControllerDidSelectAttachment) === null || _this$delegate3$compo === void 0 ? void 0 : _this$delegate3$compo.call(_this$delegate3, attachment, { + editCaption + }); + } + getSerializableElement() { + if (this.isEditingAttachment()) { + return this.documentView.shadowElement; } else { - return this.reparse(); + return this.element; } } - scheduleRender() { - return this.scheduledRender ? this.scheduledRender : this.scheduledRender = requestAnimationFrame(this.render); - } render() { - var _this$afterRender; - cancelAnimationFrame(this.scheduledRender); - this.scheduledRender = null; - if (!this.composing) { - var _this$delegate2; - (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || _this$delegate2.render(); + var _this$delegate6, _this$delegate6$compo; + if (this.revision !== this.composition.revision) { + this.documentView.setDocument(this.composition.document); + this.documentView.render(); + this.revision = this.composition.revision; } - (_this$afterRender = this.afterRender) === null || _this$afterRender === void 0 || _this$afterRender.call(this); - this.afterRender = null; + if (this.canSyncDocumentView() && !this.documentView.isSynced()) { + var _this$delegate4, _this$delegate4$compo, _this$delegate5, _this$delegate5$compo; + (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || (_this$delegate4$compo = _this$delegate4.compositionControllerWillSyncDocumentView) === null || _this$delegate4$compo === void 0 || _this$delegate4$compo.call(_this$delegate4); + this.documentView.sync(); + (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$compo = _this$delegate5.compositionControllerDidSyncDocumentView) === null || _this$delegate5$compo === void 0 || _this$delegate5$compo.call(_this$delegate5); + } + return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$compo = _this$delegate6.compositionControllerDidRender) === null || _this$delegate6$compo === void 0 ? void 0 : _this$delegate6$compo.call(_this$delegate6); } - reparse() { - var _this$delegate3; - return (_this$delegate3 = this.delegate) === null || _this$delegate3 === void 0 ? void 0 : _this$delegate3.reparse(); + rerenderViewForObject(object) { + this.invalidateViewForObject(object); + return this.render(); + } + invalidateViewForObject(object) { + return this.documentView.invalidateViewForObject(object); + } + isViewCachingEnabled() { + return this.documentView.isViewCachingEnabled(); + } + enableViewCaching() { + return this.documentView.enableViewCaching(); + } + disableViewCaching() { + return this.documentView.disableViewCaching(); + } + refreshViewCache() { + return this.documentView.garbageCollectCachedViews(); } - // Responder helpers + // Attachment editor management - insertString() { - var _this$delegate4; - let string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; - let options = arguments.length > 1 ? arguments[1] : undefined; - (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillPerformTyping(); - return this.withTargetDOMRange(function () { - var _this$responder; - return (_this$responder = this.responder) === null || _this$responder === void 0 ? void 0 : _this$responder.insertString(string, options); - }); + isEditingAttachment() { + return !!this.attachmentEditor; } - toggleAttributeIfSupported(attributeName) { - if (getAllAttributeNames().includes(attributeName)) { - var _this$delegate5; - (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || _this$delegate5.inputControllerWillPerformFormatting(attributeName); - return this.withTargetDOMRange(function () { - var _this$responder2; - return (_this$responder2 = this.responder) === null || _this$responder2 === void 0 ? void 0 : _this$responder2.toggleCurrentAttribute(attributeName); - }); - } + installAttachmentEditorForAttachment(attachment, options) { + var _this$attachmentEdito; + if (((_this$attachmentEdito = this.attachmentEditor) === null || _this$attachmentEdito === void 0 ? void 0 : _this$attachmentEdito.attachment) === attachment) return; + const element = this.documentView.findElementForObject(attachment); + if (!element) return; + this.uninstallAttachmentEditor(); + const attachmentPiece = this.composition.document.getAttachmentPieceForAttachment(attachment); + this.attachmentEditor = new AttachmentEditorController(attachmentPiece, element, this.element, options); + this.attachmentEditor.delegate = this; + } + uninstallAttachmentEditor() { + var _this$attachmentEdito2; + return (_this$attachmentEdito2 = this.attachmentEditor) === null || _this$attachmentEdito2 === void 0 ? void 0 : _this$attachmentEdito2.uninstall(); + } + + // Attachment controller delegate + + didUninstallAttachmentEditor() { + this.attachmentEditor = null; + return this.render(); + } + attachmentEditorDidRequestUpdatingAttributesForAttachment(attributes, attachment) { + var _this$delegate7, _this$delegate7$compo; + (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$compo = _this$delegate7.compositionControllerWillUpdateAttachment) === null || _this$delegate7$compo === void 0 || _this$delegate7$compo.call(_this$delegate7, attachment); + return this.composition.updateAttributesForAttachment(attributes, attachment); + } + attachmentEditorDidRequestRemovingAttributeForAttachment(attribute, attachment) { + var _this$delegate8, _this$delegate8$compo; + (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || (_this$delegate8$compo = _this$delegate8.compositionControllerWillUpdateAttachment) === null || _this$delegate8$compo === void 0 || _this$delegate8$compo.call(_this$delegate8, attachment); + return this.composition.removeAttributeForAttachment(attribute, attachment); + } + attachmentEditorDidRequestRemovalOfAttachment(attachment) { + var _this$delegate9, _this$delegate9$compo; + return (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || (_this$delegate9$compo = _this$delegate9.compositionControllerDidRequestRemovalOfAttachment) === null || _this$delegate9$compo === void 0 ? void 0 : _this$delegate9$compo.call(_this$delegate9, attachment); + } + attachmentEditorDidRequestDeselectingAttachment(attachment) { + var _this$delegate10, _this$delegate10$comp; + return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || (_this$delegate10$comp = _this$delegate10.compositionControllerDidRequestDeselectingAttachment) === null || _this$delegate10$comp === void 0 ? void 0 : _this$delegate10$comp.call(_this$delegate10, attachment); } - activateAttributeIfSupported(attributeName, value) { - if (getAllAttributeNames().includes(attributeName)) { - var _this$delegate6; - (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || _this$delegate6.inputControllerWillPerformFormatting(attributeName); - return this.withTargetDOMRange(function () { - var _this$responder3; - return (_this$responder3 = this.responder) === null || _this$responder3 === void 0 ? void 0 : _this$responder3.setCurrentAttribute(attributeName, value); - }); - } + + // Private + + canSyncDocumentView() { + return !this.isEditingAttachment(); } - deleteInDirection(direction) { - let { - recordUndoEntry - } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - recordUndoEntry: true - }; - if (recordUndoEntry) { - var _this$delegate7; - (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || _this$delegate7.inputControllerWillPerformTyping(); - } - const perform = () => { - var _this$responder4; - return (_this$responder4 = this.responder) === null || _this$responder4 === void 0 ? void 0 : _this$responder4.deleteInDirection(direction); - }; - const domRange = this.getTargetDOMRange({ - minLength: this.composing ? 1 : 2 - }); - if (domRange) { - return this.withTargetDOMRange(domRange, perform); - } else { - return perform(); - } + findAttachmentForElement(element) { + return this.composition.document.getAttachmentById(parseInt(element.dataset.trixId, 10)); } + } - // Selection helpers + class Controller extends BasicObject {} - withTargetDOMRange(domRange, fn) { - if (typeof domRange === "function") { - fn = domRange; - domRange = this.getTargetDOMRange(); - } - if (domRange) { - var _this$responder5; - return (_this$responder5 = this.responder) === null || _this$responder5 === void 0 ? void 0 : _this$responder5.withTargetDOMRange(domRange, fn.bind(this)); - } else { - selectionChangeObserver.reset(); - return fn.call(this); - } + var _$codePointAt, _; + const { + browser, + keyNames + } = config; + let pastedFileCount = 0; + class Level0InputController extends InputController { + constructor() { + super(...arguments); + this.resetInputSummary(); } - getTargetDOMRange() { - var _this$event$getTarget, _this$event; - let { - minLength - } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { - minLength: 0 - }; - const targetRanges = (_this$event$getTarget = (_this$event = this.event).getTargetRanges) === null || _this$event$getTarget === void 0 ? void 0 : _this$event$getTarget.call(_this$event); - if (targetRanges) { - if (targetRanges.length) { - const domRange = staticRangeToRange(targetRanges[0]); - if (minLength === 0 || domRange.toString().length >= minLength) { - return domRange; - } - } + setInputSummary() { + let summary = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + this.inputSummary.eventName = this.eventName; + for (const key in summary) { + const value = summary[key]; + this.inputSummary[key] = value; } + return this.inputSummary; } - withEvent(event, fn) { - let result; - this.event = event; - try { - result = fn.call(this); - } finally { - this.event = null; - } - return result; + resetInputSummary() { + this.inputSummary = {}; + } + reset() { + this.resetInputSummary(); + return selectionChangeObserver.reset(); } - } - _defineProperty(Level2InputController, "events", { - keydown(event) { - if (keyEventIsKeyboardCommand(event)) { - var _this$delegate8; - const command = keyboardCommandFromKeyEvent(event); - if ((_this$delegate8 = this.delegate) !== null && _this$delegate8 !== void 0 && _this$delegate8.inputControllerDidReceiveKeyboardCommand(command)) { - event.preventDefault(); - } - } else { - let name = event.key; - if (event.altKey) { - name += "+Alt"; - } - if (event.shiftKey) { - name += "+Shift"; - } - const handler = this.constructor.keys[name]; - if (handler) { - return this.withEvent(event, handler); - } - } - }, - // Handle paste event to work around beforeinput.insertFromPaste browser bugs. - // Safe to remove each condition once fixed upstream. - paste(event) { - var _event$clipboardData; - // https://bugs.webkit.org/show_bug.cgi?id=194921 - let paste; - const href = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData("URL"); - if (pasteEventHasFilesOnly(event)) { - event.preventDefault(); - return this.attachFiles(event.clipboardData.files); - // https://bugs.chromium.org/p/chromium/issues/detail?id=934448 - } else if (pasteEventHasPlainTextOnly(event)) { - var _this$delegate9, _this$responder6, _this$delegate10; - event.preventDefault(); - paste = { - type: "text/plain", - string: event.clipboardData.getData("text/plain") - }; - (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || _this$delegate9.inputControllerWillPaste(paste); - (_this$responder6 = this.responder) === null || _this$responder6 === void 0 || _this$responder6.insertString(paste.string); - this.render(); - return (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 ? void 0 : _this$delegate10.inputControllerDidPaste(paste); + // Mutation observer delegate - // https://bugs.webkit.org/show_bug.cgi?id=196702 - } else if (href) { - var _this$delegate11, _this$responder7, _this$delegate12; - event.preventDefault(); - paste = { - type: "text/html", - html: this.createLinkHTML(href) - }; - (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 || _this$delegate11.inputControllerWillPaste(paste); - (_this$responder7 = this.responder) === null || _this$responder7 === void 0 || _this$responder7.insertHTML(paste.html); - this.render(); - return (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 ? void 0 : _this$delegate12.inputControllerDidPaste(paste); - } - }, - beforeinput(event) { - const handler = this.constructor.inputTypes[event.inputType]; - const immmediateRender = shouldRenderInmmediatelyToDealWithIOSDictation(event); - if (handler) { - this.withEvent(event, handler); - if (!immmediateRender) { - this.scheduleRender(); - } - } - if (immmediateRender) { - this.render(); - } - }, - input(event) { - selectionChangeObserver.reset(); - }, - dragstart(event) { - var _this$responder8; - if ((_this$responder8 = this.responder) !== null && _this$responder8 !== void 0 && _this$responder8.selectionContainsAttachments()) { - var _this$responder9; - event.dataTransfer.setData("application/x-trix-dragging", true); - this.dragging = { - range: (_this$responder9 = this.responder) === null || _this$responder9 === void 0 ? void 0 : _this$responder9.getSelectedRange(), - point: pointFromEvent(event) - }; - } - }, - dragenter(event) { - if (dragEventHasFiles(event)) { - event.preventDefault(); - } - }, - dragover(event) { - if (this.dragging) { - event.preventDefault(); - const point = pointFromEvent(event); - if (!objectsAreEqual(point, this.dragging.point)) { - var _this$responder10; - this.dragging.point = point; - return (_this$responder10 = this.responder) === null || _this$responder10 === void 0 ? void 0 : _this$responder10.setLocationRangeFromPointRange(point); - } - } else if (dragEventHasFiles(event)) { - event.preventDefault(); - } - }, - drop(event) { - if (this.dragging) { - var _this$delegate13, _this$responder11; - event.preventDefault(); - (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || _this$delegate13.inputControllerWillMoveText(); - (_this$responder11 = this.responder) === null || _this$responder11 === void 0 || _this$responder11.moveTextFromRange(this.dragging.range); - this.dragging = null; - return this.scheduleRender(); - } else if (dragEventHasFiles(event)) { - var _this$responder12; - event.preventDefault(); - const point = pointFromEvent(event); - (_this$responder12 = this.responder) === null || _this$responder12 === void 0 || _this$responder12.setLocationRangeFromPointRange(point); - return this.attachFiles(event.dataTransfer.files); - } - }, - dragend() { - if (this.dragging) { - var _this$responder13; - (_this$responder13 = this.responder) === null || _this$responder13 === void 0 || _this$responder13.setSelectedRange(this.dragging.range); - this.dragging = null; - } - }, - compositionend(event) { - if (this.composing) { - this.composing = false; - if (!browser$1.recentAndroid) this.scheduleRender(); + elementDidMutate(mutationSummary) { + if (this.isComposing()) { + var _this$delegate, _this$delegate$inputC; + return (_this$delegate = this.delegate) === null || _this$delegate === void 0 || (_this$delegate$inputC = _this$delegate.inputControllerDidAllowUnhandledInput) === null || _this$delegate$inputC === void 0 ? void 0 : _this$delegate$inputC.call(_this$delegate); + } else { + return this.handleInput(function () { + if (this.mutationIsSignificant(mutationSummary)) { + if (this.mutationIsExpected(mutationSummary)) { + this.requestRender(); + } else { + this.requestReparse(); + } + } + return this.reset(); + }); } } - }); - _defineProperty(Level2InputController, "keys", { - ArrowLeft() { - var _this$responder14; - if ((_this$responder14 = this.responder) !== null && _this$responder14 !== void 0 && _this$responder14.shouldManageMovingCursorInDirection("backward")) { - var _this$responder15; - this.event.preventDefault(); - return (_this$responder15 = this.responder) === null || _this$responder15 === void 0 ? void 0 : _this$responder15.moveCursorInDirection("backward"); - } - }, - ArrowRight() { - var _this$responder16; - if ((_this$responder16 = this.responder) !== null && _this$responder16 !== void 0 && _this$responder16.shouldManageMovingCursorInDirection("forward")) { - var _this$responder17; - this.event.preventDefault(); - return (_this$responder17 = this.responder) === null || _this$responder17 === void 0 ? void 0 : _this$responder17.moveCursorInDirection("forward"); - } - }, - Backspace() { - var _this$responder18; - if ((_this$responder18 = this.responder) !== null && _this$responder18 !== void 0 && _this$responder18.shouldManageDeletingInDirection("backward")) { - var _this$delegate14, _this$responder19; - this.event.preventDefault(); - (_this$delegate14 = this.delegate) === null || _this$delegate14 === void 0 || _this$delegate14.inputControllerWillPerformTyping(); - (_this$responder19 = this.responder) === null || _this$responder19 === void 0 || _this$responder19.deleteInDirection("backward"); - return this.render(); + mutationIsExpected(_ref) { + let { + textAdded, + textDeleted + } = _ref; + if (this.inputSummary.preferDocument) { + return true; } - }, - Tab() { - var _this$responder20; - if ((_this$responder20 = this.responder) !== null && _this$responder20 !== void 0 && _this$responder20.canIncreaseNestingLevel()) { - var _this$responder21; - this.event.preventDefault(); - (_this$responder21 = this.responder) === null || _this$responder21 === void 0 || _this$responder21.increaseNestingLevel(); - return this.render(); + const mutationAdditionMatchesSummary = textAdded != null ? textAdded === this.inputSummary.textAdded : !this.inputSummary.textAdded; + const mutationDeletionMatchesSummary = textDeleted != null ? this.inputSummary.didDelete : !this.inputSummary.didDelete; + const unexpectedNewlineAddition = ["\n", " \n"].includes(textAdded) && !mutationAdditionMatchesSummary; + const unexpectedNewlineDeletion = textDeleted === "\n" && !mutationDeletionMatchesSummary; + const singleUnexpectedNewline = unexpectedNewlineAddition && !unexpectedNewlineDeletion || unexpectedNewlineDeletion && !unexpectedNewlineAddition; + if (singleUnexpectedNewline) { + const range = this.getSelectedRange(); + if (range) { + var _this$responder; + const offset = unexpectedNewlineAddition ? textAdded.replace(/\n$/, "").length || -1 : (textAdded === null || textAdded === void 0 ? void 0 : textAdded.length) || 1; + if ((_this$responder = this.responder) !== null && _this$responder !== void 0 && _this$responder.positionIsBlockBreak(range[1] + offset)) { + return true; + } + } } - }, - "Tab+Shift"() { - var _this$responder22; - if ((_this$responder22 = this.responder) !== null && _this$responder22 !== void 0 && _this$responder22.canDecreaseNestingLevel()) { - var _this$responder23; - this.event.preventDefault(); - (_this$responder23 = this.responder) === null || _this$responder23 === void 0 || _this$responder23.decreaseNestingLevel(); - return this.render(); + return mutationAdditionMatchesSummary && mutationDeletionMatchesSummary; + } + mutationIsSignificant(mutationSummary) { + var _this$compositionInpu; + const textChanged = Object.keys(mutationSummary).length > 0; + const composedEmptyString = ((_this$compositionInpu = this.compositionInput) === null || _this$compositionInpu === void 0 ? void 0 : _this$compositionInpu.getEndData()) === ""; + return textChanged || !composedEmptyString; + } + + // Private + + getCompositionInput() { + if (this.isComposing()) { + return this.compositionInput; + } else { + this.compositionInput = new CompositionInput(this); } } - }); - _defineProperty(Level2InputController, "inputTypes", { - deleteByComposition() { - return this.deleteInDirection("backward", { - recordUndoEntry: false + isComposing() { + return this.compositionInput && !this.compositionInput.isEnded(); + } + deleteInDirection(direction, event) { + var _this$responder2; + if (((_this$responder2 = this.responder) === null || _this$responder2 === void 0 ? void 0 : _this$responder2.deleteInDirection(direction)) === false) { + if (event) { + event.preventDefault(); + return this.requestRender(); + } + } else { + return this.setInputSummary({ + didDelete: true + }); + } + } + serializeSelectionToDataTransfer(dataTransfer) { + var _this$responder3; + if (!dataTransferIsWritable(dataTransfer)) return; + const document = (_this$responder3 = this.responder) === null || _this$responder3 === void 0 ? void 0 : _this$responder3.getSelectedDocument().toSerializableDocument(); + dataTransfer.setData("application/x-trix-document", JSON.stringify(document)); + dataTransfer.setData("text/html", DocumentView.render(document).innerHTML); + dataTransfer.setData("text/plain", document.toString().replace(/\n$/, "")); + return true; + } + canAcceptDataTransfer(dataTransfer) { + const types = {}; + Array.from((dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.types) || []).forEach(type => { + types[type] = true; }); - }, - deleteByCut() { - return this.deleteInDirection("backward"); - }, - deleteByDrag() { - this.event.preventDefault(); - return this.withTargetDOMRange(function () { - var _this$responder24; - this.deleteByDragRange = (_this$responder24 = this.responder) === null || _this$responder24 === void 0 ? void 0 : _this$responder24.getSelectedRange(); + return types.Files || types["application/x-trix-document"] || types["text/html"] || types["text/plain"]; + } + getPastedHTMLUsingHiddenElement(callback) { + const selectedRange = this.getSelectedRange(); + const style = { + position: "absolute", + left: "".concat(window.pageXOffset, "px"), + top: "".concat(window.pageYOffset, "px"), + opacity: 0 + }; + const element = makeElement({ + style, + tagName: "div", + editable: true }); - }, - deleteCompositionText() { - return this.deleteInDirection("backward", { - recordUndoEntry: false + document.body.appendChild(element); + element.focus(); + return requestAnimationFrame(() => { + const html = element.innerHTML; + removeNode(element); + this.setSelectedRange(selectedRange); + return callback(html); }); - }, - deleteContent() { - return this.deleteInDirection("backward"); - }, - deleteContentBackward() { - return this.deleteInDirection("backward"); - }, - deleteContentForward() { - return this.deleteInDirection("forward"); - }, - deleteEntireSoftLine() { - return this.deleteInDirection("forward"); - }, - deleteHardLineBackward() { - return this.deleteInDirection("backward"); - }, - deleteHardLineForward() { - return this.deleteInDirection("forward"); - }, - deleteSoftLineBackward() { - return this.deleteInDirection("backward"); - }, - deleteSoftLineForward() { - return this.deleteInDirection("forward"); - }, - deleteWordBackward() { - return this.deleteInDirection("backward"); - }, - deleteWordForward() { - return this.deleteInDirection("forward"); - }, - formatBackColor() { - return this.activateAttributeIfSupported("backgroundColor", this.event.data); - }, - formatBold() { - return this.toggleAttributeIfSupported("bold"); - }, - formatFontColor() { - return this.activateAttributeIfSupported("color", this.event.data); - }, - formatFontName() { - return this.activateAttributeIfSupported("font", this.event.data); - }, - formatIndent() { - var _this$responder25; - if ((_this$responder25 = this.responder) !== null && _this$responder25 !== void 0 && _this$responder25.canIncreaseNestingLevel()) { - return this.withTargetDOMRange(function () { - var _this$responder26; - return (_this$responder26 = this.responder) === null || _this$responder26 === void 0 ? void 0 : _this$responder26.increaseNestingLevel(); - }); + } + } + _defineProperty(Level0InputController, "events", { + keydown(event) { + if (!this.isComposing()) { + this.resetInputSummary(); } - }, - formatItalic() { - return this.toggleAttributeIfSupported("italic"); - }, - formatJustifyCenter() { - return this.toggleAttributeIfSupported("justifyCenter"); - }, - formatJustifyFull() { - return this.toggleAttributeIfSupported("justifyFull"); - }, - formatJustifyLeft() { - return this.toggleAttributeIfSupported("justifyLeft"); - }, - formatJustifyRight() { - return this.toggleAttributeIfSupported("justifyRight"); - }, - formatOutdent() { - var _this$responder27; - if ((_this$responder27 = this.responder) !== null && _this$responder27 !== void 0 && _this$responder27.canDecreaseNestingLevel()) { - return this.withTargetDOMRange(function () { - var _this$responder28; - return (_this$responder28 = this.responder) === null || _this$responder28 === void 0 ? void 0 : _this$responder28.decreaseNestingLevel(); + this.inputSummary.didInput = true; + const keyName = keyNames[event.keyCode]; + if (keyName) { + var _context2; + let context = this.keys; + ["ctrl", "alt", "shift", "meta"].forEach(modifier => { + if (event["".concat(modifier, "Key")]) { + var _context; + if (modifier === "ctrl") { + modifier = "control"; + } + context = (_context = context) === null || _context === void 0 ? void 0 : _context[modifier]; + } }); + if (((_context2 = context) === null || _context2 === void 0 ? void 0 : _context2[keyName]) != null) { + this.setInputSummary({ + keyName + }); + selectionChangeObserver.reset(); + context[keyName].call(this, event); + } } - }, - formatRemove() { - this.withTargetDOMRange(function () { - for (const attributeName in (_this$responder29 = this.responder) === null || _this$responder29 === void 0 ? void 0 : _this$responder29.getCurrentAttributes()) { - var _this$responder29, _this$responder30; - (_this$responder30 = this.responder) === null || _this$responder30 === void 0 || _this$responder30.removeCurrentAttribute(attributeName); + if (keyEventIsKeyboardCommand(event)) { + const character = String.fromCharCode(event.keyCode).toLowerCase(); + if (character) { + var _this$delegate3; + const keys = ["alt", "shift"].map(modifier => { + if (event["".concat(modifier, "Key")]) { + return modifier; + } + }).filter(key => key); + keys.push(character); + if ((_this$delegate3 = this.delegate) !== null && _this$delegate3 !== void 0 && _this$delegate3.inputControllerDidReceiveKeyboardCommand(keys)) { + event.preventDefault(); + } } - }); - }, - formatSetBlockTextDirection() { - return this.activateAttributeIfSupported("blockDir", this.event.data); - }, - formatSetInlineTextDirection() { - return this.activateAttributeIfSupported("textDir", this.event.data); + } }, - formatStrikeThrough() { - return this.toggleAttributeIfSupported("strike"); + keypress(event) { + if (this.inputSummary.eventName != null) return; + if (event.metaKey) return; + if (event.ctrlKey && !event.altKey) return; + const string = stringFromKeyEvent(event); + if (string) { + var _this$delegate4, _this$responder9; + (_this$delegate4 = this.delegate) === null || _this$delegate4 === void 0 || _this$delegate4.inputControllerWillPerformTyping(); + (_this$responder9 = this.responder) === null || _this$responder9 === void 0 || _this$responder9.insertString(string); + return this.setInputSummary({ + textAdded: string, + didDelete: this.selectionIsExpanded() + }); + } }, - formatSubscript() { - return this.toggleAttributeIfSupported("sub"); + textInput(event) { + // Handle autocapitalization + const { + data + } = event; + const { + textAdded + } = this.inputSummary; + if (textAdded && textAdded !== data && textAdded.toUpperCase() === data) { + var _this$responder10; + const range = this.getSelectedRange(); + this.setSelectedRange([range[0], range[1] + textAdded.length]); + (_this$responder10 = this.responder) === null || _this$responder10 === void 0 || _this$responder10.insertString(data); + this.setInputSummary({ + textAdded: data + }); + return this.setSelectedRange(range); + } }, - formatSuperscript() { - return this.toggleAttributeIfSupported("sup"); + dragenter(event) { + event.preventDefault(); }, - formatUnderline() { - return this.toggleAttributeIfSupported("underline"); + dragstart(event) { + var _this$delegate5, _this$delegate5$input; + this.serializeSelectionToDataTransfer(event.dataTransfer); + this.draggedRange = this.getSelectedRange(); + return (_this$delegate5 = this.delegate) === null || _this$delegate5 === void 0 || (_this$delegate5$input = _this$delegate5.inputControllerDidStartDrag) === null || _this$delegate5$input === void 0 ? void 0 : _this$delegate5$input.call(_this$delegate5); }, - historyRedo() { - var _this$delegate15; - return (_this$delegate15 = this.delegate) === null || _this$delegate15 === void 0 ? void 0 : _this$delegate15.inputControllerWillPerformRedo(); + dragover(event) { + if (this.draggedRange || this.canAcceptDataTransfer(event.dataTransfer)) { + event.preventDefault(); + const draggingPoint = { + x: event.clientX, + y: event.clientY + }; + if (!objectsAreEqual(draggingPoint, this.draggingPoint)) { + var _this$delegate6, _this$delegate6$input; + this.draggingPoint = draggingPoint; + return (_this$delegate6 = this.delegate) === null || _this$delegate6 === void 0 || (_this$delegate6$input = _this$delegate6.inputControllerDidReceiveDragOverPoint) === null || _this$delegate6$input === void 0 ? void 0 : _this$delegate6$input.call(_this$delegate6, this.draggingPoint); + } + } }, - historyUndo() { - var _this$delegate16; - return (_this$delegate16 = this.delegate) === null || _this$delegate16 === void 0 ? void 0 : _this$delegate16.inputControllerWillPerformUndo(); + dragend(event) { + var _this$delegate7, _this$delegate7$input; + (_this$delegate7 = this.delegate) === null || _this$delegate7 === void 0 || (_this$delegate7$input = _this$delegate7.inputControllerDidCancelDrag) === null || _this$delegate7$input === void 0 || _this$delegate7$input.call(_this$delegate7); + this.draggedRange = null; + this.draggingPoint = null; }, - insertCompositionText() { - this.composing = true; - return this.insertString(this.event.data); + drop(event) { + var _event$dataTransfer, _this$responder11; + event.preventDefault(); + const files = (_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.files; + const documentJSON = event.dataTransfer.getData("application/x-trix-document"); + const point = { + x: event.clientX, + y: event.clientY + }; + (_this$responder11 = this.responder) === null || _this$responder11 === void 0 || _this$responder11.setLocationRangeFromPointRange(point); + if (files !== null && files !== void 0 && files.length) { + this.attachFiles(files); + } else if (this.draggedRange) { + var _this$delegate8, _this$responder12; + (_this$delegate8 = this.delegate) === null || _this$delegate8 === void 0 || _this$delegate8.inputControllerWillMoveText(); + (_this$responder12 = this.responder) === null || _this$responder12 === void 0 || _this$responder12.moveTextFromRange(this.draggedRange); + this.draggedRange = null; + this.requestRender(); + } else if (documentJSON) { + var _this$responder13; + const document = Document.fromJSONString(documentJSON); + (_this$responder13 = this.responder) === null || _this$responder13 === void 0 || _this$responder13.insertDocument(document); + this.requestRender(); + } + this.draggedRange = null; + this.draggingPoint = null; }, - insertFromComposition() { - this.composing = false; - return this.insertString(this.event.data); + cut(event) { + var _this$responder14; + if ((_this$responder14 = this.responder) !== null && _this$responder14 !== void 0 && _this$responder14.selectionIsExpanded()) { + var _this$delegate9; + if (this.serializeSelectionToDataTransfer(event.clipboardData)) { + event.preventDefault(); + } + (_this$delegate9 = this.delegate) === null || _this$delegate9 === void 0 || _this$delegate9.inputControllerWillCutText(); + this.deleteInDirection("backward"); + if (event.defaultPrevented) { + return this.requestRender(); + } + } }, - insertFromDrop() { - const range = this.deleteByDragRange; - if (range) { - var _this$delegate17; - this.deleteByDragRange = null; - (_this$delegate17 = this.delegate) === null || _this$delegate17 === void 0 || _this$delegate17.inputControllerWillMoveText(); - return this.withTargetDOMRange(function () { - var _this$responder31; - return (_this$responder31 = this.responder) === null || _this$responder31 === void 0 ? void 0 : _this$responder31.moveTextFromRange(range); - }); + copy(event) { + var _this$responder15; + if ((_this$responder15 = this.responder) !== null && _this$responder15 !== void 0 && _this$responder15.selectionIsExpanded()) { + if (this.serializeSelectionToDataTransfer(event.clipboardData)) { + event.preventDefault(); + } } }, - insertFromPaste() { - const { - dataTransfer - } = this.event; + paste(event) { + const clipboard = event.clipboardData || event.testClipboardData; const paste = { - dataTransfer + clipboard }; - const href = dataTransfer.getData("URL"); - const html = dataTransfer.getData("text/html"); + if (!clipboard || pasteEventIsCrippledSafariHTMLPaste(event)) { + this.getPastedHTMLUsingHiddenElement(html => { + var _this$delegate10, _this$responder16, _this$delegate11; + paste.type = "text/html"; + paste.html = html; + (_this$delegate10 = this.delegate) === null || _this$delegate10 === void 0 || _this$delegate10.inputControllerWillPaste(paste); + (_this$responder16 = this.responder) === null || _this$responder16 === void 0 || _this$responder16.insertHTML(paste.html); + this.requestRender(); + return (_this$delegate11 = this.delegate) === null || _this$delegate11 === void 0 ? void 0 : _this$delegate11.inputControllerDidPaste(paste); + }); + return; + } + const href = clipboard.getData("URL"); + const html = clipboard.getData("text/html"); + const name = clipboard.getData("public.url-name"); if (href) { - var _this$delegate18; + var _this$delegate12, _this$responder17, _this$delegate13; let string; - this.event.preventDefault(); paste.type = "text/html"; - const name = dataTransfer.getData("public.url-name"); if (name) { string = squishBreakableWhitespace(name).trim(); } else { string = href; } paste.html = this.createLinkHTML(href, string); - (_this$delegate18 = this.delegate) === null || _this$delegate18 === void 0 || _this$delegate18.inputControllerWillPaste(paste); - this.withTargetDOMRange(function () { - var _this$responder32; - return (_this$responder32 = this.responder) === null || _this$responder32 === void 0 ? void 0 : _this$responder32.insertHTML(paste.html); + (_this$delegate12 = this.delegate) === null || _this$delegate12 === void 0 || _this$delegate12.inputControllerWillPaste(paste); + this.setInputSummary({ + textAdded: string, + didDelete: this.selectionIsExpanded() }); - this.afterRender = () => { - var _this$delegate19; - return (_this$delegate19 = this.delegate) === null || _this$delegate19 === void 0 ? void 0 : _this$delegate19.inputControllerDidPaste(paste); - }; - } else if (dataTransferIsPlainText(dataTransfer)) { - var _this$delegate20; + (_this$responder17 = this.responder) === null || _this$responder17 === void 0 || _this$responder17.insertHTML(paste.html); + this.requestRender(); + (_this$delegate13 = this.delegate) === null || _this$delegate13 === void 0 || _this$delegate13.inputControllerDidPaste(paste); + } else if (dataTransferIsPlainText(clipboard)) { + var _this$delegate14, _this$responder18, _this$delegate15; paste.type = "text/plain"; - paste.string = dataTransfer.getData("text/plain"); - (_this$delegate20 = this.delegate) === null || _this$delegate20 === void 0 || _this$delegate20.inputControllerWillPaste(paste); - this.withTargetDOMRange(function () { - var _this$responder33; - return (_this$responder33 = this.responder) === null || _this$responder33 === void 0 ? void 0 : _this$responder33.insertString(paste.string); - }); - this.afterRender = () => { - var _this$delegate21; - return (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 ? void 0 : _this$delegate21.inputControllerDidPaste(paste); - }; - } else if (processableFilePaste(this.event)) { - var _this$delegate22; - paste.type = "File"; - paste.file = dataTransfer.files[0]; - (_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPaste(paste); - this.withTargetDOMRange(function () { - var _this$responder34; - return (_this$responder34 = this.responder) === null || _this$responder34 === void 0 ? void 0 : _this$responder34.insertFile(paste.file); + paste.string = clipboard.getData("text/plain"); + (_this$delegate14 = this.delegate) === null || _this$delegate14 === void 0 || _this$delegate14.inputControllerWillPaste(paste); + this.setInputSummary({ + textAdded: paste.string, + didDelete: this.selectionIsExpanded() }); - this.afterRender = () => { - var _this$delegate23; - return (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 ? void 0 : _this$delegate23.inputControllerDidPaste(paste); - }; + (_this$responder18 = this.responder) === null || _this$responder18 === void 0 || _this$responder18.insertString(paste.string); + this.requestRender(); + (_this$delegate15 = this.delegate) === null || _this$delegate15 === void 0 || _this$delegate15.inputControllerDidPaste(paste); } else if (html) { - var _this$delegate24; - this.event.preventDefault(); + var _this$delegate16, _this$responder19, _this$delegate17; paste.type = "text/html"; paste.html = html; - (_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPaste(paste); - this.withTargetDOMRange(function () { - var _this$responder35; - return (_this$responder35 = this.responder) === null || _this$responder35 === void 0 ? void 0 : _this$responder35.insertHTML(paste.html); - }); - this.afterRender = () => { - var _this$delegate25; - return (_this$delegate25 = this.delegate) === null || _this$delegate25 === void 0 ? void 0 : _this$delegate25.inputControllerDidPaste(paste); - }; + (_this$delegate16 = this.delegate) === null || _this$delegate16 === void 0 || _this$delegate16.inputControllerWillPaste(paste); + (_this$responder19 = this.responder) === null || _this$responder19 === void 0 || _this$responder19.insertHTML(paste.html); + this.requestRender(); + (_this$delegate17 = this.delegate) === null || _this$delegate17 === void 0 || _this$delegate17.inputControllerDidPaste(paste); + } else if (Array.from(clipboard.types).includes("Files")) { + var _clipboard$items, _clipboard$items$getA; + const file = (_clipboard$items = clipboard.items) === null || _clipboard$items === void 0 || (_clipboard$items = _clipboard$items[0]) === null || _clipboard$items === void 0 || (_clipboard$items$getA = _clipboard$items.getAsFile) === null || _clipboard$items$getA === void 0 ? void 0 : _clipboard$items$getA.call(_clipboard$items); + if (file) { + var _this$delegate18, _this$responder20, _this$delegate19; + const extension = extensionForFile(file); + if (!file.name && extension) { + file.name = "pasted-file-".concat(++pastedFileCount, ".").concat(extension); + } + paste.type = "File"; + paste.file = file; + (_this$delegate18 = this.delegate) === null || _this$delegate18 === void 0 || _this$delegate18.inputControllerWillAttachFiles(); + (_this$responder20 = this.responder) === null || _this$responder20 === void 0 || _this$responder20.insertFile(paste.file); + this.requestRender(); + (_this$delegate19 = this.delegate) === null || _this$delegate19 === void 0 || _this$delegate19.inputControllerDidPaste(paste); + } } + event.preventDefault(); }, - insertFromYank() { - return this.insertString(this.event.data); + compositionstart(event) { + return this.getCompositionInput().start(event.data); }, - insertLineBreak() { - return this.insertString("\n"); + compositionupdate(event) { + return this.getCompositionInput().update(event.data); }, - insertLink() { - return this.activateAttributeIfSupported("href", this.event.data); + compositionend(event) { + return this.getCompositionInput().end(event.data); }, - insertOrderedList() { - return this.toggleAttributeIfSupported("number"); + beforeinput(event) { + this.inputSummary.didInput = true; + }, + input(event) { + this.inputSummary.didInput = true; + return event.stopPropagation(); + } + }); + _defineProperty(Level0InputController, "keys", { + backspace(event) { + var _this$delegate20; + (_this$delegate20 = this.delegate) === null || _this$delegate20 === void 0 || _this$delegate20.inputControllerWillPerformTyping(); + return this.deleteInDirection("backward", event); + }, + delete(event) { + var _this$delegate21; + (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 || _this$delegate21.inputControllerWillPerformTyping(); + return this.deleteInDirection("forward", event); + }, + return(event) { + var _this$delegate22, _this$responder21; + this.setInputSummary({ + preferDocument: true + }); + (_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPerformTyping(); + return (_this$responder21 = this.responder) === null || _this$responder21 === void 0 ? void 0 : _this$responder21.insertLineBreak(); + }, + tab(event) { + var _this$responder22; + if ((_this$responder22 = this.responder) !== null && _this$responder22 !== void 0 && _this$responder22.canIncreaseNestingLevel()) { + var _this$responder23; + (_this$responder23 = this.responder) === null || _this$responder23 === void 0 || _this$responder23.increaseNestingLevel(); + this.requestRender(); + event.preventDefault(); + } + }, + left(event) { + if (this.selectionIsInCursorTarget()) { + var _this$responder24; + event.preventDefault(); + return (_this$responder24 = this.responder) === null || _this$responder24 === void 0 ? void 0 : _this$responder24.moveCursorInDirection("backward"); + } }, - insertParagraph() { - var _this$delegate26; - (_this$delegate26 = this.delegate) === null || _this$delegate26 === void 0 || _this$delegate26.inputControllerWillPerformTyping(); - return this.withTargetDOMRange(function () { - var _this$responder36; - return (_this$responder36 = this.responder) === null || _this$responder36 === void 0 ? void 0 : _this$responder36.insertLineBreak(); - }); + right(event) { + if (this.selectionIsInCursorTarget()) { + var _this$responder25; + event.preventDefault(); + return (_this$responder25 = this.responder) === null || _this$responder25 === void 0 ? void 0 : _this$responder25.moveCursorInDirection("forward"); + } }, - insertReplacementText() { - const replacement = this.event.dataTransfer.getData("text/plain"); - const domRange = this.event.getTargetRanges()[0]; - this.withTargetDOMRange(domRange, () => { - this.insertString(replacement, { + control: { + d(event) { + var _this$delegate23; + (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 || _this$delegate23.inputControllerWillPerformTyping(); + return this.deleteInDirection("forward", event); + }, + h(event) { + var _this$delegate24; + (_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPerformTyping(); + return this.deleteInDirection("backward", event); + }, + o(event) { + var _this$delegate25, _this$responder26; + event.preventDefault(); + (_this$delegate25 = this.delegate) === null || _this$delegate25 === void 0 || _this$delegate25.inputControllerWillPerformTyping(); + (_this$responder26 = this.responder) === null || _this$responder26 === void 0 || _this$responder26.insertString("\n", { updatePosition: false }); - }); + return this.requestRender(); + } }, - insertText() { - var _this$event$dataTrans; - return this.insertString(this.event.data || ((_this$event$dataTrans = this.event.dataTransfer) === null || _this$event$dataTrans === void 0 ? void 0 : _this$event$dataTrans.getData("text/plain"))); + shift: { + return(event) { + var _this$delegate26, _this$responder27; + (_this$delegate26 = this.delegate) === null || _this$delegate26 === void 0 || _this$delegate26.inputControllerWillPerformTyping(); + (_this$responder27 = this.responder) === null || _this$responder27 === void 0 || _this$responder27.insertString("\n"); + this.requestRender(); + event.preventDefault(); + }, + tab(event) { + var _this$responder28; + if ((_this$responder28 = this.responder) !== null && _this$responder28 !== void 0 && _this$responder28.canDecreaseNestingLevel()) { + var _this$responder29; + (_this$responder29 = this.responder) === null || _this$responder29 === void 0 || _this$responder29.decreaseNestingLevel(); + this.requestRender(); + event.preventDefault(); + } + }, + left(event) { + if (this.selectionIsInCursorTarget()) { + event.preventDefault(); + return this.expandSelectionInDirection("backward"); + } + }, + right(event) { + if (this.selectionIsInCursorTarget()) { + event.preventDefault(); + return this.expandSelectionInDirection("forward"); + } + } }, - insertTranspose() { - return this.insertString(this.event.data); + alt: { + backspace(event) { + var _this$delegate27; + this.setInputSummary({ + preferDocument: false + }); + return (_this$delegate27 = this.delegate) === null || _this$delegate27 === void 0 ? void 0 : _this$delegate27.inputControllerWillPerformTyping(); + } }, - insertUnorderedList() { - return this.toggleAttributeIfSupported("bullet"); + meta: { + backspace(event) { + var _this$delegate28; + this.setInputSummary({ + preferDocument: false + }); + return (_this$delegate28 = this.delegate) === null || _this$delegate28 === void 0 ? void 0 : _this$delegate28.inputControllerWillPerformTyping(); + } } }); - const staticRangeToRange = function (staticRange) { - const range = document.createRange(); - range.setStart(staticRange.startContainer, staticRange.startOffset); - range.setEnd(staticRange.endContainer, staticRange.endOffset); - return range; - }; - - // Event helpers - - const dragEventHasFiles = event => { - var _event$dataTransfer; - return Array.from(((_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.types) || []).includes("Files"); - }; - const processableFilePaste = event => { - var _event$dataTransfer$f; - // Paste events that only have files are handled by the paste event handler, - // to work around Safari not supporting beforeinput.insertFromPaste for files. - - // MS Office text pastes include a file with a screenshot of the text, but we should - // handle them as text pastes. - return ((_event$dataTransfer$f = event.dataTransfer.files) === null || _event$dataTransfer$f === void 0 ? void 0 : _event$dataTransfer$f[0]) && !pasteEventHasFilesOnly(event) && !dataTransferIsMsOfficePaste(event); + Level0InputController.proxyMethod("responder?.getSelectedRange"); + Level0InputController.proxyMethod("responder?.setSelectedRange"); + Level0InputController.proxyMethod("responder?.expandSelectionInDirection"); + Level0InputController.proxyMethod("responder?.selectionIsInCursorTarget"); + Level0InputController.proxyMethod("responder?.selectionIsExpanded"); + const extensionForFile = file => { + var _file$type; + return (_file$type = file.type) === null || _file$type === void 0 || (_file$type = _file$type.match(/\/(\w+)$/)) === null || _file$type === void 0 ? void 0 : _file$type[1]; }; - const pasteEventHasFilesOnly = function (event) { - const clipboard = event.clipboardData; - if (clipboard) { - const fileTypes = Array.from(clipboard.types).filter(type => type.match(/file/i)); // "Files", "application/x-moz-file" - return fileTypes.length === clipboard.types.length && clipboard.files.length >= 1; + const hasStringCodePointAt = !!((_$codePointAt = (_ = " ").codePointAt) !== null && _$codePointAt !== void 0 && _$codePointAt.call(_, 0)); + const stringFromKeyEvent = function (event) { + if (event.key && hasStringCodePointAt && event.key.codePointAt(0) === event.keyCode) { + return event.key; + } else { + let code; + if (event.which === null) { + code = event.keyCode; + } else if (event.which !== 0 && event.charCode !== 0) { + code = event.charCode; + } + if (code != null && keyNames[code] !== "escape") { + return UTF16String.fromCodepoints([code]).toString(); + } } }; - const pasteEventHasPlainTextOnly = function (event) { - const clipboard = event.clipboardData; - if (clipboard) { - return clipboard.types.includes("text/plain") && clipboard.types.length === 1; + const pasteEventIsCrippledSafariHTMLPaste = function (event) { + const paste = event.clipboardData; + if (paste) { + if (paste.types.includes("text/html")) { + // Answer is yes if there's any possibility of Paste and Match Style in Safari, + // which is nearly impossible to detect confidently: https://bugs.webkit.org/show_bug.cgi?id=174165 + for (const type of paste.types) { + const hasPasteboardFlavor = /^CorePasteboardFlavorType/.test(type); + const hasReadableDynamicData = /^dyn\./.test(type) && paste.getData(type); + const mightBePasteAndMatchStyle = hasPasteboardFlavor || hasReadableDynamicData; + if (mightBePasteAndMatchStyle) { + return true; + } + } + return false; + } else { + const isExternalHTMLPaste = paste.types.includes("com.apple.webarchive"); + const isExternalRichTextPaste = paste.types.includes("com.apple.flat-rtfd"); + return isExternalHTMLPaste || isExternalRichTextPaste; + } } }; - const keyboardCommandFromKeyEvent = function (event) { - const command = []; - if (event.altKey) { - command.push("alt"); + class CompositionInput extends BasicObject { + constructor(inputController) { + super(...arguments); + this.inputController = inputController; + this.responder = this.inputController.responder; + this.delegate = this.inputController.delegate; + this.inputSummary = this.inputController.inputSummary; + this.data = {}; } - if (event.shiftKey) { - command.push("shift"); + start(data) { + this.data.start = data; + if (this.isSignificant()) { + var _this$responder5; + if (this.inputSummary.eventName === "keypress" && this.inputSummary.textAdded) { + var _this$responder4; + (_this$responder4 = this.responder) === null || _this$responder4 === void 0 || _this$responder4.deleteInDirection("left"); + } + if (!this.selectionIsExpanded()) { + this.insertPlaceholder(); + this.requestRender(); + } + this.range = (_this$responder5 = this.responder) === null || _this$responder5 === void 0 ? void 0 : _this$responder5.getSelectedRange(); + } } - command.push(event.key); - return command; - }; - const pointFromEvent = event => ({ - x: event.clientX, - y: event.clientY - }); + update(data) { + this.data.update = data; + if (this.isSignificant()) { + const range = this.selectPlaceholder(); + if (range) { + this.forgetPlaceholder(); + this.range = range; + } + } + } + end(data) { + this.data.end = data; + if (this.isSignificant()) { + this.forgetPlaceholder(); + if (this.canApplyToDocument()) { + var _this$delegate2, _this$responder6, _this$responder7, _this$responder8; + this.setInputSummary({ + preferDocument: true, + didInput: false + }); + (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 || _this$delegate2.inputControllerWillPerformTyping(); + (_this$responder6 = this.responder) === null || _this$responder6 === void 0 || _this$responder6.setSelectedRange(this.range); + (_this$responder7 = this.responder) === null || _this$responder7 === void 0 || _this$responder7.insertString(this.data.end); + return (_this$responder8 = this.responder) === null || _this$responder8 === void 0 ? void 0 : _this$responder8.setSelectedRange(this.range[0] + this.data.end.length); + } else if (this.data.start != null || this.data.update != null) { + this.requestReparse(); + return this.inputController.reset(); + } + } else { + return this.inputController.reset(); + } + } + getEndData() { + return this.data.end; + } + isEnded() { + return this.getEndData() != null; + } + isSignificant() { + if (browser.composesExistingText) { + return this.inputSummary.didInput; + } else { + return true; + } + } + + // Private + + canApplyToDocument() { + var _this$data$start, _this$data$end; + return ((_this$data$start = this.data.start) === null || _this$data$start === void 0 ? void 0 : _this$data$start.length) === 0 && ((_this$data$end = this.data.end) === null || _this$data$end === void 0 ? void 0 : _this$data$end.length) > 0 && this.range; + } + } + CompositionInput.proxyMethod("inputController.setInputSummary"); + CompositionInput.proxyMethod("inputController.requestRender"); + CompositionInput.proxyMethod("inputController.requestReparse"); + CompositionInput.proxyMethod("responder?.selectionIsExpanded"); + CompositionInput.proxyMethod("responder?.insertPlaceholder"); + CompositionInput.proxyMethod("responder?.selectPlaceholder"); + CompositionInput.proxyMethod("responder?.forgetPlaceholder"); const attributeButtonSelector = "[data-trix-attribute]"; const actionButtonSelector = "[data-trix-action]"; diff --git a/src/test/system/level_2_input_test.js b/src/test/system/level_2_input_test.js index aae390263..83716db22 100644 --- a/src/test/system/level_2_input_test.js +++ b/src/test/system/level_2_input_test.js @@ -181,6 +181,90 @@ testGroup("Level 2 Input", testOptions, () => { expectDocument("one\n") }) + // Smart Quotes replacement at cursor + test("Smart Quotes apostrophe replacement", async () => { + insertString("I'll") + await nextFrame() + + const inputType = "insertReplacementText" + const dataTransfer = createDataTransfer({ "text/plain": "'" }) + + const targetRange = document.createRange() + const textNode = getEditorElement().firstElementChild.lastChild + targetRange.setStart(textNode, 1) + targetRange.setEnd(textNode, 2) + + const event = createEvent("beforeinput", { inputType, dataTransfer, getTargetRanges: () => [ targetRange ] }) + document.activeElement.dispatchEvent(event) + + // Simulate typing the space that triggers Smart Quotes + insertString(" ") + + await nextFrame() + await nextFrame() + + expectDocument("I'll \n") + assert.locationRange({ index: 0, offset: 5 }) + }) + + // Smart Quotes after blank line (Safari bug scenario) + test("Smart Quotes apostrophe replacement after blank line", async () => { + insertString("First line") + insertString("\n") + insertString("\n") + insertString("I'll") + await nextFrame() + + const inputType = "insertReplacementText" + const dataTransfer = createDataTransfer({ "text/plain": "'" }) + + const element = getEditorElement() + const divs = element.querySelectorAll("div") + const lastDiv = divs[divs.length - 1] + const textNode = lastDiv.lastChild + + const targetRange = document.createRange() + targetRange.setStart(textNode, 1) + targetRange.setEnd(textNode, 2) + + const event = createEvent("beforeinput", { inputType, dataTransfer, getTargetRanges: () => [ targetRange ] }) + document.activeElement.dispatchEvent(event) + + // Simulate typing the space that triggers Smart Quotes + insertString(" ") + + await nextFrame() + await nextFrame() + + expectDocument("First line\n\nI'll \n") + // Cursor should be after the space + assert.locationRange({ index: 0, offset: 17 }) + }) + + // Autocorrect while typing ahead (cursor should stay in place) + test("autocorrect before cursor preserves cursor position", async () => { + insertString("teh quick") + getComposition().setSelectedRange([ 9, 9 ]) // After "quick" + await nextFrame() + + const inputType = "insertReplacementText" + const dataTransfer = createDataTransfer({ "text/plain": "the" }) + + const targetRange = document.createRange() + const textNode = getEditorElement().firstElementChild.lastChild + targetRange.setStart(textNode, 0) + targetRange.setEnd(textNode, 3) + + const event = createEvent("beforeinput", { inputType, dataTransfer, getTargetRanges: () => [ targetRange ] }) + document.activeElement.dispatchEvent(event) + + await nextFrame() + + expectDocument("the quick\n") + // Cursor should stay after "quick" (position 9) + assert.locationRange({ index: 0, offset: 9 }) + }) + // https://input-inspector.now.sh/profiles/yZlsrfG93QMzp2oyr0BE test("deleting the last character in a composed word on Android", async () => { insertString("c") diff --git a/src/trix/controllers/level_2_input_controller.js b/src/trix/controllers/level_2_input_controller.js index 121461d51..9ebdbd481 100644 --- a/src/trix/controllers/level_2_input_controller.js +++ b/src/trix/controllers/level_2_input_controller.js @@ -6,10 +6,34 @@ import { dataTransferIsMsOfficePaste, dataTransferIsPlainText, keyEventIsKeyboar import { selectionChangeObserver } from "trix/observers/selection_change_observer" +// Safari Smart Quotes bug workaround constants +// Maximum character distance from cursor to consider a replacement "at cursor" vs "before cursor" +const AT_CURSOR_DISTANCE_THRESHOLD = 2 + +// Number of input events to ignore after Smart Quotes replacement +// Safari fires multiple input events that would corrupt our cursor position +const INPUT_EVENTS_TO_IGNORE_COUNT = 3 + +// WeakMap to associate editor elements with their Smart Quotes workaround state. +// Using WeakMap instead of instance properties to avoid GC leaks from circular references +// between element → controller → element, especially if editors aren't properly cleaned up. +const editorStates = new WeakMap() + +// Export function for SelectionManager to check if it should skip syncing +export const shouldPreventSelectionSync = (element) => { + const state = editorStates.get(element) + return state?.preventSelectionSync || false +} + export default class Level2InputController extends InputController { constructor(...args) { super(...args) this.render = this.render.bind(this) + // Initialize state for this editor instance + editorStates.set(this.element, { + pendingInputEventsToIgnore: 0, + preventSelectionSync: false, + }) } static events = { @@ -89,6 +113,11 @@ export default class Level2InputController extends InputController { }, input(event) { + const state = editorStates.get(this.element) + if (state.pendingInputEventsToIgnore > 0) { + state.pendingInputEventsToIgnore-- + return + } selectionChangeObserver.reset() }, @@ -452,11 +481,37 @@ export default class Level2InputController extends InputController { insertReplacementText() { const replacement = this.event.dataTransfer.getData("text/plain") - const domRange = this.event.getTargetRanges()[0] + const domRange = this.event.getTargetRanges()?.[0] + if (!domRange) return + + const { isAtCursor, cursorPosition, targetStart, targetEnd } = this.isReplacementAtCursor(domRange) this.withTargetDOMRange(domRange, () => { this.insertString(replacement, { updatePosition: false }) }) + + // Safari has a bug where it positions the cursor incorrectly after Smart Quotes and similar + // text replacement operations. We need to manually calculate and set the correct cursor position, + // then prevent Safari from overwriting it. + if (isAtCursor) { + const oldLength = targetEnd - targetStart + const newLength = replacement.length + // +1 accounts for the triggering character (e.g., space that triggered Smart Quotes) + const newCursor = cursorPosition + (newLength - oldLength) + 1 + + this.responder?.setSelectedRange([ newCursor, newCursor ]) + + // Prevent selection syncing until Safari has finished its buggy cursor positioning + const state = editorStates.get(this.element) + state.preventSelectionSync = true + state.pendingInputEventsToIgnore = INPUT_EVENTS_TO_IGNORE_COUNT + + // Use requestAnimationFrame to restore cursor after Safari's events complete + requestAnimationFrame(() => { + this.responder?.setSelectedRange([ newCursor, newCursor ]) + state.preventSelectionSync = false + }) + } }, insertText() { @@ -540,6 +595,35 @@ export default class Level2InputController extends InputController { } } + // Replacement text helpers + + // Determines if replacement is at cursor (Smart Quotes) vs before cursor (autocorrect) + isReplacementAtCursor(domRange) { + const cursorPosition = this.responder?.getSelectedRange()?.[1] + const targetLocationRange = this.responder?.createLocationRangeFromDOMRange?.(domRange, { strict: false }) + const targetStart = targetLocationRange ? this.responder?.document?.positionFromLocation(targetLocationRange[0]) : null + const targetEnd = targetLocationRange ? this.responder?.document?.positionFromLocation(targetLocationRange[1]) : null + + if (cursorPosition == null || targetEnd == null) { + return { + isAtCursor: false, + cursorPosition: null, + targetStart: null, + targetEnd: null, + } + } + + const distanceFromCursor = Math.abs(cursorPosition - targetEnd) + const isAtCursor = distanceFromCursor <= AT_CURSOR_DISTANCE_THRESHOLD + + return { + isAtCursor, + cursorPosition, + targetStart, + targetEnd, + } + } + // Selection helpers withTargetDOMRange(domRange, fn) { diff --git a/src/trix/models/selection_manager.js b/src/trix/models/selection_manager.js index 0e0284d5f..0a53fe352 100644 --- a/src/trix/models/selection_manager.js +++ b/src/trix/models/selection_manager.js @@ -18,6 +18,8 @@ import { setDOMRange, } from "trix/core/helpers" +import { shouldPreventSelectionSync } from "trix/controllers/level_2_input_controller" + export default class SelectionManager extends BasicObject { constructor(element) { super(...arguments) @@ -143,6 +145,9 @@ export default class SelectionManager extends BasicObject { } selectionDidChange() { + // Skip selection sync if we're working around Safari Smart Quotes bug + if (shouldPreventSelectionSync?.(this.element)) return + if (!this.paused && !innerElementIsActive(this.element)) { return this.updateCurrentLocationRange() }