UNPKG

trix

Version:

A rich text editor for everyday writing

1,412 lines (1,369 loc) 148 kB
/* Trix 2.1.15 Copyright © 2025 37signals, LLC */ /* eslint-disable id-length, */ const arraysAreEqual = function () { let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; if (a.length !== b.length) { return false; } for (let index = 0; index < a.length; index++) { const value = a[index]; if (value !== b[index]) { return false; } } return true; }; const arrayStartsWith = function () { let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; return arraysAreEqual(a.slice(0, b.length), b); }; const spliceArray = function (array) { const result = array.slice(0); for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } result.splice(...args); return result; }; const summarizeArrayChange = function () { let oldArray = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; let newArray = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; const added = []; const removed = []; const existingValues = new Set(); oldArray.forEach(value => { existingValues.add(value); }); const currentValues = new Set(); newArray.forEach(value => { currentValues.add(value); if (!existingValues.has(value)) { added.push(value); } }); oldArray.forEach(value => { if (!currentValues.has(value)) { removed.push(value); } }); return { added, removed }; }; const attributes = { default: { tagName: "div", parse: false }, quote: { tagName: "blockquote", nestable: true }, heading1: { tagName: "h1", terminal: true, breakOnReturn: true, group: false }, code: { tagName: "pre", terminal: true, htmlAttributes: ["language"], text: { plaintext: true } }, bulletList: { tagName: "ul", parse: false }, bullet: { tagName: "li", listAttribute: "bulletList", group: false, nestable: true, test(element) { return tagName$1(element.parentNode) === attributes[this.listAttribute].tagName; } }, numberList: { tagName: "ol", parse: false }, number: { tagName: "li", listAttribute: "numberList", group: false, nestable: true, test(element) { return tagName$1(element.parentNode) === attributes[this.listAttribute].tagName; } }, attachmentGallery: { tagName: "div", exclusive: true, terminal: true, parse: false, group: false } }; const tagName$1 = element => { var _element$tagName; return element === null || element === void 0 || (_element$tagName = element.tagName) === null || _element$tagName === void 0 ? void 0 : _element$tagName.toLowerCase(); }; const ZERO_WIDTH_SPACE = "\uFEFF"; const NON_BREAKING_SPACE = "\u00A0"; const OBJECT_REPLACEMENT_CHARACTER = "\uFFFC"; const extend = function (properties) { for (const key in properties) { const value = properties[key]; this[key] = value; } return this; }; const attachmentSelector = "[data-trix-attachment]"; const attachments = { preview: { presentation: "gallery", caption: { name: true, size: true } }, file: { caption: { size: true } } }; const html = document.documentElement; const match = html.matches; const handleEvent = function (eventName) { let { onElement, matchingSelector, withCallback, inPhase, preventDefault, times } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const element = onElement ? onElement : html; const selector = matchingSelector; const useCapture = inPhase === "capturing"; const handler = function (event) { if (times != null && --times === 0) { handler.destroy(); } const target = findClosestElementFromNode(event.target, { matchingSelector: selector }); if (target != null) { withCallback === null || withCallback === void 0 || withCallback.call(target, event, target); if (preventDefault) { event.preventDefault(); } } }; handler.destroy = () => element.removeEventListener(eventName, handler, useCapture); element.addEventListener(eventName, handler, useCapture); return handler; }; const handleEventOnce = function (eventName) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; options.times = 1; return handleEvent(eventName, options); }; const triggerEvent = function (eventName) { let { onElement, bubbles, cancelable, attributes } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const element = onElement != null ? onElement : html; bubbles = bubbles !== false; cancelable = cancelable !== false; const event = document.createEvent("Events"); event.initEvent(eventName, bubbles, cancelable); if (attributes != null) { extend.call(event, attributes); } return element.dispatchEvent(event); }; const elementMatchesSelector = function (element, selector) { if ((element === null || element === void 0 ? void 0 : element.nodeType) === 1) { return match.call(element, selector); } }; const findClosestElementFromNode = function (node) { let { matchingSelector, untilNode } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; while (node && node.nodeType !== Node.ELEMENT_NODE) { node = node.parentNode; } if (node == null) { return; } if (matchingSelector != null) { if (node.closest && untilNode == null) { return node.closest(matchingSelector); } else { while (node && node !== untilNode) { if (elementMatchesSelector(node, matchingSelector)) { return node; } node = node.parentNode; } } } else { return node; } }; const findInnerElement = function (element) { while ((_element = element) !== null && _element !== void 0 && _element.firstElementChild) { var _element; element = element.firstElementChild; } return element; }; const innerElementIsActive = element => document.activeElement !== element && elementContainsNode(element, document.activeElement); const elementContainsNode = function (element, node) { if (!element || !node) { return; } while (node) { if (node === element) { return true; } node = node.parentNode; } }; const findNodeFromContainerAndOffset = function (container, offset) { if (!container) { return; } if (container.nodeType === Node.TEXT_NODE) { return container; } else if (offset === 0) { return container.firstChild != null ? container.firstChild : container; } else { return container.childNodes.item(offset - 1); } }; const findElementFromContainerAndOffset = function (container, offset) { const node = findNodeFromContainerAndOffset(container, offset); return findClosestElementFromNode(node); }; const findChildIndexOfNode = function (node) { var _node; if (!((_node = node) !== null && _node !== void 0 && _node.parentNode)) { return; } let childIndex = 0; node = node.previousSibling; while (node) { childIndex++; node = node.previousSibling; } return childIndex; }; const removeNode = node => { var _node$parentNode; return node === null || node === void 0 || (_node$parentNode = node.parentNode) === null || _node$parentNode === void 0 ? void 0 : _node$parentNode.removeChild(node); }; const walkTree = function (tree) { let { onlyNodesOfType, usingFilter, expandEntityReferences } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const whatToShow = (() => { switch (onlyNodesOfType) { case "element": return NodeFilter.SHOW_ELEMENT; case "text": return NodeFilter.SHOW_TEXT; case "comment": return NodeFilter.SHOW_COMMENT; default: return NodeFilter.SHOW_ALL; } })(); return document.createTreeWalker(tree, whatToShow, usingFilter != null ? usingFilter : null, expandEntityReferences === true); }; const tagName = element => { var _element$tagName; return element === null || element === void 0 || (_element$tagName = element.tagName) === null || _element$tagName === void 0 ? void 0 : _element$tagName.toLowerCase(); }; const makeElement = function (tag) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; let key, value; if (typeof tag === "object") { options = tag; tag = options.tagName; } else { options = { attributes: options }; } const element = document.createElement(tag); if (options.editable != null) { if (options.attributes == null) { options.attributes = {}; } options.attributes.contenteditable = options.editable; } if (options.attributes) { for (key in options.attributes) { value = options.attributes[key]; element.setAttribute(key, value); } } if (options.style) { for (key in options.style) { value = options.style[key]; element.style[key] = value; } } if (options.data) { for (key in options.data) { value = options.data[key]; element.dataset[key] = value; } } if (options.className) { options.className.split(" ").forEach(className => { element.classList.add(className); }); } if (options.textContent) { element.textContent = options.textContent; } if (options.childNodes) { [].concat(options.childNodes).forEach(childNode => { element.appendChild(childNode); }); } return element; }; let blockTagNames = undefined; const getBlockTagNames = function () { if (blockTagNames != null) { return blockTagNames; } blockTagNames = []; for (const key in attributes) { const attributes$1 = attributes[key]; if (attributes$1.tagName) { blockTagNames.push(attributes$1.tagName); } } return blockTagNames; }; const nodeIsBlockContainer = node => nodeIsBlockStartComment(node === null || node === void 0 ? void 0 : node.firstChild); const nodeProbablyIsBlockContainer = function (node) { return getBlockTagNames().includes(tagName(node)) && !getBlockTagNames().includes(tagName(node.firstChild)); }; const nodeIsBlockStart = function (node) { let { strict } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { strict: true }; if (strict) { return nodeIsBlockStartComment(node); } else { return nodeIsBlockStartComment(node) || !nodeIsBlockStartComment(node.firstChild) && nodeProbablyIsBlockContainer(node); } }; const nodeIsBlockStartComment = node => nodeIsCommentNode(node) && (node === null || node === void 0 ? void 0 : node.data) === "block"; const nodeIsCommentNode = node => (node === null || node === void 0 ? void 0 : node.nodeType) === Node.COMMENT_NODE; const nodeIsCursorTarget = function (node) { let { name } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!node) { return; } if (nodeIsTextNode(node)) { if (node.data === ZERO_WIDTH_SPACE) { if (name) { return node.parentNode.dataset.trixCursorTarget === name; } else { return true; } } } else { return nodeIsCursorTarget(node.firstChild); } }; const nodeIsAttachmentElement = node => elementMatchesSelector(node, attachmentSelector); const nodeIsEmptyTextNode = node => nodeIsTextNode(node) && (node === null || node === void 0 ? void 0 : node.data) === ""; const nodeIsTextNode = node => (node === null || node === void 0 ? void 0 : node.nodeType) === Node.TEXT_NODE; // https://github.com/mathiasbynens/unicode-2.1.8/blob/master/Bidi_Class/Right_To_Left/regex.js const RTL_PATTERN = /[\u05BE\u05C0\u05C3\u05D0-\u05EA\u05F0-\u05F4\u061B\u061F\u0621-\u063A\u0640-\u064A\u066D\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D5\u06E5\u06E6\u200F\u202B\u202E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE72\uFE74\uFE76-\uFEFC]/; const getDirection = function () { const input = makeElement("input", { dir: "auto", name: "x", dirName: "x.dir" }); const textArea = makeElement("textarea", { dir: "auto", name: "y", dirName: "y.dir" }); const form = makeElement("form"); form.appendChild(input); form.appendChild(textArea); const supportsDirName = function () { try { return new FormData(form).has(textArea.dirName); } catch (error) { return false; } }(); const supportsDirSelector = function () { try { return input.matches(":dir(ltr),:dir(rtl)"); } catch (error) { return false; } }(); if (supportsDirName) { return function (string) { textArea.value = string; return new FormData(form).get(textArea.dirName); }; } else if (supportsDirSelector) { return function (string) { input.value = string; if (input.matches(":dir(rtl)")) { return "rtl"; } else { return "ltr"; } }; } else { return function (string) { const char = string.trim().charAt(0); if (RTL_PATTERN.test(char)) { return "rtl"; } else { return "ltr"; } }; } }(); const androidVersionMatch = navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i); const androidVersion = androidVersionMatch && parseInt(androidVersionMatch[1]); var browser = { // Android emits composition events when moving the cursor through existing text // Introduced in Chrome 65: https://bugs.chromium.org/p/chromium/issues/detail?id=764439#c9 composesExistingText: /Android.*Chrome/.test(navigator.userAgent), // Android 13, especially on Samsung keyboards, emits extra compositionend and beforeinput events // that can make the input handler lose the current selection or enter an infinite input -> render -> input // loop. recentAndroid: androidVersion && androidVersion > 12, samsungAndroid: androidVersion && navigator.userAgent.match(/Android.*SM-/), // IE 11 activates resizing handles on editable elements that have "layout" forcesObjectResizing: /Trident.*rv:11/.test(navigator.userAgent), // https://www.w3.org/TR/input-events-1/ + https://www.w3.org/TR/input-events-2/ supportsInputEvents: typeof InputEvent !== "undefined" && ["data", "getTargetRanges", "inputType"].every(prop => prop in InputEvent.prototype) }; var css = { attachment: "attachment", attachmentCaption: "attachment__caption", attachmentCaptionEditor: "attachment__caption-editor", attachmentMetadata: "attachment__metadata", attachmentMetadataContainer: "attachment__metadata-container", attachmentName: "attachment__name", attachmentProgress: "attachment__progress", attachmentSize: "attachment__size", attachmentToolbar: "attachment__toolbar", attachmentGallery: "attachment-gallery" }; var dompurify = { ADD_ATTR: ["language"], SAFE_FOR_XML: false, RETURN_DOM: true }; var lang = { attachFiles: "Attach Files", bold: "Bold", bullets: "Bullets", byte: "Byte", bytes: "Bytes", captionPlaceholder: "Add a caption…", code: "Code", heading1: "Heading", indent: "Increase Level", italic: "Italic", link: "Link", numbers: "Numbers", outdent: "Decrease Level", quote: "Quote", redo: "Redo", remove: "Remove", strike: "Strikethrough", undo: "Undo", unlink: "Unlink", url: "URL", urlPlaceholder: "Enter a URL…", GB: "GB", KB: "KB", MB: "MB", PB: "PB", TB: "TB" }; /* eslint-disable no-case-declarations, */ const sizes = [lang.bytes, lang.KB, lang.MB, lang.GB, lang.TB, lang.PB]; var file_size_formatting = { prefix: "IEC", precision: 2, formatter(number) { switch (number) { case 0: return "0 ".concat(lang.bytes); case 1: return "1 ".concat(lang.byte); default: let base; if (this.prefix === "SI") { base = 1000; } else if (this.prefix === "IEC") { base = 1024; } const exp = Math.floor(Math.log(number) / Math.log(base)); const humanSize = number / Math.pow(base, exp); const string = humanSize.toFixed(this.precision); const withoutInsignificantZeros = string.replace(/0*$/, "").replace(/\.$/, ""); return "".concat(withoutInsignificantZeros, " ").concat(sizes[exp]); } } }; const input = { level2Enabled: true, getLevel() { if (this.level2Enabled && browser.supportsInputEvents) { return 2; } else { return 0; } }, pickFiles(callback) { const input = makeElement("input", { type: "file", multiple: true, hidden: true, id: this.fileInputId }); input.addEventListener("change", () => { callback(input.files); removeNode(input); }); removeNode(document.getElementById(this.fileInputId)); document.body.appendChild(input); input.click(); } }; var key_names = { 8: "backspace", 9: "tab", 13: "return", 27: "escape", 37: "left", 39: "right", 46: "delete", 68: "d", 72: "h", 79: "o" }; var parser = { removeBlankTableCells: false, tableCellSeparator: " | ", tableRowSeparator: "\n" }; var text_attributes = { bold: { tagName: "strong", inheritable: true, parser(element) { const style = window.getComputedStyle(element); return style.fontWeight === "bold" || style.fontWeight >= 600; } }, italic: { tagName: "em", inheritable: true, parser(element) { const style = window.getComputedStyle(element); return style.fontStyle === "italic"; } }, href: { groupTagName: "a", parser(element) { const matchingSelector = "a:not(".concat(attachmentSelector, ")"); const link = element.closest(matchingSelector); if (link) { return link.getAttribute("href"); } } }, strike: { tagName: "del", inheritable: true }, frozen: { style: { backgroundColor: "highlight" } } }; var toolbar = { getDefaultHTML() { return "<div class=\"trix-button-row\">\n <span class=\"trix-button-group trix-button-group--text-tools\" data-trix-button-group=\"text-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bold\" data-trix-attribute=\"bold\" data-trix-key=\"b\" title=\"".concat(lang.bold, "\" tabindex=\"-1\">").concat(lang.bold, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-italic\" data-trix-attribute=\"italic\" data-trix-key=\"i\" title=\"").concat(lang.italic, "\" tabindex=\"-1\">").concat(lang.italic, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-strike\" data-trix-attribute=\"strike\" title=\"").concat(lang.strike, "\" tabindex=\"-1\">").concat(lang.strike, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-link\" data-trix-attribute=\"href\" data-trix-action=\"link\" data-trix-key=\"k\" title=\"").concat(lang.link, "\" tabindex=\"-1\">").concat(lang.link, "</button>\n </span>\n\n <span class=\"trix-button-group trix-button-group--block-tools\" data-trix-button-group=\"block-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-heading-1\" data-trix-attribute=\"heading1\" title=\"").concat(lang.heading1, "\" tabindex=\"-1\">").concat(lang.heading1, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-quote\" data-trix-attribute=\"quote\" title=\"").concat(lang.quote, "\" tabindex=\"-1\">").concat(lang.quote, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-code\" data-trix-attribute=\"code\" title=\"").concat(lang.code, "\" tabindex=\"-1\">").concat(lang.code, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bullet-list\" data-trix-attribute=\"bullet\" title=\"").concat(lang.bullets, "\" tabindex=\"-1\">").concat(lang.bullets, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-number-list\" data-trix-attribute=\"number\" title=\"").concat(lang.numbers, "\" tabindex=\"-1\">").concat(lang.numbers, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-decrease-nesting-level\" data-trix-action=\"decreaseNestingLevel\" title=\"").concat(lang.outdent, "\" tabindex=\"-1\">").concat(lang.outdent, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-increase-nesting-level\" data-trix-action=\"increaseNestingLevel\" title=\"").concat(lang.indent, "\" tabindex=\"-1\">").concat(lang.indent, "</button>\n </span>\n\n <span class=\"trix-button-group trix-button-group--file-tools\" data-trix-button-group=\"file-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-attach\" data-trix-action=\"attachFiles\" title=\"").concat(lang.attachFiles, "\" tabindex=\"-1\">").concat(lang.attachFiles, "</button>\n </span>\n\n <span class=\"trix-button-group-spacer\"></span>\n\n <span class=\"trix-button-group trix-button-group--history-tools\" data-trix-button-group=\"history-tools\">\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-undo\" data-trix-action=\"undo\" data-trix-key=\"z\" title=\"").concat(lang.undo, "\" tabindex=\"-1\">").concat(lang.undo, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-redo\" data-trix-action=\"redo\" data-trix-key=\"shift+z\" title=\"").concat(lang.redo, "\" tabindex=\"-1\">").concat(lang.redo, "</button>\n </span>\n </div>\n\n <div class=\"trix-dialogs\" data-trix-dialogs>\n <div class=\"trix-dialog trix-dialog--link\" data-trix-dialog=\"href\" data-trix-dialog-attribute=\"href\">\n <div class=\"trix-dialog__link-fields\">\n <input type=\"url\" name=\"href\" class=\"trix-input trix-input--dialog\" placeholder=\"").concat(lang.urlPlaceholder, "\" aria-label=\"").concat(lang.url, "\" data-trix-validate-href required data-trix-input>\n <div class=\"trix-button-group\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"").concat(lang.link, "\" data-trix-method=\"setAttribute\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"").concat(lang.unlink, "\" data-trix-method=\"removeAttribute\">\n </div>\n </div>\n </div>\n </div>"); } }; const undo = { interval: 5000 }; let allAttributeNames = null; let blockAttributeNames = null; let textAttributeNames = null; let listAttributeNames = null; const getAllAttributeNames = () => { if (!allAttributeNames) { allAttributeNames = getTextAttributeNames().concat(getBlockAttributeNames()); } return allAttributeNames; }; const getBlockConfig = attributeName => attributes[attributeName]; const getBlockAttributeNames = () => { if (!blockAttributeNames) { blockAttributeNames = Object.keys(attributes); } return blockAttributeNames; }; const getTextConfig = attributeName => text_attributes[attributeName]; const getTextAttributeNames = () => { if (!textAttributeNames) { textAttributeNames = Object.keys(text_attributes); } return textAttributeNames; }; const getListAttributeNames = () => { if (!listAttributeNames) { listAttributeNames = []; for (const key in attributes) { const { listAttribute } = attributes[key]; if (listAttribute != null) { listAttributeNames.push(listAttribute); } } } return listAttributeNames; }; /* eslint-disable */ const installDefaultCSSForTagName = function (tagName, defaultCSS) { const styleElement = insertStyleElementForTagName(tagName); styleElement.textContent = defaultCSS.replace(/%t/g, tagName); }; const insertStyleElementForTagName = function (tagName) { const element = document.createElement("style"); element.setAttribute("type", "text/css"); element.setAttribute("data-tag-name", tagName.toLowerCase()); const nonce = getCSPNonce(); if (nonce) { element.setAttribute("nonce", nonce); } document.head.insertBefore(element, document.head.firstChild); return element; }; const getCSPNonce = function () { const element = getMetaElement("trix-csp-nonce") || getMetaElement("csp-nonce"); if (element) { const { nonce, content } = element; return nonce == "" ? content : nonce; } }; const getMetaElement = name => document.head.querySelector("meta[name=".concat(name, "]")); const testTransferData = { "application/x-trix-feature-detection": "test" }; const dataTransferIsPlainText = function (dataTransfer) { const text = dataTransfer.getData("text/plain"); const html = dataTransfer.getData("text/html"); if (text && html) { const { body } = new DOMParser().parseFromString(html, "text/html"); if (body.textContent === text) { return !body.querySelector("*"); } } else { return text === null || text === void 0 ? void 0 : text.length; } }; const dataTransferIsMsOfficePaste = _ref => { let { dataTransfer } = _ref; return dataTransfer.types.includes("Files") && dataTransfer.types.includes("text/html") && dataTransfer.getData("text/html").includes("urn:schemas-microsoft-com:office:office"); }; const dataTransferIsWritable = function (dataTransfer) { if (!(dataTransfer !== null && dataTransfer !== void 0 && dataTransfer.setData)) return false; for (const key in testTransferData) { const value = testTransferData[key]; try { dataTransfer.setData(key, value); if (!dataTransfer.getData(key) === value) return false; } catch (error) { return false; } } return true; }; const keyEventIsKeyboardCommand = function () { if (/Mac|^iP/.test(navigator.platform)) { return event => event.metaKey; } else { return event => event.ctrlKey; } }(); function shouldRenderInmmediatelyToDealWithIOSDictation(inputEvent) { if (/iPhone|iPad/.test(navigator.userAgent)) { // Handle garbled content and duplicated newlines when using dictation on iOS 18+. Upon dictation completion, iOS sends // the list of insertText / insertParagraph events in a quick sequence. If we don't render // the editor synchronously, the internal range fails to update and results in garbled content or duplicated newlines. // // This workaround is necessary because iOS doesn't send composing events as expected while dictating: // https://bugs.webkit.org/show_bug.cgi?id=261764 return !inputEvent.inputType || inputEvent.inputType === "insertParagraph"; } else { return false; } } const defer = fn => setTimeout(fn, 1); /* eslint-disable id-length, */ const copyObject = function () { let object = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const result = {}; for (const key in object) { const value = object[key]; result[key] = value; } return result; }; const objectsAreEqual = function () { let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (Object.keys(a).length !== Object.keys(b).length) { return false; } for (const key in a) { const value = a[key]; if (value !== b[key]) { return false; } } return true; }; const normalizeRange = function (range) { if (range == null) return; if (!Array.isArray(range)) { range = [range, range]; } return [copyValue(range[0]), copyValue(range[1] != null ? range[1] : range[0])]; }; const rangeIsCollapsed = function (range) { if (range == null) return; const [start, end] = normalizeRange(range); return rangeValuesAreEqual(start, end); }; const rangesAreEqual = function (leftRange, rightRange) { if (leftRange == null || rightRange == null) return; const [leftStart, leftEnd] = normalizeRange(leftRange); const [rightStart, rightEnd] = normalizeRange(rightRange); return rangeValuesAreEqual(leftStart, rightStart) && rangeValuesAreEqual(leftEnd, rightEnd); }; const copyValue = function (value) { if (typeof value === "number") { return value; } else { return copyObject(value); } }; const rangeValuesAreEqual = function (left, right) { if (typeof left === "number") { return left === right; } else { return objectsAreEqual(left, right); } }; class BasicObject { static proxyMethod(expression) { const { name, toMethod, toProperty, optional } = parseProxyMethodExpression(expression); this.prototype[name] = function () { let subject; let object; if (toMethod) { if (optional) { var _this$toMethod; object = (_this$toMethod = this[toMethod]) === null || _this$toMethod === void 0 ? void 0 : _this$toMethod.call(this); } else { object = this[toMethod](); } } else if (toProperty) { object = this[toProperty]; } if (optional) { var _object; subject = (_object = object) === null || _object === void 0 ? void 0 : _object[name]; if (subject) { return apply.call(subject, object, arguments); } } else { subject = object[name]; return apply.call(subject, object, arguments); } }; } } const parseProxyMethodExpression = function (expression) { const match = expression.match(proxyMethodExpressionPattern); if (!match) { throw new Error("can't parse @proxyMethod expression: ".concat(expression)); } const args = { name: match[4] }; if (match[2] != null) { args.toMethod = match[1]; } else { args.toProperty = match[1]; } if (match[3] != null) { args.optional = true; } return args; }; const { apply } = Function.prototype; const proxyMethodExpressionPattern = new RegExp("\ ^\ (.+?)\ (\\(\\))?\ (\\?)?\ \\.\ (.+?)\ $\ "); class SelectionChangeObserver extends BasicObject { constructor() { super(...arguments); this.update = this.update.bind(this); this.selectionManagers = []; } start() { if (!this.started) { this.started = true; document.addEventListener("selectionchange", this.update, true); } } stop() { if (this.started) { this.started = false; return document.removeEventListener("selectionchange", this.update, true); } } registerSelectionManager(selectionManager) { if (!this.selectionManagers.includes(selectionManager)) { this.selectionManagers.push(selectionManager); return this.start(); } } unregisterSelectionManager(selectionManager) { this.selectionManagers = this.selectionManagers.filter(sm => sm !== selectionManager); if (this.selectionManagers.length === 0) { return this.stop(); } } notifySelectionManagersOfSelectionChange() { return this.selectionManagers.map(selectionManager => selectionManager.selectionDidChange()); } update() { this.notifySelectionManagersOfSelectionChange(); } reset() { this.update(); } } const selectionChangeObserver = new SelectionChangeObserver(); const getDOMSelection = function () { const selection = window.getSelection(); if (selection.rangeCount > 0) { return selection; } }; const getDOMRange = function () { var _getDOMSelection; const domRange = (_getDOMSelection = getDOMSelection()) === null || _getDOMSelection === void 0 ? void 0 : _getDOMSelection.getRangeAt(0); if (domRange) { if (!domRangeIsPrivate(domRange)) { return domRange; } } }; const setDOMRange = function (domRange) { const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(domRange); return selectionChangeObserver.update(); }; // In Firefox, clicking certain <input> elements changes the selection to a // private element used to draw its UI. Attempting to access properties of those // elements throws an error. // https://bugzilla.mozilla.org/show_bug.cgi?id=208427 const domRangeIsPrivate = domRange => nodeIsPrivate(domRange.startContainer) || nodeIsPrivate(domRange.endContainer); const nodeIsPrivate = node => !Object.getPrototypeOf(node); var _Array$from, _$codePointAt, _, _String$fromCodePoint; class UTF16String extends BasicObject { static box() { let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""; if (value instanceof this) { return value; } else { return this.fromUCS2String(value === null || value === void 0 ? void 0 : value.toString()); } } static fromUCS2String(ucs2String) { return new this(ucs2String, ucs2decode(ucs2String)); } static fromCodepoints(codepoints) { return new this(ucs2encode(codepoints), codepoints); } constructor(ucs2String, codepoints) { super(...arguments); this.ucs2String = ucs2String; this.codepoints = codepoints; this.length = this.codepoints.length; this.ucs2Length = this.ucs2String.length; } offsetToUCS2Offset(offset) { return ucs2encode(this.codepoints.slice(0, Math.max(0, offset))).length; } offsetFromUCS2Offset(ucs2Offset) { return ucs2decode(this.ucs2String.slice(0, Math.max(0, ucs2Offset))).length; } slice() { return this.constructor.fromCodepoints(this.codepoints.slice(...arguments)); } charAt(offset) { return this.slice(offset, offset + 1); } isEqualTo(value) { return this.constructor.box(value).ucs2String === this.ucs2String; } toJSON() { return this.ucs2String; } getCacheKey() { return this.ucs2String; } toString() { return this.ucs2String; } } const hasArrayFrom = ((_Array$from = Array.from) === null || _Array$from === void 0 ? void 0 : _Array$from.call(Array, "\ud83d\udc7c").length) === 1; const hasStringCodePointAt = ((_$codePointAt = (_ = " ").codePointAt) === null || _$codePointAt === void 0 ? void 0 : _$codePointAt.call(_, 0)) != null; const hasStringFromCodePoint = ((_String$fromCodePoint = String.fromCodePoint) === null || _String$fromCodePoint === void 0 ? void 0 : _String$fromCodePoint.call(String, 32, 128124)) === " \ud83d\udc7c"; // UCS-2 conversion helpers ported from Mathias Bynens' Punycode.js: // https://github.com/bestiejs/punycode.js#punycodeucs2 let ucs2decode, ucs2encode; // Creates an array containing the numeric code points of each Unicode // character in the string. While JavaScript uses UCS-2 internally, // this function will convert a pair of surrogate halves (each of which // UCS-2 exposes as separate characters) into a single code point, // matching UTF-16. if (hasArrayFrom && hasStringCodePointAt) { ucs2decode = string => Array.from(string).map(char => char.codePointAt(0)); } else { ucs2decode = function (string) { const output = []; let counter = 0; const { length } = string; while (counter < length) { let value = string.charCodeAt(counter++); if (0xd800 <= value && value <= 0xdbff && counter < length) { // high surrogate, and there is a next character const extra = string.charCodeAt(counter++); if ((extra & 0xfc00) === 0xdc00) { // low surrogate value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; } else { // unmatched surrogate; only append this code unit, in case the // next code unit is the high surrogate of a surrogate pair counter--; } } output.push(value); } return output; }; } // Creates a string based on an array of numeric code points. if (hasStringFromCodePoint) { ucs2encode = array => String.fromCodePoint(...Array.from(array || [])); } else { ucs2encode = function (array) { const characters = (() => { const result = []; Array.from(array).forEach(value => { let output = ""; if (value > 0xffff) { value -= 0x10000; output += String.fromCharCode(value >>> 10 & 0x3ff | 0xd800); value = 0xdc00 | value & 0x3ff; } result.push(output + String.fromCharCode(value)); }); return result; })(); return characters.join(""); }; } /* eslint-disable id-length, no-useless-escape, */ const normalizeSpaces = string => string.replace(new RegExp("".concat(ZERO_WIDTH_SPACE), "g"), "").replace(new RegExp("".concat(NON_BREAKING_SPACE), "g"), " "); const normalizeNewlines = string => string.replace(/\r\n?/g, "\n"); const breakableWhitespacePattern = new RegExp("[^\\S".concat(NON_BREAKING_SPACE, "]")); const squishBreakableWhitespace = string => string // Replace all breakable whitespace characters with a space .replace(new RegExp("".concat(breakableWhitespacePattern.source), "g"), " ") // Replace two or more spaces with a single space .replace(/\ {2,}/g, " "); const summarizeStringChange = function (oldString, newString) { let added, removed; oldString = UTF16String.box(oldString); newString = UTF16String.box(newString); if (newString.length < oldString.length) { [removed, added] = utf16StringDifferences(oldString, newString); } else { [added, removed] = utf16StringDifferences(newString, oldString); } return { added, removed }; }; const utf16StringDifferences = function (a, b) { if (a.isEqualTo(b)) { return ["", ""]; } const diffA = utf16StringDifference(a, b); const { length } = diffA.utf16String; let diffB; if (length) { const { offset } = diffA; const codepoints = a.codepoints.slice(0, offset).concat(a.codepoints.slice(offset + length)); diffB = utf16StringDifference(b, UTF16String.fromCodepoints(codepoints)); } else { diffB = utf16StringDifference(b, a); } return [diffA.utf16String.toString(), diffB.utf16String.toString()]; }; const utf16StringDifference = function (a, b) { let leftIndex = 0; let rightIndexA = a.length; let rightIndexB = b.length; while (leftIndex < rightIndexA && a.charAt(leftIndex).isEqualTo(b.charAt(leftIndex))) { leftIndex++; } while (rightIndexA > leftIndex + 1 && a.charAt(rightIndexA - 1).isEqualTo(b.charAt(rightIndexB - 1))) { rightIndexA--; rightIndexB--; } return { utf16String: a.slice(leftIndex, rightIndexA), offset: leftIndex }; }; /* eslint-disable id-length, */ installDefaultCSSForTagName("trix-inspector", "%t {\n display: block;\n}\n\n%t {\n position: fixed;\n background: #fff;\n border: 1px solid #444;\n border-radius: 5px;\n padding: 10px;\n font-family: sans-serif;\n font-size: 12px;\n overflow: auto;\n word-wrap: break-word;\n}\n\n%t details {\n margin-bottom: 10px;\n}\n\n%t summary:focus {\n outline: none;\n}\n\n%t details .panel {\n padding: 10px;\n}\n\n%t .performance .metrics {\n margin: 0 0 5px 5px;\n}\n\n%t .selection .characters {\n margin-top: 10px;\n}\n\n%t .selection .character {\n display: inline-block;\n font-size: 8px;\n font-family: courier, monospace;\n line-height: 10px;\n vertical-align: middle;\n text-align: center;\n width: 10px;\n height: 10px;\n margin: 0 1px 1px 0;\n border: 1px solid #333;\n border-radius: 1px;\n background: #676666;\n color: #fff;\n}\n\n%t .selection .character.selected {\n background: yellow;\n color: #000;\n}"); class TrixInspector extends HTMLElement { connectedCallback() { this.editorElement = document.querySelector("trix-editor[trix-id='".concat(this.dataset.trixId, "']")); this.views = this.createViews(); this.views.forEach(view => { view.render(); this.appendChild(view.element); }); this.reposition(); this.resizeHandler = this.reposition.bind(this); addEventListener("resize", this.resizeHandler); } disconnectedCallback() { removeEventListener("resize", this.resizeHandler); } createViews() { const views = Trix.Inspector.views.map(View => new View(this.editorElement)); return views.sort((a, b) => a.title.toLowerCase() > b.title.toLowerCase()); } reposition() { const { top, right } = this.editorElement.getBoundingClientRect(); this.style.top = "".concat(top, "px"); this.style.left = "".concat(right + 10, "px"); this.style.maxWidth = "".concat(window.innerWidth - right - 40, "px"); this.style.maxHeight = "".concat(window.innerHeight - top - 30, "px"); } } window.customElements.define("trix-inspector", TrixInspector); window.Trix.Inspector = { views: [], registerView(constructor) { return this.views.push(constructor); }, install(editorElement) { this.editorElement = editorElement; const element = document.createElement("trix-inspector"); element.dataset.trixId = this.editorElement.trixId; return document.body.appendChild(element); } }; if (!window.JST) window.JST = {}; window.JST["trix/inspector/templates/debug"] = function () { return "<p>\n <label>\n <input type=\"checkbox\" name=\"viewCaching\" checked=\"".concat(this.compositionController.isViewCachingEnabled(), "\">\n Cache views between renders\n </label>\n</p>\n\n<p>\n <button data-action=\"render\">Force Render</button> <button data-action=\"parse\">Parse current HTML</button>\n</p>\n\n<p>\n <label>\n <input type=\"checkbox\" name=\"controlElement\">\n Show <code>contenteditable</code> control element\n </label>\n</p>"); }; if (!window.JST) window.JST = {}; window.JST["trix/inspector/templates/document"] = function () { const details = this.document.getBlocks().map((block, index) => { const { text } = block; const pieces = text.pieceList.toArray(); return "<details class=\"block\">\n <summary class=\"title\">\n Block ".concat(block.id, ", Index: ").concat(index, "\n </summary>\n <div class=\"attributes\">\n Attributes: ").concat(JSON.stringify(block.attributes), "\n </div>\n\n <div class=\"htmlAttributes\">\n HTML Attributes: ").concat(JSON.stringify(block.htmlAttributes), "\n </div>\n\n <div class=\"text\">\n <div class=\"title\">\n Text: ").concat(text.id, ", Pieces: ").concat(pieces.length, ", Length: ").concat(text.getLength(), "\n </div>\n <div class=\"pieces\">\n ").concat(piecePartials(pieces).join("\n"), "\n </div>\n </div>\n </details>"); }); return details.join("\n"); }; const piecePartials = pieces => pieces.map((piece, index) => "<div class=\"piece\">\n <div class=\"title\">\n Piece ".concat(piece.id, ", Index: ").concat(index, "\n </div>\n <div class=\"attributes\">\n Attributes: ").concat(JSON.stringify(piece.attributes), "\n </div>\n <div class=\"content\">\n ").concat(JSON.stringify(piece.toString()), "\n </div>\n </div>")); if (!window.JST) window.JST = {}; window.JST["trix/inspector/templates/performance"] = function () { return Object.keys(this.data).map(name => { const data = this.data[name]; return dataMetrics(name, data, this.round); }).join("\n"); }; const dataMetrics = function (name, data, round) { let item = "<strong>".concat(name, "</strong> (").concat(data.calls, ")<br>"); if (data.calls > 0) { item += "<div class=\"metrics\">\n Mean: ".concat(round(data.mean), "ms<br>\n Max: ").concat(round(data.max), "ms<br>\n Last: ").concat(round(data.last), "ms\n </div>"); return item; } }; if (!window.JST) window.JST = {}; window.JST["trix/inspector/templates/render"] = () => "Syncs: ".concat(window.syncCount); if (!window.JST) window.JST = {}; window.JST["trix/inspector/templates/selection"] = function () { return "Location range: [".concat(this.locationRange[0].index, ":").concat(this.locationRange[0].offset, ", ").concat(this.locationRange[1].index, ":").concat(this.locationRange[1].offset, "]\n ").concat(charSpans(this.characters).join("\n")); }; const charSpans = characters => Array.from(characters).map(char => "<span class=\"character ".concat(char.selected ? "selected" : undefined, "\">").concat(char.string, "</span>")); if (!window.JST) window.JST = {}; window.JST["trix/inspector/templates/undo"] = () => "<h4>Undo stack</h4>\n <ol class=\"undo-entries\">\n ".concat(entryList(window.undoEntries), "\n </ol>\n <h4>Redo stack</h4>\n <ol class=\"redo-entries\">\n ").concat(entryList(window.redoEntries), "\n </ol>"); const entryList = entries => entries.map(entry => "<li>".concat(entry.description, " ").concat(JSON.stringify({ selectedRange: entry.snapshot.selectedRange, context: entry.context }), "</li>")); const KEY_EVENTS = "keydown keypress input".split(" "); const COMPOSITION_EVENTS = "compositionstart compositionupdate compositionend textInput".split(" "); const OBSERVER_OPTIONS = { attributes: true, childList: true, characterData: true, characterDataOldValue: true, subtree: true }; class ControlElement { constructor(editorElement) { this.didMutate = this.didMutate.bind(this); this.editorElement = editorElement; this.install(); } install() { this.createElement(); this.logInputEvents(); this.logMutations(); } uninstall() { this.observer.disconnect(); this.element.parentNode.removeChild(this.element); } createElement() { this.element = document.createElement("div"); this.element.setAttribute("contenteditable", ""); this.element.style.width = getComputedStyle(this.editorElement).width; this.element.style.minHeight = "50px"; this.element.style.border = "1px solid green"; this.editorElement.parentNode.insertBefore(this.element, this.editorElement.nextSibling); } logInputEvents() { KEY_EVENTS.forEach(eventName => { this.element.addEventListener(eventName, event => console.log("".concat(event.type, ": keyCode = ").concat(event.keyCode))); }); COMPOSITION_EVENTS.forEach(eventName => { this.element.addEventListener(eventName, event => console.log("".concat(event.type, ": data = ").concat(JSON.stringify(event.data)))); }); } logMutations() { this.observer = new window.MutationObserver(this.didMutate); this.observer.observe(this.element, OBSERVER_OPTIONS); } didMutate(mutations) { console.log("Mutations (".concat(mutations.length, "):")); for (let index = 0; index < mutations.length; index++) { const mutation = mutations[index]; console.log(" ".concat(index + 1, ". ").concat(mutation.type, ":")); switch (mutation.type) { case "characterData": console.log(" oldValue = ".concat(JSON.stringify(mutation.oldValue), ", newValue = ").concat(JSON.stringify(mutation.target.data))); break; case "childList": Array.from(mutation.addedNodes).forEach(node => { console.log(" node added ".concat(inspectNode(node))); }); Array.from(mutation.removedNodes).forEach(node => { console.log(" node removed ".concat(inspectNode(node))); }); } } } } const inspectNode = function (node) { if (node.data) { return JSON.stringify(node.data); } else { return JSON.stringify(node.outerHTML); } }; function _AsyncGenerator(e) { var r, t; function resume(r, t) { try { var n = e[r](t), o = n.value, u = o instanceof _OverloadYield; Promise.resolve(u ? o.v : o).then(function (t) { if (u) { var i = "return" === r ? "return" : "next"; if (!o.k || t.done) return resume(i, t); t = e[i](t).value; } settle(n.done ? "return" : "normal", t); }, function (e) { resume("throw", e); }); } catch (e) { settle("throw", e); } } function settle(e, n) { switch (e) { case "return": r.resolve({ value: n, done: !0 }); break; case "throw": r.reject(n); break; default: r.resolve({ value: n, done: !1 }); } (r = r.next) ? resume(r.key, r.arg) : t = null; } this._invoke = function (e, n) { return new Promise(function (o, u) { var i = { key: e, arg: n, resolve: o, reject: u, next: null }; t ? t = t.next = i : (r = t = i, resume(e, n)); }); }, "function" != typeof e.return && (this.return = void 0); } _AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function () { return this; }, _AsyncGenerator.prototype.next = function (e) { return this._invoke("next", e); }, _AsyncGenerator.prototype.throw = function (e) { return this._invoke("throw", e); }, _AsyncGenerator.prototype.return = function (e) { return this._invoke("return", e); }; function _OverloadYield(t, e) { this.v = t, this.k = e; } function old_createMetadataMethodsForProperty(e, t, a, r) { return { getMetadata: function (o) { old_assertNotFinished(r, "getMetadata"), old_assertMetadataKey(o); var i = e[o]; if (void 0 !== i) if (1 === t) { var n = i.public; if (void 0 !== n) return n[a]; } else if (2 === t) { var l = i.private; if (void 0 !== l) return l.get(a); } else if (Object.hasOwnProperty.call(i, "constructor")) return i.constructor; }, setMetadata: function (o, i) { old_assertNotFinished(r, "setMetadata"), old_assertMetadataKey(o); var n = e[o]; if (void 0 === n && (n = e[o] = {}), 1 === t) {