lexxy 0.9.24 → 0.9.26
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/README.md +1 -0
- data/app/assets/javascript/lexxy.js +120 -14
- 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/app/assets/stylesheets/lexxy-editor.css +10 -2
- 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: f5c63b2cb8499843f516d8404a2a7573f93452f753b54672b53d80a60242d0ef
|
|
4
|
+
data.tar.gz: 13d59b10d606353cbc379ea5e940b40647fbe17a497f11a565c633ca1ae51519
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e0b6c3b8763c773fbc0ebe6a4d17cc5c8ee00813c48e66151975640daf3866b9a9558128584812aba21de840982ff0273200158b529343fd98b0992641622b8
|
|
7
|
+
data.tar.gz: 72db851395aea2ad408a62dd78afd2e0cea0843b1484f39a19755f58d6a8a65c482c029bb876d5f109018820bee4b8141365976b35508bfd83adc0c9754dd543
|
data/README.md
CHANGED
|
@@ -21,6 +21,7 @@ A modern rich text editor for Rails.
|
|
|
21
21
|
|
|
22
22
|
- Bug reports and pull requests are welcome on [GitHub Issues](https://github.com/basecamp/lexxy/issues). Help is especially welcome with [those tagged as "Help Wanted"](https://github.com/basecamp/lexxy/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).
|
|
23
23
|
- For questions and general Lexxy discussion, please use the [Discussions section](https://github.com/basecamp/lexxy/discussions)
|
|
24
|
+
- To build with a development version of Lexical, use `bin/link-to-local-lexical`.
|
|
24
25
|
|
|
25
26
|
## License
|
|
26
27
|
|
|
@@ -7473,9 +7473,9 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
7473
7473
|
const HEADING_BUTTON_SELECTOR = "button.lexxy-heading-button";
|
|
7474
7474
|
|
|
7475
7475
|
const HEADING_PRESETS = [
|
|
7476
|
-
{ label: "Large Heading", name: "heading-large" },
|
|
7477
|
-
{ label: "Medium Heading", name: "heading-medium" },
|
|
7478
|
-
{ label: "Small Heading", name: "heading-small" }
|
|
7476
|
+
{ label: "Large Heading", name: "heading-large", command: "setFormatHeadingLarge" },
|
|
7477
|
+
{ label: "Medium Heading", name: "heading-medium", command: "setFormatHeadingMedium" },
|
|
7478
|
+
{ label: "Small Heading", name: "heading-small", command: "setFormatHeadingSmall" }
|
|
7479
7479
|
];
|
|
7480
7480
|
|
|
7481
7481
|
class HeadingDropdown extends HTMLElement {
|
|
@@ -7505,6 +7505,14 @@ class HeadingDropdown extends HTMLElement {
|
|
|
7505
7505
|
}
|
|
7506
7506
|
}
|
|
7507
7507
|
|
|
7508
|
+
static commandFor(index) {
|
|
7509
|
+
if (index < HEADING_PRESETS.length) {
|
|
7510
|
+
return HEADING_PRESETS[index].command
|
|
7511
|
+
} else {
|
|
7512
|
+
return "applyHeadingFormat"
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7508
7516
|
#listeners = new ListenerBin()
|
|
7509
7517
|
|
|
7510
7518
|
connectedCallback() {
|
|
@@ -7567,6 +7575,7 @@ class HeadingDropdown extends HTMLElement {
|
|
|
7567
7575
|
const button = document.createElement("button");
|
|
7568
7576
|
button.type = "button";
|
|
7569
7577
|
button.dataset.heading = tag;
|
|
7578
|
+
button.dataset.command = HeadingDropdown.commandFor(index);
|
|
7570
7579
|
button.classList.add("lexxy-heading-button");
|
|
7571
7580
|
button.name = name;
|
|
7572
7581
|
button.title = label;
|
|
@@ -7581,7 +7590,7 @@ class HeadingDropdown extends HTMLElement {
|
|
|
7581
7590
|
if (!this.#editor) return
|
|
7582
7591
|
|
|
7583
7592
|
event.preventDefault();
|
|
7584
|
-
this.#editor.dispatchCommand(
|
|
7593
|
+
this.#editor.dispatchCommand(button.dataset.command, button.dataset.heading);
|
|
7585
7594
|
this.#editor.focus();
|
|
7586
7595
|
}));
|
|
7587
7596
|
});
|
|
@@ -8450,14 +8459,29 @@ function $shrinkSelectionPastBlockEdges(selection) {
|
|
|
8450
8459
|
if (!anchorBlock || !focusBlock || anchorBlock.is(focusBlock)) return
|
|
8451
8460
|
|
|
8452
8461
|
if ($isAtBlockEnd(selection.anchor, anchorBlock)) {
|
|
8453
|
-
const nextBlock = anchorBlock
|
|
8462
|
+
const nextBlock = $nearestElementSibling(anchorBlock, "next");
|
|
8454
8463
|
if (nextBlock) selection.anchor.set(nextBlock.getKey(), 0, "element");
|
|
8455
8464
|
}
|
|
8456
8465
|
|
|
8457
8466
|
if ($isAtBlockStart(selection.focus, focusBlock)) {
|
|
8458
|
-
const previousBlock = focusBlock
|
|
8467
|
+
const previousBlock = $nearestElementSibling(focusBlock, "previous");
|
|
8459
8468
|
if (previousBlock) selection.focus.set(previousBlock.getKey(), previousBlock.getChildrenSize(), "element");
|
|
8460
8469
|
}
|
|
8470
|
+
|
|
8471
|
+
// Shrinking moves the endpoints toward each other, so they can cross when
|
|
8472
|
+
// both were flush against block edges; callers assume a forward selection.
|
|
8473
|
+
j$6(selection);
|
|
8474
|
+
}
|
|
8475
|
+
|
|
8476
|
+
// Top-level decorator blocks (attachments, horizontal dividers) can't hold a
|
|
8477
|
+
// selection point, so walk past them to the nearest element sibling. Returns
|
|
8478
|
+
// null when no element sibling exists in that direction.
|
|
8479
|
+
function $nearestElementSibling(block, direction) {
|
|
8480
|
+
let sibling = block;
|
|
8481
|
+
do {
|
|
8482
|
+
sibling = direction === "next" ? sibling.getNextSibling() : sibling.getPreviousSibling();
|
|
8483
|
+
} while (sibling && !Wi(sibling))
|
|
8484
|
+
return sibling
|
|
8461
8485
|
}
|
|
8462
8486
|
|
|
8463
8487
|
function $isAtBlockEnd(point, block) {
|
|
@@ -10309,6 +10333,9 @@ const COMMANDS = [
|
|
|
10309
10333
|
"unlink",
|
|
10310
10334
|
"toggleHighlight",
|
|
10311
10335
|
"removeHighlight",
|
|
10336
|
+
"setFormatHeadingLarge",
|
|
10337
|
+
"setFormatHeadingMedium",
|
|
10338
|
+
"setFormatHeadingSmall",
|
|
10312
10339
|
"setFormatParagraph",
|
|
10313
10340
|
"applyHeadingFormat",
|
|
10314
10341
|
"clearFormatting",
|
|
@@ -10502,6 +10529,18 @@ class CommandDispatcher {
|
|
|
10502
10529
|
Pt$3(new HorizontalDividerNode);
|
|
10503
10530
|
}
|
|
10504
10531
|
|
|
10532
|
+
dispatchSetFormatHeadingLarge() {
|
|
10533
|
+
this.#applyConfiguredHeadingFormat(0);
|
|
10534
|
+
}
|
|
10535
|
+
|
|
10536
|
+
dispatchSetFormatHeadingMedium() {
|
|
10537
|
+
this.#applyConfiguredHeadingFormat(1);
|
|
10538
|
+
}
|
|
10539
|
+
|
|
10540
|
+
dispatchSetFormatHeadingSmall() {
|
|
10541
|
+
this.#applyConfiguredHeadingFormat(2);
|
|
10542
|
+
}
|
|
10543
|
+
|
|
10505
10544
|
dispatchSetFormatParagraph() {
|
|
10506
10545
|
this.contents.applyParagraphFormat();
|
|
10507
10546
|
}
|
|
@@ -10558,6 +10597,11 @@ class CommandDispatcher {
|
|
|
10558
10597
|
this.#listeners.dispose();
|
|
10559
10598
|
}
|
|
10560
10599
|
|
|
10600
|
+
#applyConfiguredHeadingFormat(index) {
|
|
10601
|
+
const tag = this.editorElement.config.get("headings")[index];
|
|
10602
|
+
if (tag) this.contents.applyHeadingFormat(tag);
|
|
10603
|
+
}
|
|
10604
|
+
|
|
10561
10605
|
#registerCommands() {
|
|
10562
10606
|
for (const command of COMMANDS) {
|
|
10563
10607
|
const methodName = `dispatch${capitalize(command)}`;
|
|
@@ -12128,11 +12172,11 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
12128
12172
|
}
|
|
12129
12173
|
|
|
12130
12174
|
#forgetUploadRequest() {
|
|
12131
|
-
this.#editorElement
|
|
12175
|
+
this.#editorElement?.uploadRequests.forget(this.getKey());
|
|
12132
12176
|
}
|
|
12133
12177
|
|
|
12134
12178
|
#rememberUploadRequest(request) {
|
|
12135
|
-
this.#editorElement
|
|
12179
|
+
this.#editorElement?.uploadRequests.track(this.getKey(), request);
|
|
12136
12180
|
}
|
|
12137
12181
|
|
|
12138
12182
|
get #editorElement() {
|
|
@@ -12438,13 +12482,27 @@ class PastedContentFormatter {
|
|
|
12438
12482
|
}
|
|
12439
12483
|
|
|
12440
12484
|
format() {
|
|
12485
|
+
this.#stripStyleElements();
|
|
12441
12486
|
this.#unwrapPlaceholderAnchors();
|
|
12442
12487
|
this.#stripTableCellColorStyles();
|
|
12488
|
+
this.#unwrapWrappedListChildren();
|
|
12443
12489
|
this.#nestStrayListChildren();
|
|
12444
12490
|
this.#stripStrayListChildren();
|
|
12445
12491
|
return this.doc
|
|
12446
12492
|
}
|
|
12447
12493
|
|
|
12494
|
+
// Spreadsheets (e.g. Excel) copy a <style> block whose rules (td { color:
|
|
12495
|
+
// black }, .xlNN { ... }) cascade onto the imported text. That color rides
|
|
12496
|
+
// in through the cascade rather than an inline style, so it slips past both
|
|
12497
|
+
// the cell-level stripping below and the paste style canonicalizer, leaving
|
|
12498
|
+
// pasted tables with foreign text colors that don't adapt to the theme. Drop
|
|
12499
|
+
// foreign style sheets so nothing cascades into imported content.
|
|
12500
|
+
#stripStyleElements() {
|
|
12501
|
+
for (const style of this.doc.querySelectorAll("style")) {
|
|
12502
|
+
style.remove();
|
|
12503
|
+
}
|
|
12504
|
+
}
|
|
12505
|
+
|
|
12448
12506
|
// Anchors with non-meaningful hrefs (e.g. "#", "") appear in content copied
|
|
12449
12507
|
// from rendered views where mentions and interactive elements are wrapped in
|
|
12450
12508
|
// <a href="#"> tags. Unwrap them so their text content pastes as plain text
|
|
@@ -12469,6 +12527,29 @@ class PastedContentFormatter {
|
|
|
12469
12527
|
}
|
|
12470
12528
|
}
|
|
12471
12529
|
|
|
12530
|
+
// Some sources wrap runs of <li>s in a stray element (e.g. a <div> directly
|
|
12531
|
+
// inside the list). Dissolve such wrappers so their items become direct
|
|
12532
|
+
// list children and any nested list they hide becomes visible to
|
|
12533
|
+
// #nestStrayListChildren below.
|
|
12534
|
+
#unwrapWrappedListChildren() {
|
|
12535
|
+
for (const list of this.doc.querySelectorAll("ol, ul")) {
|
|
12536
|
+
let wrapper = this.#wrappedListChild(list);
|
|
12537
|
+
while (wrapper) {
|
|
12538
|
+
wrapper.replaceWith(...wrapper.childNodes);
|
|
12539
|
+
wrapper = this.#wrappedListChild(list);
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
}
|
|
12543
|
+
|
|
12544
|
+
#wrappedListChild(list) {
|
|
12545
|
+
for (const child of list.children) {
|
|
12546
|
+
if (child.tagName !== "LI" && !this.#isNestedList(child) && this.#containsListItems(child)) {
|
|
12547
|
+
return child
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
return null
|
|
12551
|
+
}
|
|
12552
|
+
|
|
12472
12553
|
// Some sources (e.g. Gmail) nest a sublist as a direct child of the parent
|
|
12473
12554
|
// <ol>/<ul> instead of inside a <li>. Move each nested list into its
|
|
12474
12555
|
// preceding <li> so the import preserves the nesting instead of dropping it.
|
|
@@ -12485,15 +12566,40 @@ class PastedContentFormatter {
|
|
|
12485
12566
|
}
|
|
12486
12567
|
}
|
|
12487
12568
|
|
|
12488
|
-
// Only <li> is a valid child of a list
|
|
12489
|
-
//
|
|
12569
|
+
// Only <li> is a valid child of a list. Unwrap remaining stray children
|
|
12570
|
+
// that still hold list items (a nested list with no preceding <li> to nest
|
|
12571
|
+
// under) so the items survive, and drop stray <br>/whitespace so the import
|
|
12572
|
+
// doesn't wrap them into an empty leading item.
|
|
12490
12573
|
#stripStrayListChildren() {
|
|
12491
12574
|
for (const list of this.doc.querySelectorAll("ol, ul")) {
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12575
|
+
let stray = this.#firstStrayListChild(list);
|
|
12576
|
+
while (stray) {
|
|
12577
|
+
if (this.#containsListItems(stray)) {
|
|
12578
|
+
stray.replaceWith(...stray.childNodes);
|
|
12579
|
+
} else {
|
|
12580
|
+
stray.remove();
|
|
12581
|
+
}
|
|
12582
|
+
|
|
12583
|
+
stray = this.#firstStrayListChild(list);
|
|
12584
|
+
}
|
|
12585
|
+
}
|
|
12586
|
+
}
|
|
12587
|
+
|
|
12588
|
+
#firstStrayListChild(list) {
|
|
12589
|
+
for (const child of list.childNodes) {
|
|
12590
|
+
if (child.nodeType !== Node.ELEMENT_NODE || child.tagName !== "LI") {
|
|
12591
|
+
return child
|
|
12495
12592
|
}
|
|
12496
12593
|
}
|
|
12594
|
+
return null
|
|
12595
|
+
}
|
|
12596
|
+
|
|
12597
|
+
#isNestedList(node) {
|
|
12598
|
+
return node.nodeType === Node.ELEMENT_NODE && (node.tagName === "OL" || node.tagName === "UL")
|
|
12599
|
+
}
|
|
12600
|
+
|
|
12601
|
+
#containsListItems(node) {
|
|
12602
|
+
return node.nodeType === Node.ELEMENT_NODE && node.querySelector("li") !== null
|
|
12497
12603
|
}
|
|
12498
12604
|
}
|
|
12499
12605
|
|
|
@@ -16046,7 +16152,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
16046
16152
|
{ label: "Normal", command: "setFormatParagraph", tag: null },
|
|
16047
16153
|
...headings.map((tag, index) => ({
|
|
16048
16154
|
label: HeadingDropdown.labelFor(tag, index),
|
|
16049
|
-
command:
|
|
16155
|
+
command: HeadingDropdown.commandFor(index),
|
|
16050
16156
|
tag
|
|
16051
16157
|
}))
|
|
16052
16158
|
]
|
|
Binary file
|
|
Binary file
|