lexxy 0.9.27 → 0.9.29
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 +294 -9
- 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 +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c6392cdd1a3fd5cc9535778cb84e544b5864e6d75378af4e19b6d7c6c0a5628
|
|
4
|
+
data.tar.gz: 88bccc783d7e3de2f7650e2d80ad7899369053182de3fbe4b51c50f7566421fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0aa7bf890d287539720cb79cd58cee9e32833c26ed4bd0d6418ab7f94c0bed4fee739a14299093a3333e8337d23a0d4cd00fe1524defefa8f14842ceb0b278a9
|
|
7
|
+
data.tar.gz: c0dfc34fee283b34b76281b77b9dd24a57fcd7ef8d488e70628107fb1aa7a537c8811986e1a5f77d5df2573e6d040214a62f037c5f3845de136f44b813f2e71c
|
|
@@ -8029,7 +8029,7 @@ class CustomActionTextAttachmentNode extends Ji {
|
|
|
8029
8029
|
return Lexxy.global.get("attachmentTagName")
|
|
8030
8030
|
}
|
|
8031
8031
|
|
|
8032
|
-
constructor({ tagName, sgid, contentType, innerHtml, plainText }, key) {
|
|
8032
|
+
constructor({ tagName, sgid, contentType, innerHtml, plainText } = {}, key) {
|
|
8033
8033
|
super(key);
|
|
8034
8034
|
|
|
8035
8035
|
const contentTypeNamespace = Lexxy.global.get("attachmentContentTypeNamespace");
|
|
@@ -8840,7 +8840,7 @@ class ActionTextAttachmentNode extends Ji {
|
|
|
8840
8840
|
return Lexxy.global.get("attachmentTagName")
|
|
8841
8841
|
}
|
|
8842
8842
|
|
|
8843
|
-
constructor({ tagName, sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError }, key) {
|
|
8843
|
+
constructor({ tagName, sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError } = {}, key) {
|
|
8844
8844
|
super(key);
|
|
8845
8845
|
|
|
8846
8846
|
this.tagName = tagName || ActionTextAttachmentNode.TAG_NAME;
|
|
@@ -12034,7 +12034,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12034
12034
|
return null
|
|
12035
12035
|
}
|
|
12036
12036
|
|
|
12037
|
-
constructor(node, key) {
|
|
12037
|
+
constructor(node = {}, key) {
|
|
12038
12038
|
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError, fileName, contentType } = node;
|
|
12039
12039
|
super({ ...node, contentType: file?.type ?? contentType }, key);
|
|
12040
12040
|
this.file = file ?? null;
|
|
@@ -12055,9 +12055,10 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12055
12055
|
// node is reloaded from saved state such as from history.
|
|
12056
12056
|
this.#startUploadIfNeeded();
|
|
12057
12057
|
|
|
12058
|
-
// Bridge-managed uploads (uploadUrl is null)
|
|
12059
|
-
//
|
|
12060
|
-
|
|
12058
|
+
// Bridge-managed uploads (uploadUrl is null) and restored or remote
|
|
12059
|
+
// instances (no local File) don't have file data to show an image
|
|
12060
|
+
// preview, so always show the file icon during upload.
|
|
12061
|
+
const canPreviewFile = this.isPreviewableAttachment && this.uploadUrl != null && this.file != null;
|
|
12061
12062
|
const figure = this.createAttachmentFigure(canPreviewFile);
|
|
12062
12063
|
|
|
12063
12064
|
if (canPreviewFile) {
|
|
@@ -12128,9 +12129,12 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12128
12129
|
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
12129
12130
|
|
|
12130
12131
|
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
12132
|
figcaption.appendChild(nameSpan);
|
|
12133
|
-
|
|
12133
|
+
|
|
12134
|
+
if (this.file) {
|
|
12135
|
+
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file.size) });
|
|
12136
|
+
figcaption.appendChild(sizeSpan);
|
|
12137
|
+
}
|
|
12134
12138
|
|
|
12135
12139
|
return figcaption
|
|
12136
12140
|
}
|
|
@@ -12152,6 +12156,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12152
12156
|
async #startUploadIfNeeded() {
|
|
12153
12157
|
if (this.#uploadStarted) return
|
|
12154
12158
|
if (!this.uploadUrl) return // Bridge-managed upload — skip DirectUpload
|
|
12159
|
+
if (!this.file) return // Restored or remote instance — no local File to upload
|
|
12155
12160
|
|
|
12156
12161
|
this.#setUploadStarted();
|
|
12157
12162
|
|
|
@@ -12500,21 +12505,191 @@ BaseNodeInserter.for = (selection) => {
|
|
|
12500
12505
|
return inserterClass ? new inserterClass(selection) : selection
|
|
12501
12506
|
};
|
|
12502
12507
|
|
|
12508
|
+
// Word and Outlook never emit <ul>/<ol>/<li>. They simulate lists with flat
|
|
12509
|
+
// paragraphs that declare their depth in an inline `mso-list:l0 level2 lfo1`
|
|
12510
|
+
// and carry the bullet or number glyph in a <span style="mso-list:Ignore">.
|
|
12511
|
+
// Nothing downstream understands either, so the items land as plain paragraphs
|
|
12512
|
+
// with the marker baked into the text. Rebuild real lists from the level each
|
|
12513
|
+
// paragraph declares.
|
|
12514
|
+
//
|
|
12515
|
+
// Everything this reads lives in inline attributes, so it stays correct after
|
|
12516
|
+
// the pasted content formatter has dropped Office's style sheet.
|
|
12517
|
+
class OfficeFormatter {
|
|
12518
|
+
constructor(doc) {
|
|
12519
|
+
this.doc = doc;
|
|
12520
|
+
}
|
|
12521
|
+
|
|
12522
|
+
format() {
|
|
12523
|
+
for (const run of this.#listRuns()) {
|
|
12524
|
+
this.#replaceWithLists(run);
|
|
12525
|
+
}
|
|
12526
|
+
}
|
|
12527
|
+
|
|
12528
|
+
// A run is a stretch of Office list paragraphs that are siblings with nothing
|
|
12529
|
+
// but whitespace and Word's conditional comments between them. Anything else
|
|
12530
|
+
// in between (a heading, a plain paragraph) ends one list and starts another.
|
|
12531
|
+
#listRuns() {
|
|
12532
|
+
const runs = [];
|
|
12533
|
+
const claimed = new Set();
|
|
12534
|
+
|
|
12535
|
+
for (const paragraph of this.doc.querySelectorAll("p[style*='mso-list']")) {
|
|
12536
|
+
if (claimed.has(paragraph) || this.#listLevel(paragraph) === null) continue
|
|
12537
|
+
|
|
12538
|
+
const run = [];
|
|
12539
|
+
let node = paragraph;
|
|
12540
|
+
while (node) {
|
|
12541
|
+
run.push(node);
|
|
12542
|
+
claimed.add(node);
|
|
12543
|
+
node = this.#nextListParagraph(node);
|
|
12544
|
+
}
|
|
12545
|
+
runs.push(run);
|
|
12546
|
+
}
|
|
12547
|
+
|
|
12548
|
+
return runs
|
|
12549
|
+
}
|
|
12550
|
+
|
|
12551
|
+
#nextListParagraph(paragraph) {
|
|
12552
|
+
let sibling = paragraph.nextSibling;
|
|
12553
|
+
while (sibling && this.#isIgnorableBetweenListParagraphs(sibling)) {
|
|
12554
|
+
sibling = sibling.nextSibling;
|
|
12555
|
+
}
|
|
12556
|
+
|
|
12557
|
+
if (sibling && sibling.nodeType === Node.ELEMENT_NODE && this.#listLevel(sibling) !== null) {
|
|
12558
|
+
return sibling
|
|
12559
|
+
}
|
|
12560
|
+
|
|
12561
|
+
return null
|
|
12562
|
+
}
|
|
12563
|
+
|
|
12564
|
+
#isIgnorableBetweenListParagraphs(node) {
|
|
12565
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
12566
|
+
return true
|
|
12567
|
+
}
|
|
12568
|
+
return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === ""
|
|
12569
|
+
}
|
|
12570
|
+
|
|
12571
|
+
// Word numbers levels from 1, but a copied selection often starts partway down
|
|
12572
|
+
// a list, so every paragraph declares level3 with no level1 above it. Anchoring
|
|
12573
|
+
// the run at its own shallowest level is what keeps the result from arriving
|
|
12574
|
+
// needlessly deep.
|
|
12575
|
+
#replaceWithLists(paragraphs) {
|
|
12576
|
+
const levels = paragraphs.map((paragraph) => this.#listLevel(paragraph));
|
|
12577
|
+
const baseLevel = Math.min(...levels);
|
|
12578
|
+
const roots = [];
|
|
12579
|
+
const stack = [];
|
|
12580
|
+
|
|
12581
|
+
paragraphs.forEach((paragraph, index) => {
|
|
12582
|
+
const depth = levels[index] - baseLevel + 1;
|
|
12583
|
+
const tagName = this.#listTagName(paragraph);
|
|
12584
|
+
|
|
12585
|
+
while (stack.length > depth) {
|
|
12586
|
+
stack.pop();
|
|
12587
|
+
}
|
|
12588
|
+
if (stack.length === depth && stack[depth - 1].tagName !== tagName.toUpperCase()) {
|
|
12589
|
+
stack.pop();
|
|
12590
|
+
}
|
|
12591
|
+
|
|
12592
|
+
while (stack.length < depth) {
|
|
12593
|
+
const list = this.doc.createElement(tagName);
|
|
12594
|
+
if (stack.length === 0) {
|
|
12595
|
+
roots.push(list);
|
|
12596
|
+
} else {
|
|
12597
|
+
this.#lastItemOf(stack[stack.length - 1]).appendChild(list);
|
|
12598
|
+
}
|
|
12599
|
+
stack.push(list);
|
|
12600
|
+
}
|
|
12601
|
+
|
|
12602
|
+
stack[stack.length - 1].appendChild(this.#createListItem(paragraph));
|
|
12603
|
+
});
|
|
12604
|
+
|
|
12605
|
+
paragraphs[0].replaceWith(...roots);
|
|
12606
|
+
for (const paragraph of paragraphs.slice(1)) {
|
|
12607
|
+
paragraph.remove();
|
|
12608
|
+
}
|
|
12609
|
+
}
|
|
12610
|
+
|
|
12611
|
+
#lastItemOf(list) {
|
|
12612
|
+
if (!list.lastElementChild) {
|
|
12613
|
+
list.appendChild(this.doc.createElement("li"));
|
|
12614
|
+
}
|
|
12615
|
+
return list.lastElementChild
|
|
12616
|
+
}
|
|
12617
|
+
|
|
12618
|
+
// Word writes bullets as the raw glyph of the marker font (Symbol's "·",
|
|
12619
|
+
// Wingdings' "§", Courier New's "o") and numbers as the rendered counter
|
|
12620
|
+
// ("1.", "a)", "iv)"). A bare letter with no delimiter after it is Courier
|
|
12621
|
+
// New's bullet, not a counter, so the delimiter is what tells them apart.
|
|
12622
|
+
#listTagName(paragraph) {
|
|
12623
|
+
const marker = paragraph.querySelector("[style*='mso-list:Ignore']");
|
|
12624
|
+
const text = marker?.textContent || "";
|
|
12625
|
+
|
|
12626
|
+
if (/\d/.test(text) || /^\(?[A-Za-z]+[.)]/.test(text)) {
|
|
12627
|
+
return "ol"
|
|
12628
|
+
}
|
|
12629
|
+
return "ul"
|
|
12630
|
+
}
|
|
12631
|
+
|
|
12632
|
+
#createListItem(paragraph) {
|
|
12633
|
+
for (const marker of paragraph.querySelectorAll("[style*='mso-list:Ignore']")) {
|
|
12634
|
+
marker.remove();
|
|
12635
|
+
}
|
|
12636
|
+
|
|
12637
|
+
const item = this.doc.createElement("li");
|
|
12638
|
+
item.append(...paragraph.childNodes);
|
|
12639
|
+
return item
|
|
12640
|
+
}
|
|
12641
|
+
|
|
12642
|
+
#listLevel(element) {
|
|
12643
|
+
const match = /mso-list:[^;"']*\blevel(\d+)/.exec(element.getAttribute("style") || "");
|
|
12644
|
+
if (match) {
|
|
12645
|
+
return parseInt(match[1], 10)
|
|
12646
|
+
}
|
|
12647
|
+
return null
|
|
12648
|
+
}
|
|
12649
|
+
}
|
|
12650
|
+
|
|
12651
|
+
// Properties worth rescuing from a foreign style sheet before #stripStyleElements
|
|
12652
|
+
// drops it, keyed by the elements that carry them. Word and Outlook declare
|
|
12653
|
+
// paragraph spacing in the sheet (p.MsoNormal { margin: 0cm }) and never inline,
|
|
12654
|
+
// so once the sheet goes there is nothing left to say how tightly the author's
|
|
12655
|
+
// paragraphs sat, and hosts that turn pasted paragraph margins into explicit
|
|
12656
|
+
// blank lines read tight Office paragraphs as spaced ones and multiply every
|
|
12657
|
+
// line break.
|
|
12658
|
+
const PRESERVABLE_STYLES = {
|
|
12659
|
+
p: [ "margin-top", "margin-bottom" ]
|
|
12660
|
+
};
|
|
12661
|
+
|
|
12503
12662
|
class PastedContentFormatter {
|
|
12504
12663
|
constructor(doc) {
|
|
12505
12664
|
this.doc = doc;
|
|
12506
12665
|
}
|
|
12507
12666
|
|
|
12508
12667
|
format() {
|
|
12668
|
+
this.#inlinePreservableStyles();
|
|
12509
12669
|
this.#stripStyleElements();
|
|
12510
12670
|
this.#unwrapPlaceholderAnchors();
|
|
12511
12671
|
this.#stripTableCellColorStyles();
|
|
12672
|
+
new OfficeFormatter(this.doc).format();
|
|
12512
12673
|
this.#unwrapWrappedListChildren();
|
|
12513
12674
|
this.#nestStrayListChildren();
|
|
12514
12675
|
this.#stripStrayListChildren();
|
|
12676
|
+
this.#replaceGmailEmojiImgTags();
|
|
12515
12677
|
return this.doc
|
|
12516
12678
|
}
|
|
12517
12679
|
|
|
12680
|
+
// Runs before #stripStyleElements, to inline anything we still need
|
|
12681
|
+
// from an external style before we remove it
|
|
12682
|
+
#inlinePreservableStyles() {
|
|
12683
|
+
const rules = this.#styleRules();
|
|
12684
|
+
if (rules.length === 0) return
|
|
12685
|
+
|
|
12686
|
+
for (const [ selector, properties ] of Object.entries(PRESERVABLE_STYLES)) {
|
|
12687
|
+
for (const element of this.doc.querySelectorAll(selector)) {
|
|
12688
|
+
this.#inlineDeclaredStyles(element, properties, rules);
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
}
|
|
12692
|
+
|
|
12518
12693
|
// Spreadsheets (e.g. Excel) copy a <style> block whose rules (td { color:
|
|
12519
12694
|
// black }, .xlNN { ... }) cascade onto the imported text. That color rides
|
|
12520
12695
|
// in through the cascade rather than an inline style, so it slips past both
|
|
@@ -12609,6 +12784,72 @@ class PastedContentFormatter {
|
|
|
12609
12784
|
}
|
|
12610
12785
|
}
|
|
12611
12786
|
|
|
12787
|
+
// Read the rules from a sheet we build rather than from this.doc.styleSheets: the
|
|
12788
|
+
// document came from DOMParser, so it has no browsing context, and WebKit leaves
|
|
12789
|
+
// its styleSheets empty. Parsing the text ourselves still expands the shorthands
|
|
12790
|
+
// Word writes (margin: 0cm) into the longhands we ask for.
|
|
12791
|
+
#styleRules() {
|
|
12792
|
+
const css = Array.from(this.doc.querySelectorAll("style"), (style) => style.textContent).join("\n");
|
|
12793
|
+
if (css.trim() === "") return []
|
|
12794
|
+
|
|
12795
|
+
const sheet = new CSSStyleSheet();
|
|
12796
|
+
sheet.replaceSync(css);
|
|
12797
|
+
|
|
12798
|
+
const rules = [];
|
|
12799
|
+
for (const rule of sheet.cssRules) {
|
|
12800
|
+
if (rule instanceof CSSStyleRule) {
|
|
12801
|
+
rules.push(rule);
|
|
12802
|
+
}
|
|
12803
|
+
}
|
|
12804
|
+
return rules
|
|
12805
|
+
}
|
|
12806
|
+
|
|
12807
|
+
// A declaration the element already carries inline is the author's own and wins.
|
|
12808
|
+
#inlineDeclaredStyles(element, properties, rules) {
|
|
12809
|
+
for (const property of properties) {
|
|
12810
|
+
if (element.style.getPropertyValue(property) === "") {
|
|
12811
|
+
const value = this.#declaredValue(rules, element, property);
|
|
12812
|
+
if (value) {
|
|
12813
|
+
this.#appendStyleProperty(element, property, value);
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12817
|
+
}
|
|
12818
|
+
|
|
12819
|
+
// Source order alone doesn't decide the cascade, so when the matching rules
|
|
12820
|
+
// disagree we can't say which value wins without resolving specificity. Skip
|
|
12821
|
+
// the property rather than guess: guessing is permanent, since the sheet is
|
|
12822
|
+
// about to go, and skipping only leaves the host on its own default.
|
|
12823
|
+
#declaredValue(rules, element, property) {
|
|
12824
|
+
const declared = new Set();
|
|
12825
|
+
for (const rule of rules) {
|
|
12826
|
+
const value = rule.style.getPropertyValue(property);
|
|
12827
|
+
if (value !== "" && element.matches(rule.selectorText)) {
|
|
12828
|
+
declared.add(value);
|
|
12829
|
+
}
|
|
12830
|
+
}
|
|
12831
|
+
|
|
12832
|
+
if (declared.size === 1) {
|
|
12833
|
+
return declared.values().next().value
|
|
12834
|
+
} else {
|
|
12835
|
+
return null
|
|
12836
|
+
}
|
|
12837
|
+
}
|
|
12838
|
+
|
|
12839
|
+
// Assigning through element.style would reserialize the style attribute and
|
|
12840
|
+
// drop the mso-* declarations Word leaves inline (they aren't valid CSS),
|
|
12841
|
+
// and OfficeFormatter still needs to read mso-list from them. Append to the
|
|
12842
|
+
// attribute instead so the original declarations survive untouched.
|
|
12843
|
+
#appendStyleProperty(element, property, value) {
|
|
12844
|
+
const style = element.getAttribute("style");
|
|
12845
|
+
const declaration = `${property}:${value}`;
|
|
12846
|
+
if (style === null || style.trim() === "") {
|
|
12847
|
+
element.setAttribute("style", declaration);
|
|
12848
|
+
} else {
|
|
12849
|
+
element.setAttribute("style", `${style.replace(/;\s*$/, "")};${declaration}`);
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
|
|
12612
12853
|
#firstStrayListChild(list) {
|
|
12613
12854
|
for (const child of list.childNodes) {
|
|
12614
12855
|
if (child.nodeType !== Node.ELEMENT_NODE || child.tagName !== "LI") {
|
|
@@ -12625,6 +12866,16 @@ class PastedContentFormatter {
|
|
|
12625
12866
|
#containsListItems(node) {
|
|
12626
12867
|
return node.nodeType === Node.ELEMENT_NODE && node.querySelector("li") !== null
|
|
12627
12868
|
}
|
|
12869
|
+
|
|
12870
|
+
// 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;">
|
|
12871
|
+
#replaceGmailEmojiImgTags() {
|
|
12872
|
+
for (const emojiImg of this.doc.querySelectorAll("img[data-emoji]")) {
|
|
12873
|
+
const emoji = emojiImg.dataset.emoji;
|
|
12874
|
+
if (emoji === emojiImg.alt) {
|
|
12875
|
+
emojiImg.replaceWith(emoji);
|
|
12876
|
+
}
|
|
12877
|
+
}
|
|
12878
|
+
}
|
|
12628
12879
|
}
|
|
12629
12880
|
|
|
12630
12881
|
class Contents {
|
|
@@ -15384,6 +15635,39 @@ class CustomAttachmentDragAndDropExtension extends LexxyExtension {
|
|
|
15384
15635
|
}
|
|
15385
15636
|
}
|
|
15386
15637
|
|
|
15638
|
+
const LINE_SEPARATORS = /\r\n|[\n\r\u2028\u2029]/;
|
|
15639
|
+
|
|
15640
|
+
// A literal line separator inside a text node renders as a break in the editor, or not at all,
|
|
15641
|
+
// but never survives serialization to HTML. macOS text replacements deliver U+2028 LINE SEPARATOR,
|
|
15642
|
+
// and other insertions can carry "\n", so however the text arrives, a transform converts whatever
|
|
15643
|
+
// lands in a text node into line break nodes.
|
|
15644
|
+
class LineSeparatorsExtension extends LexxyExtension {
|
|
15645
|
+
get lexicalExtension() {
|
|
15646
|
+
return this.defineExtension({
|
|
15647
|
+
name: "lexxy/line-separators",
|
|
15648
|
+
register(editor) {
|
|
15649
|
+
return editor.registerNodeTransform(gr, $replaceLineSeparatorWithLineBreak)
|
|
15650
|
+
}
|
|
15651
|
+
})
|
|
15652
|
+
}
|
|
15653
|
+
}
|
|
15654
|
+
|
|
15655
|
+
// Replaces the first separator only: the split leaves dirty text nodes behind, so Lexical re-runs
|
|
15656
|
+
// the transform until none remain.
|
|
15657
|
+
function $replaceLineSeparatorWithLineBreak(textNode) {
|
|
15658
|
+
const match = textNode.getTextContent().match(LINE_SEPARATORS);
|
|
15659
|
+
|
|
15660
|
+
if (match) {
|
|
15661
|
+
let separatorNode;
|
|
15662
|
+
if (match.index === 0) {
|
|
15663
|
+
[ separatorNode ] = textNode.splitText(match[0].length);
|
|
15664
|
+
} else {
|
|
15665
|
+
[ , separatorNode ] = textNode.splitText(match.index, match.index + match[0].length);
|
|
15666
|
+
}
|
|
15667
|
+
separatorNode.replace(or());
|
|
15668
|
+
}
|
|
15669
|
+
}
|
|
15670
|
+
|
|
15387
15671
|
class LexicalEditorElement extends HTMLElement {
|
|
15388
15672
|
static formAssociated = true
|
|
15389
15673
|
static debug = false
|
|
@@ -15542,7 +15826,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15542
15826
|
FormatEscapeExtension,
|
|
15543
15827
|
LinkOpenerExtension,
|
|
15544
15828
|
PreventLexicalTripleClickExtension,
|
|
15545
|
-
CustomAttachmentDragAndDropExtension
|
|
15829
|
+
CustomAttachmentDragAndDropExtension,
|
|
15830
|
+
LineSeparatorsExtension
|
|
15546
15831
|
]
|
|
15547
15832
|
}
|
|
15548
15833
|
|
|
Binary file
|
|
Binary file
|