UNPKG

trix

Version:

A rich text editor for everyday writing

1,474 lines (1,436 loc) 515 kB
/* Trix 2.1.15 Copyright © 2025 37signals, LLC */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Trix = factory()); })(this, (function () { 'use strict'; var name = "trix"; var version = "2.1.15"; var description = "A rich text editor for everyday writing"; var main = "dist/trix.umd.min.js"; var module = "dist/trix.esm.min.js"; var style = "dist/trix.css"; var files = [ "dist/*.css", "dist/*.js", "dist/*.map", "src/{inspector,trix}/*.js" ]; var repository = { type: "git", url: "git+https://github.com/basecamp/trix.git" }; var keywords = [ "rich text", "wysiwyg", "editor" ]; var author = "37signals, LLC"; var license = "MIT"; var bugs = { url: "https://github.com/basecamp/trix/issues" }; var homepage = "https://trix-editor.org/"; var devDependencies = { "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.4", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^22.0.2", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.3.0", "@web/dev-server": "^0.1.34", "babel-eslint": "^10.1.0", chokidar: "^4.0.2", concurrently: "^7.4.0", eslint: "^7.32.0", esm: "^3.2.25", karma: "6.4.1", "karma-chrome-launcher": "3.2.0", "karma-qunit": "^4.1.2", "karma-sauce-launcher": "^4.3.6", qunit: "2.19.1", rangy: "^1.3.0", rollup: "^2.56.3", "rollup-plugin-includepaths": "^0.2.4", "rollup-plugin-terser": "^7.0.2", sass: "^1.83.0", svgo: "^2.8.0", webdriverio: "^7.19.5" }; var resolutions = { webdriverio: "^7.19.5" }; var scripts = { "build-css": "bin/sass-build assets/trix.scss dist/trix.css", "build-js": "rollup -c", "build-assets": "cp -f assets/*.html dist/", build: "yarn run build-js && yarn run build-css && yarn run build-assets", watch: "rollup -c -w", lint: "eslint .", pretest: "yarn run lint && yarn run build", test: "karma start", prerelease: "yarn version && yarn test", release: "npm adduser && npm publish", postrelease: "git push && git push --tags", dev: "web-dev-server --app-index index.html --root-dir dist --node-resolve --open", start: "yarn build-assets && concurrently --kill-others --names js,css,dev-server 'yarn watch' 'yarn build-css --watch' 'yarn dev'" }; var dependencies = { dompurify: "^3.2.5" }; var _package = { name: name, version: version, description: description, main: main, module: module, style: style, files: files, repository: repository, keywords: keywords, author: author, license: license, bugs: bugs, homepage: homepage, devDependencies: devDependencies, resolutions: resolutions, scripts: scripts, dependencies: dependencies }; const attachmentSelector = "[data-trix-attachment]"; const attachments = { preview: { presentation: "gallery", caption: { name: true, size: true } }, file: { caption: { size: true } } }; 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 androidVersionMatch = navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i); const androidVersion = androidVersionMatch && parseInt(androidVersionMatch[1]); var browser$1 = { // 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$3 = { 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$1 = { 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$1.bytes, lang$1.KB, lang$1.MB, lang$1.GB, lang$1.TB, lang$1.PB]; var file_size_formatting = { prefix: "IEC", precision: 2, formatter(number) { switch (number) { case 0: return "0 ".concat(lang$1.bytes); case 1: return "1 ".concat(lang$1.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 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 html$2 = document.documentElement; const match = html$2.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$2; 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$2; 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; const input = { level2Enabled: true, getLevel() { if (this.level2Enabled && browser$1.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$1.bold, "\" tabindex=\"-1\">").concat(lang$1.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$1.italic, "\" tabindex=\"-1\">").concat(lang$1.italic, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-strike\" data-trix-attribute=\"strike\" title=\"").concat(lang$1.strike, "\" tabindex=\"-1\">").concat(lang$1.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$1.link, "\" tabindex=\"-1\">").concat(lang$1.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$1.heading1, "\" tabindex=\"-1\">").concat(lang$1.heading1, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-quote\" data-trix-attribute=\"quote\" title=\"").concat(lang$1.quote, "\" tabindex=\"-1\">").concat(lang$1.quote, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-code\" data-trix-attribute=\"code\" title=\"").concat(lang$1.code, "\" tabindex=\"-1\">").concat(lang$1.code, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-bullet-list\" data-trix-attribute=\"bullet\" title=\"").concat(lang$1.bullets, "\" tabindex=\"-1\">").concat(lang$1.bullets, "</button>\n <button type=\"button\" class=\"trix-button trix-button--icon trix-button--icon-number-list\" data-trix-attribute=\"number\" title=\"").concat(lang$1.numbers, "\" tabindex=\"-1\">").concat(lang$1.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$1.outdent, "\" tabindex=\"-1\">").concat(lang$1.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$1.indent, "\" tabindex=\"-1\">").concat(lang$1.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$1.attachFiles, "\" tabindex=\"-1\">").concat(lang$1.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$1.undo, "\" tabindex=\"-1\">").concat(lang$1.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$1.redo, "\" tabindex=\"-1\">").concat(lang$1.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$1.urlPlaceholder, "\" aria-label=\"").concat(lang$1.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$1.link, "\" data-trix-method=\"setAttribute\">\n <input type=\"button\" class=\"trix-button trix-button--dialog\" value=\"").concat(lang$1.unlink, "\" data-trix-method=\"removeAttribute\">\n </div>\n </div>\n </div>\n </div>"); } }; const undo = { interval: 5000 }; var config = /*#__PURE__*/Object.freeze({ __proto__: null, attachments: attachments, blockAttributes: attributes, browser: browser$1, css: css$3, dompurify: dompurify, fileSize: file_size_formatting, input: input, keyNames: key_names, lang: lang$1, parser: parser, textAttributes: text_attributes, toolbar: toolbar, undo: undo }); 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$1.call(subject, object, arguments); } } else { subject = object[name]; return apply$1.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: apply$1 } = Function.prototype; const proxyMethodExpressionPattern = new RegExp("\ ^\ (.+?)\ (\\(\\))?\ (\\?)?\ \\.\ (.+?)\ $\ "); var _Array$from, _$codePointAt$1, _$1, _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$1 = ((_$codePointAt$1 = (_$1 = " ").codePointAt) === null || _$codePointAt$1 === void 0 ? void 0 : _$codePointAt$1.call(_$1, 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$1) { 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(""); }; } let id$2 = 0; class TrixObject extends BasicObject { static fromJSONString(jsonString) { return this.fromJSON(JSON.parse(jsonString)); } constructor() { super(...arguments); this.id = ++id$2; } hasSameConstructorAs(object) { return this.constructor === (object === null || object === void 0 ? void 0 : object.constructor); } isEqualTo(object) { return this === object; } inspect() { const parts = []; const contents = this.contentsForInspection() || {}; for (const key in contents) { const value = contents[key]; parts.push("".concat(key, "=").concat(value)); } return "#<".concat(this.constructor.name, ":").concat(this.id).concat(parts.length ? " ".concat(parts.join(", ")) : "", ">"); } contentsForInspection() {} toJSONString() { return JSON.stringify(this); } toUTF16String() { return UTF16String.box(this); } getCacheKey() { return this.id.toString(); } } /* 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 }; }; // 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"; } }; } }(); 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 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); /* 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 }; }; class Hash extends TrixObject { static fromCommonAttributesOfObjects() { let objects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; if (!objects.length) { return new this(); } let hash = box(objects[0]); let keys = hash.getKeys(); objects.slice(1).forEach(object => { keys = hash.getKeysCommonToHash(box(object)); hash = hash.slice(keys); }); return hash; } static box(values) { return box(values); } constructor() { let values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; super(...arguments); this.values = copy(values); } add(key, value) { return this.merge(object(key, value)); } remove(key) { return new Hash(copy(this.values, key)); } get(key) { return this.values[key]; } has(key) { return key in this.values; } merge(values) { return new Hash(merge(this.values, unbox(values))); } slice(keys) { const values = {}; Array.from(keys).forEach(key => { if (this.has(key)) { values[key] = this.values[key]; } }); return new Hash(values); } getKeys() { return Object.keys(this.values); } getKeysCommonToHash(hash) { hash = box(hash); return this.getKeys().filter(key => this.values[key] === hash.values[key]); } isEqualTo(values) { return arraysAreEqual(this.toArray(), box(values).toArray()); } isEmpty() { return this.getKeys().length === 0; } toArray() { if (!this.array) { const result = []; for (const key in this.values) { const value = this.values[key]; result.push(result.push(key, value)); } this.array = result.slice(0); } return this.array; } toObject() { return copy(this.values); } toJSON() { return this.toObject(); } contentsForInspection() { return { values: JSON.stringify(this.values) }; } } const object = function (key, value) { const result = {}; result[key] = value; return result; }; const merge = function (object, values) { const result = copy(object); for (const key in values) { const value = values[key]; result[key] = value; } return result; }; const copy = function (object, keyToRemove) { const result = {}; const sortedKeys = Object.keys(object).sort(); sortedKeys.forEach(key => { if (key !== keyToRemove) { result[key] = object[key]; } }); return result; }; const box = function (object) { if (object instanceof Hash) { return object; } else { return new Hash(object); } }; const unbox = function (object) { if (object instanceof Hash) { return object.values; } else { return object; } }; class ObjectGroup { static groupObjects() { let ungroupedObjects = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; let { depth, asTree } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; let group; if (asTree) { if (depth == null) { depth = 0; } } const objects = []; Array.from(ungroupedObjects).forEach(object => { var _object$canBeGrouped2; if (group) { var _object$canBeGrouped, _group$canBeGroupedWi, _group; if ((_object$canBeGrouped = object.canBeGrouped) !== null && _object$canBeGrouped !== void 0 && _object$canBeGrouped.call(object, depth) && (_group$canBeGroupedWi = (_group = group[group.length - 1]).canBeGroupedWith) !== null && _group$canBeGroupedWi !== void 0 && _group$canBeGroupedWi.call(_group, object, depth)) { group.push(object); return; } else { objects.push(new this(group, { depth, asTree })); group = null; } } if ((_object$canBeGrouped2 = object.canBeGrouped) !== null && _object$canBeGrouped2 !== void 0 && _object$canBeGrouped2.call(object, depth)) { group = [object]; } else { objects.push(object); } }); if (group) { objects.push(new this(group, { depth, asTree })); } return objects;