lexxy 0.9.28 → 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.
- checksums.yaml +4 -4
- data/app/assets/javascript/lexxy.js +180 -35
- data/app/assets/javascript/lexxy.js.br +0 -0
- data/app/assets/javascript/lexxy.js.gz +0 -0
- data/app/assets/javascript/lexxy.js.map +1 -1
- data/app/assets/javascript/lexxy.min.js +1 -1
- data/app/assets/javascript/lexxy.min.js.br +0 -0
- data/app/assets/javascript/lexxy.min.js.gz +0 -0
- data/lib/lexxy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 29ffce96e1428f47448c9dd8dba203d14209fed8614c09319de418275e7d7c4b
|
|
4
|
+
data.tar.gz: 54b507d1fa6cbbb69e0c34ae60fe9d2ffca21b3f9a9312cb11d690c9a46bd39d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a018d5b7719927ffa23b3dd7f83487c1988899b32cb14178415e460358bc86e908a583ec8e064f9692b0deee3f2a7c57f85c0f2dcf761b01653783749ab2c498
|
|
7
|
+
data.tar.gz: d9c7cf6b9653ec1938009ad6c6b6d341dbe6fa79a202432ea3b49090f30d054cf29ec6b580fdb31228868f55fe1b114a1e746dc2a806531cba84bc6fc070a636
|
|
@@ -6479,6 +6479,22 @@ function styleFilterHook(_currentNode, hookEvent) {
|
|
|
6479
6479
|
|
|
6480
6480
|
purify.addHook("uponSanitizeAttribute", styleFilterHook);
|
|
6481
6481
|
|
|
6482
|
+
const FORBIDDEN_STIMULUS_ATTRIBUTES = [ "data-controller", "data-action" ];
|
|
6483
|
+
|
|
6484
|
+
// Stimulus behavior attributes must never survive sanitization, whatever an
|
|
6485
|
+
// extension's allowedElements declares. FORBID_ATTR alone isn't enough: in
|
|
6486
|
+
// DOMPurify 3.x the functional ADD_ATTR — which Lexxy builds from the public
|
|
6487
|
+
// allowedElements API — is evaluated ahead of FORBID_ATTR, so an extension that
|
|
6488
|
+
// listed one of these on a tag would otherwise reinstate it. This hook drops
|
|
6489
|
+
// them unconditionally, keeping the class-level prohibition config-independent.
|
|
6490
|
+
function stimulusAttributeFilterHook(_currentNode, hookEvent) {
|
|
6491
|
+
if (FORBIDDEN_STIMULUS_ATTRIBUTES.includes(hookEvent.attrName)) {
|
|
6492
|
+
hookEvent.keepAttr = false;
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6496
|
+
purify.addHook("uponSanitizeAttribute", stimulusAttributeFilterHook);
|
|
6497
|
+
|
|
6482
6498
|
purify.addHook("uponSanitizeElement", (node, data) => {
|
|
6483
6499
|
if (data.tagName === "strong" || data.tagName === "em") {
|
|
6484
6500
|
node.removeAttribute("class");
|
|
@@ -6502,7 +6518,12 @@ function buildConfig(allowedElements ) {
|
|
|
6502
6518
|
ALLOWED_ATTR: ALLOWED_HTML_ATTRIBUTES,
|
|
6503
6519
|
ADD_ATTR: (attribute, tag) => tagAttributes[tag]?.includes(attribute),
|
|
6504
6520
|
ADD_URI_SAFE_ATTR: [ "caption", "filename" ],
|
|
6505
|
-
SAFE_FOR_XML: false // So that it does not strip attributes that contains serialized HTML (like content)
|
|
6521
|
+
SAFE_FOR_XML: false, // So that it does not strip attributes that contains serialized HTML (like content)
|
|
6522
|
+
// Stimulus behavior attributes must never survive sanitization: they let stored content
|
|
6523
|
+
// wire up arbitrary controllers/actions in the viewer's session. FORBID_ATTR wins over
|
|
6524
|
+
// ALLOWED_ATTR/ADD_ATTR/ALLOW_DATA_ATTR in DOMPurify, so this holds even though other
|
|
6525
|
+
// data-* attributes (data-language, data-trix-*, etc.) are otherwise allowed through.
|
|
6526
|
+
FORBID_ATTR: [ "data-controller", "data-action" ]
|
|
6506
6527
|
}
|
|
6507
6528
|
}
|
|
6508
6529
|
|
|
@@ -8029,7 +8050,7 @@ class CustomActionTextAttachmentNode extends Ji {
|
|
|
8029
8050
|
return Lexxy.global.get("attachmentTagName")
|
|
8030
8051
|
}
|
|
8031
8052
|
|
|
8032
|
-
constructor({ tagName, sgid, contentType, innerHtml, plainText }, key) {
|
|
8053
|
+
constructor({ tagName, sgid, contentType, innerHtml, plainText } = {}, key) {
|
|
8033
8054
|
super(key);
|
|
8034
8055
|
|
|
8035
8056
|
const contentTypeNamespace = Lexxy.global.get("attachmentContentTypeNamespace");
|
|
@@ -8840,7 +8861,7 @@ class ActionTextAttachmentNode extends Ji {
|
|
|
8840
8861
|
return Lexxy.global.get("attachmentTagName")
|
|
8841
8862
|
}
|
|
8842
8863
|
|
|
8843
|
-
constructor({ tagName, sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError }, key) {
|
|
8864
|
+
constructor({ tagName, sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError } = {}, key) {
|
|
8844
8865
|
super(key);
|
|
8845
8866
|
|
|
8846
8867
|
this.tagName = tagName || ActionTextAttachmentNode.TAG_NAME;
|
|
@@ -12034,7 +12055,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12034
12055
|
return null
|
|
12035
12056
|
}
|
|
12036
12057
|
|
|
12037
|
-
constructor(node, key) {
|
|
12058
|
+
constructor(node = {}, key) {
|
|
12038
12059
|
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError, fileName, contentType } = node;
|
|
12039
12060
|
super({ ...node, contentType: file?.type ?? contentType }, key);
|
|
12040
12061
|
this.file = file ?? null;
|
|
@@ -12055,9 +12076,10 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12055
12076
|
// node is reloaded from saved state such as from history.
|
|
12056
12077
|
this.#startUploadIfNeeded();
|
|
12057
12078
|
|
|
12058
|
-
// Bridge-managed uploads (uploadUrl is null)
|
|
12059
|
-
//
|
|
12060
|
-
|
|
12079
|
+
// Bridge-managed uploads (uploadUrl is null) and restored or remote
|
|
12080
|
+
// instances (no local File) don't have file data to show an image
|
|
12081
|
+
// preview, so always show the file icon during upload.
|
|
12082
|
+
const canPreviewFile = this.isPreviewableAttachment && this.uploadUrl != null && this.file != null;
|
|
12061
12083
|
const figure = this.createAttachmentFigure(canPreviewFile);
|
|
12062
12084
|
|
|
12063
12085
|
if (canPreviewFile) {
|
|
@@ -12128,9 +12150,12 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12128
12150
|
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
12129
12151
|
|
|
12130
12152
|
const nameSpan = createElement("span", { className: "attachment__name", textContent: this.caption || this.fileName || "" });
|
|
12131
|
-
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file?.size) });
|
|
12132
12153
|
figcaption.appendChild(nameSpan);
|
|
12133
|
-
|
|
12154
|
+
|
|
12155
|
+
if (this.file) {
|
|
12156
|
+
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file.size) });
|
|
12157
|
+
figcaption.appendChild(sizeSpan);
|
|
12158
|
+
}
|
|
12134
12159
|
|
|
12135
12160
|
return figcaption
|
|
12136
12161
|
}
|
|
@@ -12152,6 +12177,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12152
12177
|
async #startUploadIfNeeded() {
|
|
12153
12178
|
if (this.#uploadStarted) return
|
|
12154
12179
|
if (!this.uploadUrl) return // Bridge-managed upload — skip DirectUpload
|
|
12180
|
+
if (!this.file) return // Restored or remote instance — no local File to upload
|
|
12155
12181
|
|
|
12156
12182
|
this.#setUploadStarted();
|
|
12157
12183
|
|
|
@@ -12643,12 +12669,24 @@ class OfficeFormatter {
|
|
|
12643
12669
|
}
|
|
12644
12670
|
}
|
|
12645
12671
|
|
|
12672
|
+
// Properties worth rescuing from a foreign style sheet before #stripStyleElements
|
|
12673
|
+
// drops it, keyed by the elements that carry them. Word and Outlook declare
|
|
12674
|
+
// paragraph spacing in the sheet (p.MsoNormal { margin: 0cm }) and never inline,
|
|
12675
|
+
// so once the sheet goes there is nothing left to say how tightly the author's
|
|
12676
|
+
// paragraphs sat, and hosts that turn pasted paragraph margins into explicit
|
|
12677
|
+
// blank lines read tight Office paragraphs as spaced ones and multiply every
|
|
12678
|
+
// line break.
|
|
12679
|
+
const PRESERVABLE_STYLES = {
|
|
12680
|
+
p: [ "margin-top", "margin-bottom" ]
|
|
12681
|
+
};
|
|
12682
|
+
|
|
12646
12683
|
class PastedContentFormatter {
|
|
12647
12684
|
constructor(doc) {
|
|
12648
12685
|
this.doc = doc;
|
|
12649
12686
|
}
|
|
12650
12687
|
|
|
12651
12688
|
format() {
|
|
12689
|
+
this.#inlinePreservableStyles();
|
|
12652
12690
|
this.#stripStyleElements();
|
|
12653
12691
|
this.#unwrapPlaceholderAnchors();
|
|
12654
12692
|
this.#stripTableCellColorStyles();
|
|
@@ -12656,9 +12694,23 @@ class PastedContentFormatter {
|
|
|
12656
12694
|
this.#unwrapWrappedListChildren();
|
|
12657
12695
|
this.#nestStrayListChildren();
|
|
12658
12696
|
this.#stripStrayListChildren();
|
|
12697
|
+
this.#replaceGmailEmojiImgTags();
|
|
12659
12698
|
return this.doc
|
|
12660
12699
|
}
|
|
12661
12700
|
|
|
12701
|
+
// Runs before #stripStyleElements, to inline anything we still need
|
|
12702
|
+
// from an external style before we remove it
|
|
12703
|
+
#inlinePreservableStyles() {
|
|
12704
|
+
const rules = this.#styleRules();
|
|
12705
|
+
if (rules.length === 0) return
|
|
12706
|
+
|
|
12707
|
+
for (const [ selector, properties ] of Object.entries(PRESERVABLE_STYLES)) {
|
|
12708
|
+
for (const element of this.doc.querySelectorAll(selector)) {
|
|
12709
|
+
this.#inlineDeclaredStyles(element, properties, rules);
|
|
12710
|
+
}
|
|
12711
|
+
}
|
|
12712
|
+
}
|
|
12713
|
+
|
|
12662
12714
|
// Spreadsheets (e.g. Excel) copy a <style> block whose rules (td { color:
|
|
12663
12715
|
// black }, .xlNN { ... }) cascade onto the imported text. That color rides
|
|
12664
12716
|
// in through the cascade rather than an inline style, so it slips past both
|
|
@@ -12753,6 +12805,72 @@ class PastedContentFormatter {
|
|
|
12753
12805
|
}
|
|
12754
12806
|
}
|
|
12755
12807
|
|
|
12808
|
+
// Read the rules from a sheet we build rather than from this.doc.styleSheets: the
|
|
12809
|
+
// document came from DOMParser, so it has no browsing context, and WebKit leaves
|
|
12810
|
+
// its styleSheets empty. Parsing the text ourselves still expands the shorthands
|
|
12811
|
+
// Word writes (margin: 0cm) into the longhands we ask for.
|
|
12812
|
+
#styleRules() {
|
|
12813
|
+
const css = Array.from(this.doc.querySelectorAll("style"), (style) => style.textContent).join("\n");
|
|
12814
|
+
if (css.trim() === "") return []
|
|
12815
|
+
|
|
12816
|
+
const sheet = new CSSStyleSheet();
|
|
12817
|
+
sheet.replaceSync(css);
|
|
12818
|
+
|
|
12819
|
+
const rules = [];
|
|
12820
|
+
for (const rule of sheet.cssRules) {
|
|
12821
|
+
if (rule instanceof CSSStyleRule) {
|
|
12822
|
+
rules.push(rule);
|
|
12823
|
+
}
|
|
12824
|
+
}
|
|
12825
|
+
return rules
|
|
12826
|
+
}
|
|
12827
|
+
|
|
12828
|
+
// A declaration the element already carries inline is the author's own and wins.
|
|
12829
|
+
#inlineDeclaredStyles(element, properties, rules) {
|
|
12830
|
+
for (const property of properties) {
|
|
12831
|
+
if (element.style.getPropertyValue(property) === "") {
|
|
12832
|
+
const value = this.#declaredValue(rules, element, property);
|
|
12833
|
+
if (value) {
|
|
12834
|
+
this.#appendStyleProperty(element, property, value);
|
|
12835
|
+
}
|
|
12836
|
+
}
|
|
12837
|
+
}
|
|
12838
|
+
}
|
|
12839
|
+
|
|
12840
|
+
// Source order alone doesn't decide the cascade, so when the matching rules
|
|
12841
|
+
// disagree we can't say which value wins without resolving specificity. Skip
|
|
12842
|
+
// the property rather than guess: guessing is permanent, since the sheet is
|
|
12843
|
+
// about to go, and skipping only leaves the host on its own default.
|
|
12844
|
+
#declaredValue(rules, element, property) {
|
|
12845
|
+
const declared = new Set();
|
|
12846
|
+
for (const rule of rules) {
|
|
12847
|
+
const value = rule.style.getPropertyValue(property);
|
|
12848
|
+
if (value !== "" && element.matches(rule.selectorText)) {
|
|
12849
|
+
declared.add(value);
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
|
|
12853
|
+
if (declared.size === 1) {
|
|
12854
|
+
return declared.values().next().value
|
|
12855
|
+
} else {
|
|
12856
|
+
return null
|
|
12857
|
+
}
|
|
12858
|
+
}
|
|
12859
|
+
|
|
12860
|
+
// Assigning through element.style would reserialize the style attribute and
|
|
12861
|
+
// drop the mso-* declarations Word leaves inline (they aren't valid CSS),
|
|
12862
|
+
// and OfficeFormatter still needs to read mso-list from them. Append to the
|
|
12863
|
+
// attribute instead so the original declarations survive untouched.
|
|
12864
|
+
#appendStyleProperty(element, property, value) {
|
|
12865
|
+
const style = element.getAttribute("style");
|
|
12866
|
+
const declaration = `${property}:${value}`;
|
|
12867
|
+
if (style === null || style.trim() === "") {
|
|
12868
|
+
element.setAttribute("style", declaration);
|
|
12869
|
+
} else {
|
|
12870
|
+
element.setAttribute("style", `${style.replace(/;\s*$/, "")};${declaration}`);
|
|
12871
|
+
}
|
|
12872
|
+
}
|
|
12873
|
+
|
|
12756
12874
|
#firstStrayListChild(list) {
|
|
12757
12875
|
for (const child of list.childNodes) {
|
|
12758
12876
|
if (child.nodeType !== Node.ELEMENT_NODE || child.tagName !== "LI") {
|
|
@@ -12769,6 +12887,16 @@ class PastedContentFormatter {
|
|
|
12769
12887
|
#containsListItems(node) {
|
|
12770
12888
|
return node.nodeType === Node.ELEMENT_NODE && node.querySelector("li") !== null
|
|
12771
12889
|
}
|
|
12890
|
+
|
|
12891
|
+
// Emoji in gmail: <img data-emoji="😂" class="an1" alt="😂" aria-label="😂" draggable="false" src="https://fonts.gstatic.com/s/e/notoemoji/17.0/1f602/32.png" loading="lazy" style="height: 1.2em; width: 1.2em; vertical-align: middle;">
|
|
12892
|
+
#replaceGmailEmojiImgTags() {
|
|
12893
|
+
for (const emojiImg of this.doc.querySelectorAll("img[data-emoji]")) {
|
|
12894
|
+
const emoji = emojiImg.dataset.emoji;
|
|
12895
|
+
if (emoji === emojiImg.alt) {
|
|
12896
|
+
emojiImg.replaceWith(emoji);
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
}
|
|
12772
12900
|
}
|
|
12773
12901
|
|
|
12774
12902
|
class Contents {
|
|
@@ -12803,7 +12931,7 @@ class Contents {
|
|
|
12803
12931
|
insertText(text, { tag } = {}) {
|
|
12804
12932
|
this.editor.update(() => {
|
|
12805
12933
|
const paragraph = eo();
|
|
12806
|
-
text.split(
|
|
12934
|
+
text.split(/\r\n|\r|\n/).forEach((line, index) => {
|
|
12807
12935
|
if (index > 0) paragraph.append(or());
|
|
12808
12936
|
paragraph.append(kr(line));
|
|
12809
12937
|
});
|
|
@@ -12811,20 +12939,6 @@ class Contents {
|
|
|
12811
12939
|
}, { tag });
|
|
12812
12940
|
}
|
|
12813
12941
|
|
|
12814
|
-
// A literal "\n" stays visible in the editor, which Lexical renders with white-space: pre-wrap,
|
|
12815
|
-
// but collapses to a space once serialized to HTML, so it has to become line break nodes.
|
|
12816
|
-
insertTextWithLineBreaks(text) {
|
|
12817
|
-
const normalizedText = text?.replace(/\r\n?/g, "\n");
|
|
12818
|
-
const selection = Qr();
|
|
12819
|
-
|
|
12820
|
-
if (normalizedText?.includes("\n") && Fr(selection)) {
|
|
12821
|
-
selection.insertRawText(normalizedText);
|
|
12822
|
-
return true
|
|
12823
|
-
} else {
|
|
12824
|
-
return false
|
|
12825
|
-
}
|
|
12826
|
-
}
|
|
12827
|
-
|
|
12828
12942
|
insertAtCursor(...nodes) {
|
|
12829
12943
|
const selection = this.#insertableSelection();
|
|
12830
12944
|
const inserter = BaseNodeInserter.for(selection);
|
|
@@ -13531,6 +13645,12 @@ ${e}</tr>
|
|
|
13531
13645
|
`+this.renderer.text(o);t?n+=this.renderer.paragraph({type:"paragraph",raw:a,text:a,tokens:[{type:"text",raw:a,text:a,escaped:true}]}):n+=a;continue}default:{let o='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}parseInline(e,t=this.renderer){let n="";for(let r=0;r<e.length;r++){let i=e[r];if(this.options.extensions?.renderers?.[i.type]){let o=this.options.extensions.renderers[i.type].call({parser:this},i);if(o!==false||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(i.type)){n+=o||"";continue}}let s=i;switch(s.type){case "escape":{n+=t.text(s);break}case "html":{n+=t.html(s);break}case "link":{n+=t.link(s);break}case "image":{n+=t.image(s);break}case "strong":{n+=t.strong(s);break}case "em":{n+=t.em(s);break}case "codespan":{n+=t.codespan(s);break}case "br":{n+=t.br(s);break}case "del":{n+=t.del(s);break}case "text":{n+=t.text(s);break}default:{let o='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}};var S=class{options;block;constructor(e){this.options=e||T;}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens","emStrongMask"]);static passThroughHooksRespectAsync=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}emStrongMask(e){return e}provideLexer(){return this.block?x.lex:x.lexInline}provideParser(){return this.block?b.parse:b.parseInline}};var B=class{defaults=L();options=this.setOptions;parse=this.parseMarkdown(true);parseInline=this.parseMarkdown(false);Parser=b;Renderer=P;TextRenderer=$;Lexer=x;Tokenizer=y;Hooks=S;constructor(...e){this.use(...e);}walkTokens(e,t){let n=[];for(let r of e)switch(n=n.concat(t.call(this,r)),r.type){case "table":{let i=r;for(let s of i.header)n=n.concat(this.walkTokens(s.tokens,t));for(let s of i.rows)for(let o of s)n=n.concat(this.walkTokens(o.tokens,t));break}case "list":{let i=r;n=n.concat(this.walkTokens(i.items,t));break}default:{let i=r;this.defaults.extensions?.childTokens?.[i.type]?this.defaults.extensions.childTokens[i.type].forEach(s=>{let o=i[s].flat(1/0);n=n.concat(this.walkTokens(o,t));}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)));}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let r={...n};if(r.async=this.defaults.async||r.async||false,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let s=t.renderers[i.name];s?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===false&&(a=s.apply(this,o)),a}:t.renderers[i.name]=i.renderer;}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let s=t[i.level];s?s.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]));}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens);}),r.extensions=t),n.renderer){let i=this.defaults.renderer||new P(this.defaults);for(let s in n.renderer){if(!(s in i))throw new Error(`renderer '${s}' does not exist`);if(["options","parser"].includes(s))continue;let o=s,a=n.renderer[o],l=i[o];i[o]=(...c)=>{let p=a.apply(i,c);return p===false&&(p=l.apply(i,c)),p||""};}r.renderer=i;}if(n.tokenizer){let i=this.defaults.tokenizer||new y(this.defaults);for(let s in n.tokenizer){if(!(s in i))throw new Error(`tokenizer '${s}' does not exist`);if(["options","rules","lexer"].includes(s))continue;let o=s,a=n.tokenizer[o],l=i[o];i[o]=(...c)=>{let p=a.apply(i,c);return p===false&&(p=l.apply(i,c)),p};}r.tokenizer=i;}if(n.hooks){let i=this.defaults.hooks||new S;for(let s in n.hooks){if(!(s in i))throw new Error(`hook '${s}' does not exist`);if(["options","block"].includes(s))continue;let o=s,a=n.hooks[o],l=i[o];S.passThroughHooks.has(s)?i[o]=c=>{if(this.defaults.async&&S.passThroughHooksRespectAsync.has(s))return (async()=>{let g=await a.call(i,c);return l.call(i,g)})();let p=a.call(i,c);return l.call(i,p)}:i[o]=(...c)=>{if(this.defaults.async)return (async()=>{let g=await a.apply(i,c);return g===false&&(g=await l.apply(i,c)),g})();let p=a.apply(i,c);return p===false&&(p=l.apply(i,c)),p};}r.hooks=i;}if(n.walkTokens){let i=this.defaults.walkTokens,s=n.walkTokens;r.walkTokens=function(o){let a=[];return a.push(s.call(this,o)),i&&(a=a.concat(i.call(this,o))),a};}this.defaults={...this.defaults,...r};}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return (n,r)=>{let i={...r},s={...this.defaults,...i},o=this.onError(!!s.silent,!!s.async);if(this.defaults.async===true&&i.async===false)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(s.hooks&&(s.hooks.options=s,s.hooks.block=e),s.async)return (async()=>{let a=s.hooks?await s.hooks.preprocess(n):n,c=await(s.hooks?await s.hooks.provideLexer():e?x.lex:x.lexInline)(a,s),p=s.hooks?await s.hooks.processAllTokens(c):c;s.walkTokens&&await Promise.all(this.walkTokens(p,s.walkTokens));let d=await(s.hooks?await s.hooks.provideParser():e?b.parse:b.parseInline)(p,s);return s.hooks?await s.hooks.postprocess(d):d})().catch(o);try{s.hooks&&(n=s.hooks.preprocess(n));let l=(s.hooks?s.hooks.provideLexer():e?x.lex:x.lexInline)(n,s);s.hooks&&(l=s.hooks.processAllTokens(l)),s.walkTokens&&this.walkTokens(l,s.walkTokens);let p=(s.hooks?s.hooks.provideParser():e?b.parse:b.parseInline)(l,s);return s.hooks&&(p=s.hooks.postprocess(p)),p}catch(a){return o(a)}}}onError(e,t){return n=>{if(n.message+=`
|
|
13532
13646
|
Please report this to https://github.com/markedjs/marked.`,e){let r="<p>An error occurred:</p><pre>"+w(n.message+"",true)+"</pre>";return t?Promise.resolve(r):r}if(t)return Promise.reject(n);throw n}}};var _=new B;function k(u,e){return _.parse(u,e)}k.options=k.setOptions=function(u){return _.setOptions(u),k.defaults=_.defaults,G(k.defaults),k};k.getDefaults=L;k.defaults=T;k.use=function(...u){return _.use(...u),k.defaults=_.defaults,G(k.defaults),k};k.walkTokens=function(u,e){return _.walkTokens(u,e)};k.parseInline=_.parseInline;k.Parser=b;k.parser=b.parse;k.Renderer=P;k.TextRenderer=$;k.Lexer=x;k.lexer=x.lex;k.Tokenizer=y;k.Hooks=S;k.parse=k;k.options;k.setOptions;k.use;k.walkTokens;k.parseInline;b.parse;x.lex;
|
|
13533
13647
|
|
|
13648
|
+
// Markdown reads any line indented by a tab or four spaces as a code block. Pasted
|
|
13649
|
+
// plain text is full of incidental indentation — outlines, notes, logs — and nobody
|
|
13650
|
+
// indents a note meaning "this is code"; they fence it. Keep fences, drop the
|
|
13651
|
+
// indentation rule.
|
|
13652
|
+
const pastedMarkdown = new B({ breaks: true }).use({ tokenizer: { code: () => undefined } });
|
|
13653
|
+
|
|
13534
13654
|
class Clipboard {
|
|
13535
13655
|
#listeners = new ListenerBin()
|
|
13536
13656
|
|
|
@@ -13717,7 +13837,7 @@ class Clipboard {
|
|
|
13717
13837
|
}
|
|
13718
13838
|
|
|
13719
13839
|
#pasteMarkdown(text) {
|
|
13720
|
-
const html =
|
|
13840
|
+
const html = pastedMarkdown.parse(text);
|
|
13721
13841
|
const doc = parseHtml(html);
|
|
13722
13842
|
|
|
13723
13843
|
if (this.#isPlainTextWithoutMarkdown(doc)) {
|
|
@@ -15542,6 +15662,39 @@ class CustomAttachmentDragAndDropExtension extends LexxyExtension {
|
|
|
15542
15662
|
}
|
|
15543
15663
|
}
|
|
15544
15664
|
|
|
15665
|
+
const LINE_SEPARATORS = /\r\n|[\n\r\u2028\u2029]/;
|
|
15666
|
+
|
|
15667
|
+
// A literal line separator inside a text node renders as a break in the editor, or not at all,
|
|
15668
|
+
// but never survives serialization to HTML. macOS text replacements deliver U+2028 LINE SEPARATOR,
|
|
15669
|
+
// and other insertions can carry "\n", so however the text arrives, a transform converts whatever
|
|
15670
|
+
// lands in a text node into line break nodes.
|
|
15671
|
+
class LineSeparatorsExtension extends LexxyExtension {
|
|
15672
|
+
get lexicalExtension() {
|
|
15673
|
+
return this.defineExtension({
|
|
15674
|
+
name: "lexxy/line-separators",
|
|
15675
|
+
register(editor) {
|
|
15676
|
+
return editor.registerNodeTransform(gr, $replaceLineSeparatorWithLineBreak)
|
|
15677
|
+
}
|
|
15678
|
+
})
|
|
15679
|
+
}
|
|
15680
|
+
}
|
|
15681
|
+
|
|
15682
|
+
// Replaces the first separator only: the split leaves dirty text nodes behind, so Lexical re-runs
|
|
15683
|
+
// the transform until none remain.
|
|
15684
|
+
function $replaceLineSeparatorWithLineBreak(textNode) {
|
|
15685
|
+
const match = textNode.getTextContent().match(LINE_SEPARATORS);
|
|
15686
|
+
|
|
15687
|
+
if (match) {
|
|
15688
|
+
let separatorNode;
|
|
15689
|
+
if (match.index === 0) {
|
|
15690
|
+
[ separatorNode ] = textNode.splitText(match[0].length);
|
|
15691
|
+
} else {
|
|
15692
|
+
[ , separatorNode ] = textNode.splitText(match.index, match.index + match[0].length);
|
|
15693
|
+
}
|
|
15694
|
+
separatorNode.replace(or());
|
|
15695
|
+
}
|
|
15696
|
+
}
|
|
15697
|
+
|
|
15545
15698
|
class LexicalEditorElement extends HTMLElement {
|
|
15546
15699
|
static formAssociated = true
|
|
15547
15700
|
static debug = false
|
|
@@ -15700,7 +15853,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15700
15853
|
FormatEscapeExtension,
|
|
15701
15854
|
LinkOpenerExtension,
|
|
15702
15855
|
PreventLexicalTripleClickExtension,
|
|
15703
|
-
CustomAttachmentDragAndDropExtension
|
|
15856
|
+
CustomAttachmentDragAndDropExtension,
|
|
15857
|
+
LineSeparatorsExtension
|
|
15704
15858
|
]
|
|
15705
15859
|
}
|
|
15706
15860
|
|
|
@@ -15888,7 +16042,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15888
16042
|
#initialize() {
|
|
15889
16043
|
this.#registerComponents();
|
|
15890
16044
|
this.#handleEnter();
|
|
15891
|
-
this.#handleReplacementText();
|
|
15892
16045
|
this.#registerFocusEvents();
|
|
15893
16046
|
this.#registerHistoryEvents();
|
|
15894
16047
|
this.#registerFileAcceptFilter();
|
|
@@ -16152,14 +16305,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
16152
16305
|
));
|
|
16153
16306
|
}
|
|
16154
16307
|
|
|
16155
|
-
#handleReplacementText() {
|
|
16156
|
-
this.#listeners.track(this.editor.registerCommand(
|
|
16157
|
-
ge$3,
|
|
16158
|
-
(event) => event instanceof InputEvent && this.contents.insertTextWithLineBreaks(event.data),
|
|
16159
|
-
oo
|
|
16160
|
-
));
|
|
16161
|
-
}
|
|
16162
|
-
|
|
16163
16308
|
#registerFocusEvents() {
|
|
16164
16309
|
this.#listeners.track(
|
|
16165
16310
|
registerEventListener(this, "focusin", this.#handleFocusIn),
|
|
Binary file
|
|
Binary file
|