Skip to content

Commit c1689d8

Browse files
Update vendored trix version to 2.1.6 (rails#53170)
1 parent ca7b317 commit c1689d8

File tree

1 file changed

+149
-129
lines changed
  • actiontext/app/assets/javascripts

1 file changed

+149
-129
lines changed

actiontext/app/assets/javascripts/trix.js

Lines changed: 149 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Trix 2.1.1
2+
Trix 2.1.6
33
Copyright © 2024 37signals, LLC
44
*/
55
(function (global, factory) {
@@ -9,7 +9,7 @@ Copyright © 2024 37signals, LLC
99
})(this, (function () { 'use strict';
1010

1111
var name = "trix";
12-
var version = "2.1.1";
12+
var version = "2.1.6";
1313
var description = "A rich text editor for everyday writing";
1414
var main = "dist/trix.umd.min.js";
1515
var module = "dist/trix.esm.min.js";
@@ -1059,6 +1059,12 @@ $\
10591059
return text === null || text === void 0 ? void 0 : text.length;
10601060
}
10611061
};
1062+
const dataTransferIsMsOfficePaste = _ref => {
1063+
let {
1064+
dataTransfer
1065+
} = _ref;
1066+
return dataTransfer.types.includes("Files") && dataTransfer.types.includes("text/html") && dataTransfer.getData("text/html").includes("urn:schemas-microsoft-com:office:office");
1067+
};
10621068
const dataTransferIsWritable = function (dataTransfer) {
10631069
if (!(dataTransfer !== null && dataTransfer !== void 0 && dataTransfer.setData)) return false;
10641070
for (const key in testTransferData) {
@@ -1707,6 +1713,116 @@ $\
17071713
}
17081714
}
17091715

1716+
const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height language class".split(" ");
1717+
const DEFAULT_FORBIDDEN_PROTOCOLS = "javascript:".split(" ");
1718+
const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form noscript".split(" ");
1719+
class HTMLSanitizer extends BasicObject {
1720+
static setHTML(element, html) {
1721+
const sanitizedElement = new this(html).sanitize();
1722+
const sanitizedHtml = sanitizedElement.getHTML ? sanitizedElement.getHTML() : sanitizedElement.outerHTML;
1723+
element.innerHTML = sanitizedHtml;
1724+
}
1725+
static sanitize(html, options) {
1726+
const sanitizer = new this(html, options);
1727+
sanitizer.sanitize();
1728+
return sanitizer;
1729+
}
1730+
constructor(html) {
1731+
let {
1732+
allowedAttributes,
1733+
forbiddenProtocols,
1734+
forbiddenElements
1735+
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1736+
super(...arguments);
1737+
this.allowedAttributes = allowedAttributes || DEFAULT_ALLOWED_ATTRIBUTES;
1738+
this.forbiddenProtocols = forbiddenProtocols || DEFAULT_FORBIDDEN_PROTOCOLS;
1739+
this.forbiddenElements = forbiddenElements || DEFAULT_FORBIDDEN_ELEMENTS;
1740+
this.body = createBodyElementForHTML(html);
1741+
}
1742+
sanitize() {
1743+
this.sanitizeElements();
1744+
return this.normalizeListElementNesting();
1745+
}
1746+
getHTML() {
1747+
return this.body.innerHTML;
1748+
}
1749+
getBody() {
1750+
return this.body;
1751+
}
1752+
1753+
// Private
1754+
1755+
sanitizeElements() {
1756+
const walker = walkTree(this.body);
1757+
const nodesToRemove = [];
1758+
while (walker.nextNode()) {
1759+
const node = walker.currentNode;
1760+
switch (node.nodeType) {
1761+
case Node.ELEMENT_NODE:
1762+
if (this.elementIsRemovable(node)) {
1763+
nodesToRemove.push(node);
1764+
} else {
1765+
this.sanitizeElement(node);
1766+
}
1767+
break;
1768+
case Node.COMMENT_NODE:
1769+
nodesToRemove.push(node);
1770+
break;
1771+
}
1772+
}
1773+
nodesToRemove.forEach(node => removeNode(node));
1774+
return this.body;
1775+
}
1776+
sanitizeElement(element) {
1777+
if (element.hasAttribute("href")) {
1778+
if (this.forbiddenProtocols.includes(element.protocol)) {
1779+
element.removeAttribute("href");
1780+
}
1781+
}
1782+
Array.from(element.attributes).forEach(_ref => {
1783+
let {
1784+
name
1785+
} = _ref;
1786+
if (!this.allowedAttributes.includes(name) && name.indexOf("data-trix") !== 0) {
1787+
element.removeAttribute(name);
1788+
}
1789+
});
1790+
return element;
1791+
}
1792+
normalizeListElementNesting() {
1793+
Array.from(this.body.querySelectorAll("ul,ol")).forEach(listElement => {
1794+
const previousElement = listElement.previousElementSibling;
1795+
if (previousElement) {
1796+
if (tagName(previousElement) === "li") {
1797+
previousElement.appendChild(listElement);
1798+
}
1799+
}
1800+
});
1801+
return this.body;
1802+
}
1803+
elementIsRemovable(element) {
1804+
if ((element === null || element === void 0 ? void 0 : element.nodeType) !== Node.ELEMENT_NODE) return;
1805+
return this.elementIsForbidden(element) || this.elementIsntSerializable(element);
1806+
}
1807+
elementIsForbidden(element) {
1808+
return this.forbiddenElements.includes(tagName(element));
1809+
}
1810+
elementIsntSerializable(element) {
1811+
return element.getAttribute("data-trix-serialize") === "false" && !nodeIsAttachmentElement(element);
1812+
}
1813+
}
1814+
const createBodyElementForHTML = function () {
1815+
let html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
1816+
// Remove everything after </html>
1817+
html = html.replace(/<\/html[^>]*>[^]*$/i, "</html>");
1818+
const doc = document.implementation.createHTMLDocument("");
1819+
doc.documentElement.innerHTML = html;
1820+
Array.from(doc.head.querySelectorAll("style")).forEach(element => {
1821+
doc.body.appendChild(element);
1822+
});
1823+
return doc.body;
1824+
};
1825+
17101826
const {
17111827
css: css$2
17121828
} = config;
@@ -1741,7 +1857,7 @@ $\
17411857
figure.appendChild(innerElement);
17421858
}
17431859
if (this.attachment.hasContent()) {
1744-
innerElement.innerHTML = this.attachment.getContent();
1860+
HTMLSanitizer.setHTML(innerElement, this.attachment.getContent());
17451861
} else {
17461862
this.createContentNodes().forEach(node => {
17471863
innerElement.appendChild(node);
@@ -1869,7 +1985,7 @@ $\
18691985
});
18701986
const htmlContainsTagName = function (html, tagName) {
18711987
const div = makeElement("div");
1872-
div.innerHTML = html || "";
1988+
HTMLSanitizer.setHTML(div, html || "");
18731989
return div.querySelector(tagName);
18741990
};
18751991

@@ -6816,111 +6932,6 @@ $\
68166932
return attributes;
68176933
};
68186934

6819-
const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height language class".split(" ");
6820-
const DEFAULT_FORBIDDEN_PROTOCOLS = "javascript:".split(" ");
6821-
const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form noscript".split(" ");
6822-
class HTMLSanitizer extends BasicObject {
6823-
static sanitize(html, options) {
6824-
const sanitizer = new this(html, options);
6825-
sanitizer.sanitize();
6826-
return sanitizer;
6827-
}
6828-
constructor(html) {
6829-
let {
6830-
allowedAttributes,
6831-
forbiddenProtocols,
6832-
forbiddenElements
6833-
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
6834-
super(...arguments);
6835-
this.allowedAttributes = allowedAttributes || DEFAULT_ALLOWED_ATTRIBUTES;
6836-
this.forbiddenProtocols = forbiddenProtocols || DEFAULT_FORBIDDEN_PROTOCOLS;
6837-
this.forbiddenElements = forbiddenElements || DEFAULT_FORBIDDEN_ELEMENTS;
6838-
this.body = createBodyElementForHTML(html);
6839-
}
6840-
sanitize() {
6841-
this.sanitizeElements();
6842-
return this.normalizeListElementNesting();
6843-
}
6844-
getHTML() {
6845-
return this.body.innerHTML;
6846-
}
6847-
getBody() {
6848-
return this.body;
6849-
}
6850-
6851-
// Private
6852-
6853-
sanitizeElements() {
6854-
const walker = walkTree(this.body);
6855-
const nodesToRemove = [];
6856-
while (walker.nextNode()) {
6857-
const node = walker.currentNode;
6858-
switch (node.nodeType) {
6859-
case Node.ELEMENT_NODE:
6860-
if (this.elementIsRemovable(node)) {
6861-
nodesToRemove.push(node);
6862-
} else {
6863-
this.sanitizeElement(node);
6864-
}
6865-
break;
6866-
case Node.COMMENT_NODE:
6867-
nodesToRemove.push(node);
6868-
break;
6869-
}
6870-
}
6871-
nodesToRemove.forEach(node => removeNode(node));
6872-
return this.body;
6873-
}
6874-
sanitizeElement(element) {
6875-
if (element.hasAttribute("href")) {
6876-
if (this.forbiddenProtocols.includes(element.protocol)) {
6877-
element.removeAttribute("href");
6878-
}
6879-
}
6880-
Array.from(element.attributes).forEach(_ref => {
6881-
let {
6882-
name
6883-
} = _ref;
6884-
if (!this.allowedAttributes.includes(name) && name.indexOf("data-trix") !== 0) {
6885-
element.removeAttribute(name);
6886-
}
6887-
});
6888-
return element;
6889-
}
6890-
normalizeListElementNesting() {
6891-
Array.from(this.body.querySelectorAll("ul,ol")).forEach(listElement => {
6892-
const previousElement = listElement.previousElementSibling;
6893-
if (previousElement) {
6894-
if (tagName(previousElement) === "li") {
6895-
previousElement.appendChild(listElement);
6896-
}
6897-
}
6898-
});
6899-
return this.body;
6900-
}
6901-
elementIsRemovable(element) {
6902-
if ((element === null || element === void 0 ? void 0 : element.nodeType) !== Node.ELEMENT_NODE) return;
6903-
return this.elementIsForbidden(element) || this.elementIsntSerializable(element);
6904-
}
6905-
elementIsForbidden(element) {
6906-
return this.forbiddenElements.includes(tagName(element));
6907-
}
6908-
elementIsntSerializable(element) {
6909-
return element.getAttribute("data-trix-serialize") === "false" && !nodeIsAttachmentElement(element);
6910-
}
6911-
}
6912-
const createBodyElementForHTML = function () {
6913-
let html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
6914-
// Remove everything after </html>
6915-
html = html.replace(/<\/html[^>]*>[^]*$/i, "</html>");
6916-
const doc = document.implementation.createHTMLDocument("");
6917-
doc.documentElement.innerHTML = html;
6918-
Array.from(doc.head.querySelectorAll("style")).forEach(element => {
6919-
doc.body.appendChild(element);
6920-
});
6921-
return doc.body;
6922-
};
6923-
69246935
/* eslint-disable
69256936
no-case-declarations,
69266937
no-irregular-whitespace,
@@ -6956,11 +6967,7 @@ $\
69566967
};
69576968
const parseTrixDataAttribute = (element, name) => {
69586969
try {
6959-
const data = JSON.parse(element.getAttribute("data-trix-".concat(name)));
6960-
if (data.contentType === "text/html" && data.content) {
6961-
data.content = HTMLSanitizer.sanitize(data.content).getHTML();
6962-
}
6963-
return data;
6970+
return JSON.parse(element.getAttribute("data-trix-".concat(name)));
69646971
} catch (error) {
69656972
return {};
69666973
}
@@ -7003,8 +7010,7 @@ $\
70037010
parse() {
70047011
try {
70057012
this.createHiddenContainer();
7006-
const html = HTMLSanitizer.sanitize(this.html).getHTML();
7007-
this.containerElement.innerHTML = html;
7013+
HTMLSanitizer.setHTML(this.containerElement, this.html);
70087014
const walker = walkTree(this.containerElement, {
70097015
usingFilter: nodeFilter
70107016
});
@@ -10527,7 +10533,7 @@ $\
1052710533
return (_this$responder4 = this.responder) === null || _this$responder4 === void 0 ? void 0 : _this$responder4.deleteInDirection(direction);
1052810534
};
1052910535
const domRange = this.getTargetDOMRange({
10530-
minLength: 2
10536+
minLength: this.composing ? 1 : 2
1053110537
});
1053210538
if (domRange) {
1053310539
return this.withTargetDOMRange(domRange, perform);
@@ -10641,6 +10647,11 @@ $\
1064110647
},
1064210648
beforeinput(event) {
1064310649
const handler = this.constructor.inputTypes[event.inputType];
10650+
10651+
// Handles bug with Siri dictation on iOS 18+.
10652+
if (!event.inputType) {
10653+
this.render();
10654+
}
1064410655
if (handler) {
1064510656
this.withEvent(event, handler);
1064610657
this.scheduleRender();
@@ -10905,7 +10916,6 @@ $\
1090510916
}
1090610917
},
1090710918
insertFromPaste() {
10908-
var _dataTransfer$files;
1090910919
const {
1091010920
dataTransfer
1091110921
} = this.event;
@@ -10948,28 +10958,28 @@ $\
1094810958
var _this$delegate21;
1094910959
return (_this$delegate21 = this.delegate) === null || _this$delegate21 === void 0 ? void 0 : _this$delegate21.inputControllerDidPaste(paste);
1095010960
};
10951-
} else if (html) {
10961+
} else if (processableFilePaste(this.event)) {
1095210962
var _this$delegate22;
10953-
this.event.preventDefault();
10954-
paste.type = "text/html";
10955-
paste.html = html;
10963+
paste.type = "File";
10964+
paste.file = dataTransfer.files[0];
1095610965
(_this$delegate22 = this.delegate) === null || _this$delegate22 === void 0 || _this$delegate22.inputControllerWillPaste(paste);
1095710966
this.withTargetDOMRange(function () {
1095810967
var _this$responder34;
10959-
return (_this$responder34 = this.responder) === null || _this$responder34 === void 0 ? void 0 : _this$responder34.insertHTML(paste.html);
10968+
return (_this$responder34 = this.responder) === null || _this$responder34 === void 0 ? void 0 : _this$responder34.insertFile(paste.file);
1096010969
});
1096110970
this.afterRender = () => {
1096210971
var _this$delegate23;
1096310972
return (_this$delegate23 = this.delegate) === null || _this$delegate23 === void 0 ? void 0 : _this$delegate23.inputControllerDidPaste(paste);
1096410973
};
10965-
} else if ((_dataTransfer$files = dataTransfer.files) !== null && _dataTransfer$files !== void 0 && _dataTransfer$files.length) {
10974+
} else if (html) {
1096610975
var _this$delegate24;
10967-
paste.type = "File";
10968-
paste.file = dataTransfer.files[0];
10976+
this.event.preventDefault();
10977+
paste.type = "text/html";
10978+
paste.html = html;
1096910979
(_this$delegate24 = this.delegate) === null || _this$delegate24 === void 0 || _this$delegate24.inputControllerWillPaste(paste);
1097010980
this.withTargetDOMRange(function () {
1097110981
var _this$responder35;
10972-
return (_this$responder35 = this.responder) === null || _this$responder35 === void 0 ? void 0 : _this$responder35.insertFile(paste.file);
10982+
return (_this$responder35 = this.responder) === null || _this$responder35 === void 0 ? void 0 : _this$responder35.insertHTML(paste.html);
1097310983
});
1097410984
this.afterRender = () => {
1097510985
var _this$delegate25;
@@ -11030,10 +11040,20 @@ $\
1103011040
var _event$dataTransfer;
1103111041
return Array.from(((_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.types) || []).includes("Files");
1103211042
};
11043+
const processableFilePaste = event => {
11044+
var _event$dataTransfer$f;
11045+
// Paste events that only have files are handled by the paste event handler,
11046+
// to work around Safari not supporting beforeinput.insertFromPaste for files.
11047+
11048+
// MS Office text pastes include a file with a screenshot of the text, but we should
11049+
// handle them as text pastes.
11050+
return ((_event$dataTransfer$f = event.dataTransfer.files) === null || _event$dataTransfer$f === void 0 ? void 0 : _event$dataTransfer$f[0]) && !pasteEventHasFilesOnly(event) && !dataTransferIsMsOfficePaste(event);
11051+
};
1103311052
const pasteEventHasFilesOnly = function (event) {
1103411053
const clipboard = event.clipboardData;
1103511054
if (clipboard) {
11036-
return clipboard.types.includes("Files") && clipboard.types.length === 1 && clipboard.files.length >= 1;
11055+
const fileTypes = Array.from(clipboard.types).filter(type => type.match(/file/i)); // "Files", "application/x-moz-file"
11056+
return fileTypes.length === clipboard.types.length && clipboard.files.length >= 1;
1103711057
}
1103811058
};
1103911059
const pasteEventHasPlainTextOnly = function (event) {
@@ -11956,7 +11976,7 @@ $\
1195611976
};
1195711977
}
1195811978
}();
11959-
installDefaultCSSForTagName("trix-editor", "%t {\n display: block;\n}\n\n%t:empty:not(:focus)::before {\n content: attr(placeholder);\n color: graytext;\n cursor: text;\n pointer-events: none;\n white-space: pre-line;\n}\n\n%t a[contenteditable=false] {\n cursor: text;\n}\n\n%t img {\n max-width: 100%;\n height: auto;\n}\n\n%t ".concat(attachmentSelector, " figcaption textarea {\n resize: none;\n}\n\n%t ").concat(attachmentSelector, " figcaption textarea.trix-autoresize-clone {\n position: absolute;\n left: -9999px;\n max-height: 0px;\n}\n\n%t ").concat(attachmentSelector, " figcaption[data-trix-placeholder]:empty::before {\n content: attr(data-trix-placeholder);\n color: graytext;\n}\n\n%t [data-trix-cursor-target] {\n display: ").concat(cursorTargetStyles.display, " !important;\n width: ").concat(cursorTargetStyles.width, " !important;\n padding: 0 !important;\n margin: 0 !important;\n border: none !important;\n}\n\n%t [data-trix-cursor-target=left] {\n vertical-align: top !important;\n margin-left: -1px !important;\n}\n\n%t [data-trix-cursor-target=right] {\n vertical-align: bottom !important;\n margin-right: -1px !important;\n}"));
11979+
installDefaultCSSForTagName("trix-editor", "%t {\n display: block;\n}\n\n%t:empty::before {\n content: attr(placeholder);\n color: graytext;\n cursor: text;\n pointer-events: none;\n white-space: pre-line;\n}\n\n%t a[contenteditable=false] {\n cursor: text;\n}\n\n%t img {\n max-width: 100%;\n height: auto;\n}\n\n%t ".concat(attachmentSelector, " figcaption textarea {\n resize: none;\n}\n\n%t ").concat(attachmentSelector, " figcaption textarea.trix-autoresize-clone {\n position: absolute;\n left: -9999px;\n max-height: 0px;\n}\n\n%t ").concat(attachmentSelector, " figcaption[data-trix-placeholder]:empty::before {\n content: attr(data-trix-placeholder);\n color: graytext;\n}\n\n%t [data-trix-cursor-target] {\n display: ").concat(cursorTargetStyles.display, " !important;\n width: ").concat(cursorTargetStyles.width, " !important;\n padding: 0 !important;\n margin: 0 !important;\n border: none !important;\n}\n\n%t [data-trix-cursor-target=left] {\n vertical-align: top !important;\n margin-left: -1px !important;\n}\n\n%t [data-trix-cursor-target=right] {\n vertical-align: bottom !important;\n margin-right: -1px !important;\n}"));
1196011980
class TrixEditorElement extends HTMLElement {
1196111981
// Properties
1196211982

0 commit comments

Comments
 (0)