trix
Version:
A rich text editor for everyday writing
1,474 lines (1,436 loc) • 515 kB
JavaScript
/*
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;