1
1
/*
2
- Trix 2.1.1
2
+ Trix 2.1.6
3
3
Copyright © 2024 37signals, LLC
4
4
*/
5
5
( function ( global , factory ) {
@@ -9,7 +9,7 @@ Copyright © 2024 37signals, LLC
9
9
} ) ( this , ( function ( ) { 'use strict' ;
10
10
11
11
var name = "trix" ;
12
- var version = "2.1.1 " ;
12
+ var version = "2.1.6 " ;
13
13
var description = "A rich text editor for everyday writing" ;
14
14
var main = "dist/trix.umd.min.js" ;
15
15
var module = "dist/trix.esm.min.js" ;
@@ -1059,6 +1059,12 @@ $\
1059
1059
return text === null || text === void 0 ? void 0 : text . length ;
1060
1060
}
1061
1061
} ;
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
+ } ;
1062
1068
const dataTransferIsWritable = function ( dataTransfer ) {
1063
1069
if ( ! ( dataTransfer !== null && dataTransfer !== void 0 && dataTransfer . setData ) ) return false ;
1064
1070
for ( const key in testTransferData ) {
@@ -1707,6 +1713,116 @@ $\
1707
1713
}
1708
1714
}
1709
1715
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 ( / < \/ h t m l [ ^ > ] * > [ ^ ] * $ / 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
+
1710
1826
const {
1711
1827
css : css$2
1712
1828
} = config ;
1741
1857
figure . appendChild ( innerElement ) ;
1742
1858
}
1743
1859
if ( this . attachment . hasContent ( ) ) {
1744
- innerElement . innerHTML = this . attachment . getContent ( ) ;
1860
+ HTMLSanitizer . setHTML ( innerElement , this . attachment . getContent ( ) ) ;
1745
1861
} else {
1746
1862
this . createContentNodes ( ) . forEach ( node => {
1747
1863
innerElement . appendChild ( node ) ;
1869
1985
} ) ;
1870
1986
const htmlContainsTagName = function ( html , tagName ) {
1871
1987
const div = makeElement ( "div" ) ;
1872
- div . innerHTML = html || "" ;
1988
+ HTMLSanitizer . setHTML ( div , html || "" ) ;
1873
1989
return div . querySelector ( tagName ) ;
1874
1990
} ;
1875
1991
@@ -6816,111 +6932,6 @@ $\
6816
6932
return attributes ;
6817
6933
} ;
6818
6934
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 ( / < \/ h t m l [ ^ > ] * > [ ^ ] * $ / 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
-
6924
6935
/* eslint-disable
6925
6936
no-case-declarations,
6926
6937
no-irregular-whitespace,
@@ -6956,11 +6967,7 @@ $\
6956
6967
} ;
6957
6968
const parseTrixDataAttribute = ( element , name ) => {
6958
6969
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 ) ) ) ;
6964
6971
} catch ( error ) {
6965
6972
return { } ;
6966
6973
}
7003
7010
parse ( ) {
7004
7011
try {
7005
7012
this . createHiddenContainer ( ) ;
7006
- const html = HTMLSanitizer . sanitize ( this . html ) . getHTML ( ) ;
7007
- this . containerElement . innerHTML = html ;
7013
+ HTMLSanitizer . setHTML ( this . containerElement , this . html ) ;
7008
7014
const walker = walkTree ( this . containerElement , {
7009
7015
usingFilter : nodeFilter
7010
7016
} ) ;
@@ -10527,7 +10533,7 @@ $\
10527
10533
return ( _this$responder4 = this . responder ) === null || _this$responder4 === void 0 ? void 0 : _this$responder4 . deleteInDirection ( direction ) ;
10528
10534
} ;
10529
10535
const domRange = this . getTargetDOMRange ( {
10530
- minLength : 2
10536
+ minLength : this . composing ? 1 : 2
10531
10537
} ) ;
10532
10538
if ( domRange ) {
10533
10539
return this . withTargetDOMRange ( domRange , perform ) ;
@@ -10641,6 +10647,11 @@ $\
10641
10647
} ,
10642
10648
beforeinput ( event ) {
10643
10649
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
+ }
10644
10655
if ( handler ) {
10645
10656
this . withEvent ( event , handler ) ;
10646
10657
this . scheduleRender ( ) ;
@@ -10905,7 +10916,6 @@ $\
10905
10916
}
10906
10917
} ,
10907
10918
insertFromPaste ( ) {
10908
- var _dataTransfer$files ;
10909
10919
const {
10910
10920
dataTransfer
10911
10921
} = this . event ;
@@ -10948,28 +10958,28 @@ $\
10948
10958
var _this$delegate21 ;
10949
10959
return ( _this$delegate21 = this . delegate ) === null || _this$delegate21 === void 0 ? void 0 : _this$delegate21 . inputControllerDidPaste ( paste ) ;
10950
10960
} ;
10951
- } else if ( html ) {
10961
+ } else if ( processableFilePaste ( this . event ) ) {
10952
10962
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 ] ;
10956
10965
( _this$delegate22 = this . delegate ) === null || _this$delegate22 === void 0 || _this$delegate22 . inputControllerWillPaste ( paste ) ;
10957
10966
this . withTargetDOMRange ( function ( ) {
10958
10967
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 ) ;
10960
10969
} ) ;
10961
10970
this . afterRender = ( ) => {
10962
10971
var _this$delegate23 ;
10963
10972
return ( _this$delegate23 = this . delegate ) === null || _this$delegate23 === void 0 ? void 0 : _this$delegate23 . inputControllerDidPaste ( paste ) ;
10964
10973
} ;
10965
- } else if ( ( _dataTransfer$files = dataTransfer . files ) !== null && _dataTransfer$files !== void 0 && _dataTransfer$files . length ) {
10974
+ } else if ( html ) {
10966
10975
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 ;
10969
10979
( _this$delegate24 = this . delegate ) === null || _this$delegate24 === void 0 || _this$delegate24 . inputControllerWillPaste ( paste ) ;
10970
10980
this . withTargetDOMRange ( function ( ) {
10971
10981
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 ) ;
10973
10983
} ) ;
10974
10984
this . afterRender = ( ) => {
10975
10985
var _this$delegate25 ;
@@ -11030,10 +11040,20 @@ $\
11030
11040
var _event$dataTransfer ;
11031
11041
return Array . from ( ( ( _event$dataTransfer = event . dataTransfer ) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer . types ) || [ ] ) . includes ( "Files" ) ;
11032
11042
} ;
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
+ } ;
11033
11052
const pasteEventHasFilesOnly = function ( event ) {
11034
11053
const clipboard = event . clipboardData ;
11035
11054
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 ( / f i l e / i) ) ; // "Files", "application/x-moz-file"
11056
+ return fileTypes . length === clipboard . types . length && clipboard . files . length >= 1 ;
11037
11057
}
11038
11058
} ;
11039
11059
const pasteEventHasPlainTextOnly = function ( event ) {
@@ -11956,7 +11976,7 @@ $\
11956
11976
} ;
11957
11977
}
11958
11978
} ( ) ;
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}" ) ) ;
11960
11980
class TrixEditorElement extends HTMLElement {
11961
11981
// Properties
11962
11982
0 commit comments