lexxy 0.9.27 → 0.9.28
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 +167 -0
- 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: afb455253a99563e4938f9f1634f997fdb3231f52e7b760e53cb590eb4873934
|
|
4
|
+
data.tar.gz: 8334d0fb26282b75e340e5d5c5eeb65000a876700038331d2f58347865ef6232
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46e58bf9d6993735988227cea4c883a8cb8f6e485626c8a7a28cce6e128b2d417faaef93ff404b0d0c64505263df7424ef909c91fe463576f9fe0087a823ef31
|
|
7
|
+
data.tar.gz: 9c5250b597e25d858378e6b219fab8b3c09b6e472f224fff879b22a7b02c85313b3c74a574142566aca9dcc645ceb0691793d30c8b86e1f1cc2c3d5f69382261
|
|
@@ -12500,6 +12500,149 @@ BaseNodeInserter.for = (selection) => {
|
|
|
12500
12500
|
return inserterClass ? new inserterClass(selection) : selection
|
|
12501
12501
|
};
|
|
12502
12502
|
|
|
12503
|
+
// Word and Outlook never emit <ul>/<ol>/<li>. They simulate lists with flat
|
|
12504
|
+
// paragraphs that declare their depth in an inline `mso-list:l0 level2 lfo1`
|
|
12505
|
+
// and carry the bullet or number glyph in a <span style="mso-list:Ignore">.
|
|
12506
|
+
// Nothing downstream understands either, so the items land as plain paragraphs
|
|
12507
|
+
// with the marker baked into the text. Rebuild real lists from the level each
|
|
12508
|
+
// paragraph declares.
|
|
12509
|
+
//
|
|
12510
|
+
// Everything this reads lives in inline attributes, so it stays correct after
|
|
12511
|
+
// the pasted content formatter has dropped Office's style sheet.
|
|
12512
|
+
class OfficeFormatter {
|
|
12513
|
+
constructor(doc) {
|
|
12514
|
+
this.doc = doc;
|
|
12515
|
+
}
|
|
12516
|
+
|
|
12517
|
+
format() {
|
|
12518
|
+
for (const run of this.#listRuns()) {
|
|
12519
|
+
this.#replaceWithLists(run);
|
|
12520
|
+
}
|
|
12521
|
+
}
|
|
12522
|
+
|
|
12523
|
+
// A run is a stretch of Office list paragraphs that are siblings with nothing
|
|
12524
|
+
// but whitespace and Word's conditional comments between them. Anything else
|
|
12525
|
+
// in between (a heading, a plain paragraph) ends one list and starts another.
|
|
12526
|
+
#listRuns() {
|
|
12527
|
+
const runs = [];
|
|
12528
|
+
const claimed = new Set();
|
|
12529
|
+
|
|
12530
|
+
for (const paragraph of this.doc.querySelectorAll("p[style*='mso-list']")) {
|
|
12531
|
+
if (claimed.has(paragraph) || this.#listLevel(paragraph) === null) continue
|
|
12532
|
+
|
|
12533
|
+
const run = [];
|
|
12534
|
+
let node = paragraph;
|
|
12535
|
+
while (node) {
|
|
12536
|
+
run.push(node);
|
|
12537
|
+
claimed.add(node);
|
|
12538
|
+
node = this.#nextListParagraph(node);
|
|
12539
|
+
}
|
|
12540
|
+
runs.push(run);
|
|
12541
|
+
}
|
|
12542
|
+
|
|
12543
|
+
return runs
|
|
12544
|
+
}
|
|
12545
|
+
|
|
12546
|
+
#nextListParagraph(paragraph) {
|
|
12547
|
+
let sibling = paragraph.nextSibling;
|
|
12548
|
+
while (sibling && this.#isIgnorableBetweenListParagraphs(sibling)) {
|
|
12549
|
+
sibling = sibling.nextSibling;
|
|
12550
|
+
}
|
|
12551
|
+
|
|
12552
|
+
if (sibling && sibling.nodeType === Node.ELEMENT_NODE && this.#listLevel(sibling) !== null) {
|
|
12553
|
+
return sibling
|
|
12554
|
+
}
|
|
12555
|
+
|
|
12556
|
+
return null
|
|
12557
|
+
}
|
|
12558
|
+
|
|
12559
|
+
#isIgnorableBetweenListParagraphs(node) {
|
|
12560
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
12561
|
+
return true
|
|
12562
|
+
}
|
|
12563
|
+
return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === ""
|
|
12564
|
+
}
|
|
12565
|
+
|
|
12566
|
+
// Word numbers levels from 1, but a copied selection often starts partway down
|
|
12567
|
+
// a list, so every paragraph declares level3 with no level1 above it. Anchoring
|
|
12568
|
+
// the run at its own shallowest level is what keeps the result from arriving
|
|
12569
|
+
// needlessly deep.
|
|
12570
|
+
#replaceWithLists(paragraphs) {
|
|
12571
|
+
const levels = paragraphs.map((paragraph) => this.#listLevel(paragraph));
|
|
12572
|
+
const baseLevel = Math.min(...levels);
|
|
12573
|
+
const roots = [];
|
|
12574
|
+
const stack = [];
|
|
12575
|
+
|
|
12576
|
+
paragraphs.forEach((paragraph, index) => {
|
|
12577
|
+
const depth = levels[index] - baseLevel + 1;
|
|
12578
|
+
const tagName = this.#listTagName(paragraph);
|
|
12579
|
+
|
|
12580
|
+
while (stack.length > depth) {
|
|
12581
|
+
stack.pop();
|
|
12582
|
+
}
|
|
12583
|
+
if (stack.length === depth && stack[depth - 1].tagName !== tagName.toUpperCase()) {
|
|
12584
|
+
stack.pop();
|
|
12585
|
+
}
|
|
12586
|
+
|
|
12587
|
+
while (stack.length < depth) {
|
|
12588
|
+
const list = this.doc.createElement(tagName);
|
|
12589
|
+
if (stack.length === 0) {
|
|
12590
|
+
roots.push(list);
|
|
12591
|
+
} else {
|
|
12592
|
+
this.#lastItemOf(stack[stack.length - 1]).appendChild(list);
|
|
12593
|
+
}
|
|
12594
|
+
stack.push(list);
|
|
12595
|
+
}
|
|
12596
|
+
|
|
12597
|
+
stack[stack.length - 1].appendChild(this.#createListItem(paragraph));
|
|
12598
|
+
});
|
|
12599
|
+
|
|
12600
|
+
paragraphs[0].replaceWith(...roots);
|
|
12601
|
+
for (const paragraph of paragraphs.slice(1)) {
|
|
12602
|
+
paragraph.remove();
|
|
12603
|
+
}
|
|
12604
|
+
}
|
|
12605
|
+
|
|
12606
|
+
#lastItemOf(list) {
|
|
12607
|
+
if (!list.lastElementChild) {
|
|
12608
|
+
list.appendChild(this.doc.createElement("li"));
|
|
12609
|
+
}
|
|
12610
|
+
return list.lastElementChild
|
|
12611
|
+
}
|
|
12612
|
+
|
|
12613
|
+
// Word writes bullets as the raw glyph of the marker font (Symbol's "·",
|
|
12614
|
+
// Wingdings' "§", Courier New's "o") and numbers as the rendered counter
|
|
12615
|
+
// ("1.", "a)", "iv)"). A bare letter with no delimiter after it is Courier
|
|
12616
|
+
// New's bullet, not a counter, so the delimiter is what tells them apart.
|
|
12617
|
+
#listTagName(paragraph) {
|
|
12618
|
+
const marker = paragraph.querySelector("[style*='mso-list:Ignore']");
|
|
12619
|
+
const text = marker?.textContent || "";
|
|
12620
|
+
|
|
12621
|
+
if (/\d/.test(text) || /^\(?[A-Za-z]+[.)]/.test(text)) {
|
|
12622
|
+
return "ol"
|
|
12623
|
+
}
|
|
12624
|
+
return "ul"
|
|
12625
|
+
}
|
|
12626
|
+
|
|
12627
|
+
#createListItem(paragraph) {
|
|
12628
|
+
for (const marker of paragraph.querySelectorAll("[style*='mso-list:Ignore']")) {
|
|
12629
|
+
marker.remove();
|
|
12630
|
+
}
|
|
12631
|
+
|
|
12632
|
+
const item = this.doc.createElement("li");
|
|
12633
|
+
item.append(...paragraph.childNodes);
|
|
12634
|
+
return item
|
|
12635
|
+
}
|
|
12636
|
+
|
|
12637
|
+
#listLevel(element) {
|
|
12638
|
+
const match = /mso-list:[^;"']*\blevel(\d+)/.exec(element.getAttribute("style") || "");
|
|
12639
|
+
if (match) {
|
|
12640
|
+
return parseInt(match[1], 10)
|
|
12641
|
+
}
|
|
12642
|
+
return null
|
|
12643
|
+
}
|
|
12644
|
+
}
|
|
12645
|
+
|
|
12503
12646
|
class PastedContentFormatter {
|
|
12504
12647
|
constructor(doc) {
|
|
12505
12648
|
this.doc = doc;
|
|
@@ -12509,6 +12652,7 @@ class PastedContentFormatter {
|
|
|
12509
12652
|
this.#stripStyleElements();
|
|
12510
12653
|
this.#unwrapPlaceholderAnchors();
|
|
12511
12654
|
this.#stripTableCellColorStyles();
|
|
12655
|
+
new OfficeFormatter(this.doc).format();
|
|
12512
12656
|
this.#unwrapWrappedListChildren();
|
|
12513
12657
|
this.#nestStrayListChildren();
|
|
12514
12658
|
this.#stripStrayListChildren();
|
|
@@ -12667,6 +12811,20 @@ class Contents {
|
|
|
12667
12811
|
}, { tag });
|
|
12668
12812
|
}
|
|
12669
12813
|
|
|
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
|
+
|
|
12670
12828
|
insertAtCursor(...nodes) {
|
|
12671
12829
|
const selection = this.#insertableSelection();
|
|
12672
12830
|
const inserter = BaseNodeInserter.for(selection);
|
|
@@ -15730,6 +15888,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15730
15888
|
#initialize() {
|
|
15731
15889
|
this.#registerComponents();
|
|
15732
15890
|
this.#handleEnter();
|
|
15891
|
+
this.#handleReplacementText();
|
|
15733
15892
|
this.#registerFocusEvents();
|
|
15734
15893
|
this.#registerHistoryEvents();
|
|
15735
15894
|
this.#registerFileAcceptFilter();
|
|
@@ -15993,6 +16152,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15993
16152
|
));
|
|
15994
16153
|
}
|
|
15995
16154
|
|
|
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
|
+
|
|
15996
16163
|
#registerFocusEvents() {
|
|
15997
16164
|
this.#listeners.track(
|
|
15998
16165
|
registerEventListener(this, "focusin", this.#handleFocusIn),
|
|
Binary file
|
|
Binary file
|