lexxy 0.9.26 → 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 +192 -1
- 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
|
|
@@ -11085,10 +11085,34 @@ class Selection {
|
|
|
11085
11085
|
|
|
11086
11086
|
this.editor.registerCommand(ie$4, () => {
|
|
11087
11087
|
this.#syncSelectedClasses();
|
|
11088
|
-
}, io)
|
|
11088
|
+
}, io),
|
|
11089
|
+
|
|
11090
|
+
this.editor.registerCommand(ie$4, () => {
|
|
11091
|
+
this.#preserveFocusOfOtherFields();
|
|
11092
|
+
return false
|
|
11093
|
+
}, lo)
|
|
11089
11094
|
);
|
|
11090
11095
|
}
|
|
11091
11096
|
|
|
11097
|
+
// Firefox (152+) keeps window.getSelection() anchored inside the editor after focus
|
|
11098
|
+
// moves to another field, and fires document selectionchange for every keystroke
|
|
11099
|
+
// typed there. Lexical reads that stale selection, reconciles, and pulls focus back
|
|
11100
|
+
// into the editor — stealing it mid-typing. When another text-entry field owns focus,
|
|
11101
|
+
// tag the update so Lexical leaves the DOM selection (and with it, focus) alone.
|
|
11102
|
+
#preserveFocusOfOtherFields() {
|
|
11103
|
+
if (this.#anotherFieldIsFocused) {
|
|
11104
|
+
Os(qn);
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
get #anotherFieldIsFocused() {
|
|
11109
|
+
const rootElement = this.editor.getRootElement();
|
|
11110
|
+
const activeElement = rootElement?.ownerDocument.activeElement;
|
|
11111
|
+
|
|
11112
|
+
return activeElement && !rootElement.contains(activeElement) &&
|
|
11113
|
+
(activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement.isContentEditable)
|
|
11114
|
+
}
|
|
11115
|
+
|
|
11092
11116
|
#listenForNodeSelections() {
|
|
11093
11117
|
this.#listeners.track(this.editor.registerCommand(se$5, ({ target }) => {
|
|
11094
11118
|
if (!qs(target)) return false
|
|
@@ -12476,6 +12500,149 @@ BaseNodeInserter.for = (selection) => {
|
|
|
12476
12500
|
return inserterClass ? new inserterClass(selection) : selection
|
|
12477
12501
|
};
|
|
12478
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
|
+
|
|
12479
12646
|
class PastedContentFormatter {
|
|
12480
12647
|
constructor(doc) {
|
|
12481
12648
|
this.doc = doc;
|
|
@@ -12485,6 +12652,7 @@ class PastedContentFormatter {
|
|
|
12485
12652
|
this.#stripStyleElements();
|
|
12486
12653
|
this.#unwrapPlaceholderAnchors();
|
|
12487
12654
|
this.#stripTableCellColorStyles();
|
|
12655
|
+
new OfficeFormatter(this.doc).format();
|
|
12488
12656
|
this.#unwrapWrappedListChildren();
|
|
12489
12657
|
this.#nestStrayListChildren();
|
|
12490
12658
|
this.#stripStrayListChildren();
|
|
@@ -12643,6 +12811,20 @@ class Contents {
|
|
|
12643
12811
|
}, { tag });
|
|
12644
12812
|
}
|
|
12645
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
|
+
|
|
12646
12828
|
insertAtCursor(...nodes) {
|
|
12647
12829
|
const selection = this.#insertableSelection();
|
|
12648
12830
|
const inserter = BaseNodeInserter.for(selection);
|
|
@@ -15706,6 +15888,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15706
15888
|
#initialize() {
|
|
15707
15889
|
this.#registerComponents();
|
|
15708
15890
|
this.#handleEnter();
|
|
15891
|
+
this.#handleReplacementText();
|
|
15709
15892
|
this.#registerFocusEvents();
|
|
15710
15893
|
this.#registerHistoryEvents();
|
|
15711
15894
|
this.#registerFileAcceptFilter();
|
|
@@ -15969,6 +16152,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15969
16152
|
));
|
|
15970
16153
|
}
|
|
15971
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
|
+
|
|
15972
16163
|
#registerFocusEvents() {
|
|
15973
16164
|
this.#listeners.track(
|
|
15974
16165
|
registerEventListener(this, "focusin", this.#handleFocusIn),
|
|
Binary file
|
|
Binary file
|