trix
Version:
A rich text editor for everyday writing
1,412 lines (1,369 loc) • 148 kB
JavaScript
/*
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) {