@37signals/lexxy 0.9.29 → 0.9.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/lexxy.esm.js +31 -4
  2. package/package.json +1 -1
package/dist/lexxy.esm.js CHANGED
@@ -15,7 +15,7 @@ import { HistoryExtension } from '@lexical/history';
15
15
  import { $isCodeNode, CodeHighlightNode, PrismTokenizer, $isCodeHighlightNode, $createCodeNode, $createCodeHighlightNode, CodeNode, registerCodeHighlighting, normalizeCodeLang, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
16
16
  import { TRANSFORMERS, registerMarkdownShortcuts } from '@lexical/markdown';
17
17
  import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, setScrollableTablesActive, registerTablePlugin, registerTableSelectionObserver, TableCellHeaderStates, $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $findTableNode, $getTableRowIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode, $findCellNode, $getElementForTableNode } from '@lexical/table';
18
- import { marked } from 'marked';
18
+ import { Marked } from 'marked';
19
19
  import { $insertDataTransferForRichText } from '@lexical/clipboard';
20
20
  import 'prismjs';
21
21
  import 'prismjs/components/prism-clike';
@@ -67,6 +67,22 @@ function styleFilterHook(_currentNode, hookEvent) {
67
67
 
68
68
  DOMPurify.addHook("uponSanitizeAttribute", styleFilterHook);
69
69
 
70
+ const FORBIDDEN_STIMULUS_ATTRIBUTES = [ "data-controller", "data-action" ];
71
+
72
+ // Stimulus behavior attributes must never survive sanitization, whatever an
73
+ // extension's allowedElements declares. FORBID_ATTR alone isn't enough: in
74
+ // DOMPurify 3.x the functional ADD_ATTR — which Lexxy builds from the public
75
+ // allowedElements API — is evaluated ahead of FORBID_ATTR, so an extension that
76
+ // listed one of these on a tag would otherwise reinstate it. This hook drops
77
+ // them unconditionally, keeping the class-level prohibition config-independent.
78
+ function stimulusAttributeFilterHook(_currentNode, hookEvent) {
79
+ if (FORBIDDEN_STIMULUS_ATTRIBUTES.includes(hookEvent.attrName)) {
80
+ hookEvent.keepAttr = false;
81
+ }
82
+ }
83
+
84
+ DOMPurify.addHook("uponSanitizeAttribute", stimulusAttributeFilterHook);
85
+
70
86
  DOMPurify.addHook("uponSanitizeElement", (node, data) => {
71
87
  if (data.tagName === "strong" || data.tagName === "em") {
72
88
  node.removeAttribute("class");
@@ -90,7 +106,12 @@ function buildConfig(allowedElements ) {
90
106
  ALLOWED_ATTR: ALLOWED_HTML_ATTRIBUTES,
91
107
  ADD_ATTR: (attribute, tag) => tagAttributes[tag]?.includes(attribute),
92
108
  ADD_URI_SAFE_ATTR: [ "caption", "filename" ],
93
- SAFE_FOR_XML: false // So that it does not strip attributes that contains serialized HTML (like content)
109
+ SAFE_FOR_XML: false, // So that it does not strip attributes that contains serialized HTML (like content)
110
+ // Stimulus behavior attributes must never survive sanitization: they let stored content
111
+ // wire up arbitrary controllers/actions in the viewer's session. FORBID_ATTR wins over
112
+ // ALLOWED_ATTR/ADD_ATTR/ALLOW_DATA_ATTR in DOMPurify, so this holds even though other
113
+ // data-* attributes (data-language, data-trix-*, etc.) are otherwise allowed through.
114
+ FORBID_ATTR: [ "data-controller", "data-action" ]
94
115
  }
95
116
  }
96
117
 
@@ -6348,7 +6369,7 @@ class Contents {
6348
6369
  insertText(text, { tag } = {}) {
6349
6370
  this.editor.update(() => {
6350
6371
  const paragraph = $createParagraphNode();
6351
- text.split("\n").forEach((line, index) => {
6372
+ text.split(/\r\n|\r|\n/).forEach((line, index) => {
6352
6373
  if (index > 0) paragraph.append($createLineBreakNode());
6353
6374
  paragraph.append($createTextNode(line));
6354
6375
  });
@@ -6990,6 +7011,12 @@ class Contents {
6990
7011
  }
6991
7012
  }
6992
7013
 
7014
+ // Markdown reads any line indented by a tab or four spaces as a code block. Pasted
7015
+ // plain text is full of incidental indentation — outlines, notes, logs — and nobody
7016
+ // indents a note meaning "this is code"; they fence it. Keep fences, drop the
7017
+ // indentation rule.
7018
+ const pastedMarkdown = new Marked({ breaks: true }).use({ tokenizer: { code: () => undefined } });
7019
+
6993
7020
  class Clipboard {
6994
7021
  #listeners = new ListenerBin()
6995
7022
 
@@ -7176,7 +7203,7 @@ class Clipboard {
7176
7203
  }
7177
7204
 
7178
7205
  #pasteMarkdown(text) {
7179
- const html = marked(text, { breaks: true });
7206
+ const html = pastedMarkdown.parse(text);
7180
7207
  const doc = parseHtml(html);
7181
7208
 
7182
7209
  if (this.#isPlainTextWithoutMarkdown(doc)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",