lexxy 0.9.19.alpha.1 → 0.9.19.alpha.2
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 +313 -86
- 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 +2 -2
- 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: 3f4e64b061d290f12430a666006c5845f242f1776474b8b8691c53e83a7a5674
|
|
4
|
+
data.tar.gz: 8f539bac381f8273f97d18c9d40093c917272d89e74a36ef3d8dee9888ab149d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2245d2ea20bae119bda7464502ef6c83390f08425ea998e9e2e4a84b4ac20d389fc967ae4d4aab1d821d462050c3358cf617d3170cf11a4fcad526d368d59453
|
|
7
|
+
data.tar.gz: db3cef1b72fcc42d00f66160292ce2f13b2c1b82a57bbe44fa53a7100a31e65143a52c68674ada67d20901be052c26a037516b5a13b72994673e1391d8c24883
|
|
@@ -7810,8 +7810,12 @@ function $isSafeForRoot(node) {
|
|
|
7810
7810
|
function $makeSafeForRoot(node) {
|
|
7811
7811
|
if ($isSafeForRoot(node)) {
|
|
7812
7812
|
return node
|
|
7813
|
-
} else {
|
|
7813
|
+
} else if (node.getParent()) {
|
|
7814
7814
|
return Tt$4(node, () => node.createParentElementNode())
|
|
7815
|
+
} else {
|
|
7816
|
+
// Detached nodes (e.g. clipboard nodes being inserted) can't be `replace`d in place,
|
|
7817
|
+
// so append them into a fresh required parent instead.
|
|
7818
|
+
return node.createParentElementNode().append(node)
|
|
7815
7819
|
}
|
|
7816
7820
|
}
|
|
7817
7821
|
|
|
@@ -7999,7 +8003,7 @@ function $splitSelectedParagraphsAtInnerLineBreaks(selection) {
|
|
|
7999
8003
|
}
|
|
8000
8004
|
}
|
|
8001
8005
|
|
|
8002
|
-
function $expandSelectionToLineBreaksAndSplitAtEdges(selection) {
|
|
8006
|
+
function $expandSelectionToLineBreaksAndSplitAtEdges(selection, fallbackAncestor = (node) => node.getTopLevelElement()) {
|
|
8003
8007
|
j$6(selection);
|
|
8004
8008
|
|
|
8005
8009
|
const focusCaret = ql(selection.focus, "next");
|
|
@@ -8019,8 +8023,8 @@ function $expandSelectionToLineBreaksAndSplitAtEdges(selection) {
|
|
|
8019
8023
|
const focusOuter = focusBrCaret && $splitAroundLineBreak(focusBrCaret);
|
|
8020
8024
|
const anchorOuter = anchorBrCaret && $splitAroundLineBreak(anchorBrCaret);
|
|
8021
8025
|
|
|
8022
|
-
const innerStart = anchorOuter?.getNextSibling() ?? selection.anchor.getNode()
|
|
8023
|
-
const innerEnd = focusOuter?.getPreviousSibling() ?? selection.focus.getNode()
|
|
8026
|
+
const innerStart = anchorOuter?.getNextSibling() ?? fallbackAncestor(selection.anchor.getNode());
|
|
8027
|
+
const innerEnd = focusOuter?.getPreviousSibling() ?? fallbackAncestor(selection.focus.getNode());
|
|
8024
8028
|
if (!innerStart || !innerEnd) return
|
|
8025
8029
|
|
|
8026
8030
|
Gl(Wl(
|
|
@@ -8126,6 +8130,49 @@ function $splitAroundLineBreak(lineBreakCaret) {
|
|
|
8126
8130
|
return outer
|
|
8127
8131
|
}
|
|
8128
8132
|
|
|
8133
|
+
// Lexical's RangeSelection.insertNodes/insertLineBreak require every selection point to have a
|
|
8134
|
+
// block ancestor with inline children. An element point on a container of block nodes — e.g. a
|
|
8135
|
+
// quote holding paragraphs — has none, so Lexical throws invariant #211 or #212. This detects
|
|
8136
|
+
// such a point so callers can descend it to a leaf before inserting.
|
|
8137
|
+
function $isPointOnBlockContainer(point) {
|
|
8138
|
+
if (point.type !== "element") return false
|
|
8139
|
+
|
|
8140
|
+
const firstChild = point.getNode().getFirstChild();
|
|
8141
|
+
return (Wi(firstChild) || ji(firstChild)) && !firstChild.isInline()
|
|
8142
|
+
}
|
|
8143
|
+
|
|
8144
|
+
function $hasPointOnBlockContainer(selection) {
|
|
8145
|
+
return Fr(selection) &&
|
|
8146
|
+
[ selection.anchor, selection.focus ].some($isPointOnBlockContainer)
|
|
8147
|
+
}
|
|
8148
|
+
|
|
8149
|
+
// Descend any block-container element point in the selection to a leaf position, so a subsequent
|
|
8150
|
+
// Lexical insert (insertNodes, insertLineBreak, INSERT_PARAGRAPH) doesn't throw invariant #211/#212.
|
|
8151
|
+
function $normalizeBlockContainerSelection(selection = Qr()) {
|
|
8152
|
+
if (!$hasPointOnBlockContainer(selection)) return false
|
|
8153
|
+
|
|
8154
|
+
St$3(selection);
|
|
8155
|
+
return true
|
|
8156
|
+
}
|
|
8157
|
+
|
|
8158
|
+
function $consecutiveSiblingGroups(blocks) {
|
|
8159
|
+
const ordered = [ ...blocks ].sort((a, b) => a.getIndexWithinParent() - b.getIndexWithinParent());
|
|
8160
|
+
const groups = [];
|
|
8161
|
+
|
|
8162
|
+
for (const block of ordered) {
|
|
8163
|
+
const lastGroup = groups.at(-1);
|
|
8164
|
+
const previous = lastGroup?.at(-1);
|
|
8165
|
+
|
|
8166
|
+
if (previous && previous.getParent().is(block.getParent()) && previous.getNextSibling()?.is(block)) {
|
|
8167
|
+
lastGroup.push(block);
|
|
8168
|
+
} else {
|
|
8169
|
+
groups.push([ block ]);
|
|
8170
|
+
}
|
|
8171
|
+
}
|
|
8172
|
+
|
|
8173
|
+
return groups
|
|
8174
|
+
}
|
|
8175
|
+
|
|
8129
8176
|
// Payload: Record<nodeKey, { patch?, replace? }>
|
|
8130
8177
|
// - patch: plain object, shallow-merged into the existing node's properties
|
|
8131
8178
|
// - replace: a LexicalNode instance that replaces the node
|
|
@@ -10067,6 +10114,16 @@ class CommandDispatcher {
|
|
|
10067
10114
|
#registerKeyboardCommands() {
|
|
10068
10115
|
this.#registerCommandHandler(ke$2, oo, this.#handleArrowRightKey.bind(this));
|
|
10069
10116
|
this.#registerCommandHandler(Fe$2, oo, this.#handleTabKey.bind(this));
|
|
10117
|
+
|
|
10118
|
+
// Run before Lexical's built-in insert handlers to descend an element point on a
|
|
10119
|
+
// block container to a leaf, avoiding error #211 on Enter / Shift+Enter in a quote.
|
|
10120
|
+
this.#registerCommandHandler(de$4, so, this.#normalizeBlockContainerSelection.bind(this));
|
|
10121
|
+
this.#registerCommandHandler(he$3, so, this.#normalizeBlockContainerSelection.bind(this));
|
|
10122
|
+
}
|
|
10123
|
+
|
|
10124
|
+
#normalizeBlockContainerSelection() {
|
|
10125
|
+
$normalizeBlockContainerSelection();
|
|
10126
|
+
return false
|
|
10070
10127
|
}
|
|
10071
10128
|
|
|
10072
10129
|
#handleArrowRightKey(event) {
|
|
@@ -10339,6 +10396,10 @@ class Selection {
|
|
|
10339
10396
|
return this.nearestNodeOfType(ge$2)
|
|
10340
10397
|
}
|
|
10341
10398
|
|
|
10399
|
+
get isInsideBlockQuote() {
|
|
10400
|
+
return this.nearestNodeOfType(At$1)
|
|
10401
|
+
}
|
|
10402
|
+
|
|
10342
10403
|
get isIndentedList() {
|
|
10343
10404
|
const closestListNode = this.nearestNodeOfType(me$2);
|
|
10344
10405
|
return closestListNode && (G$7(closestListNode) > 1)
|
|
@@ -10766,6 +10827,9 @@ class Selection {
|
|
|
10766
10827
|
// - First item (no previous sibling): convert to a paragraph above the
|
|
10767
10828
|
// list, matching the standard "unwrap list formatting" behavior that
|
|
10768
10829
|
// users expect from pressing backspace at the start of a list item.
|
|
10830
|
+
// Inside a blockquote we instead just remove the empty item and move
|
|
10831
|
+
// the cursor into the next one — stranding a paragraph there would
|
|
10832
|
+
// leave the blank line the user is trying to close.
|
|
10769
10833
|
//
|
|
10770
10834
|
// When the empty item is the last/only one in the list, we return false
|
|
10771
10835
|
// and let Lexical's default (convert to paragraph) provide the standard
|
|
@@ -10784,19 +10848,22 @@ class Selection {
|
|
|
10784
10848
|
if (!nextSibling) return false
|
|
10785
10849
|
|
|
10786
10850
|
const previousSibling = listItem.getPreviousSibling();
|
|
10851
|
+
const listNode = At$2(listItem, me$2);
|
|
10852
|
+
if (!listNode) return false
|
|
10853
|
+
|
|
10787
10854
|
if (previousSibling) {
|
|
10788
10855
|
previousSibling.selectEnd();
|
|
10789
10856
|
listItem.remove();
|
|
10790
|
-
|
|
10857
|
+
} else if (Mt$2(listNode.getParent())) {
|
|
10858
|
+
nextSibling.selectStart();
|
|
10859
|
+
listItem.remove();
|
|
10860
|
+
} else {
|
|
10861
|
+
const paragraph = eo();
|
|
10862
|
+
listNode.insertBefore(paragraph);
|
|
10863
|
+
listItem.remove();
|
|
10864
|
+
paragraph.selectStart();
|
|
10791
10865
|
}
|
|
10792
10866
|
|
|
10793
|
-
const listNode = At$2(listItem, me$2);
|
|
10794
|
-
if (!listNode) return false
|
|
10795
|
-
|
|
10796
|
-
const paragraph = eo();
|
|
10797
|
-
listNode.insertBefore(paragraph);
|
|
10798
|
-
listItem.remove();
|
|
10799
|
-
paragraph.selectStart();
|
|
10800
10867
|
return true
|
|
10801
10868
|
}
|
|
10802
10869
|
|
|
@@ -11672,24 +11739,13 @@ function $createActionTextAttachmentUploadNode(...args) {
|
|
|
11672
11739
|
return new ActionTextAttachmentUploadNode(...args)
|
|
11673
11740
|
}
|
|
11674
11741
|
|
|
11675
|
-
class
|
|
11676
|
-
static for(selection) {
|
|
11677
|
-
const INSERTERS = [
|
|
11678
|
-
CodeNodeInserter,
|
|
11679
|
-
ShadowRootNodeInserter,
|
|
11680
|
-
NodeSelectionNodeInserter,
|
|
11681
|
-
BlockContainerNodeInserter
|
|
11682
|
-
];
|
|
11683
|
-
const Inserter = INSERTERS.find(inserter => inserter.handles(selection));
|
|
11684
|
-
return Inserter ? new Inserter(selection) : selection
|
|
11685
|
-
}
|
|
11686
|
-
|
|
11742
|
+
class BaseNodeInserter {
|
|
11687
11743
|
constructor(selection) {
|
|
11688
11744
|
this.selection = selection;
|
|
11689
11745
|
}
|
|
11690
11746
|
}
|
|
11691
11747
|
|
|
11692
|
-
class CodeNodeInserter extends
|
|
11748
|
+
class CodeNodeInserter extends BaseNodeInserter {
|
|
11693
11749
|
static handles(selection) {
|
|
11694
11750
|
return At$2(selection.anchor?.getNode(), z$1)
|
|
11695
11751
|
}
|
|
@@ -11748,10 +11804,9 @@ class CodeNodeInserter extends NodeInserter {
|
|
|
11748
11804
|
return kr(node.getTextContent())
|
|
11749
11805
|
}
|
|
11750
11806
|
}
|
|
11751
|
-
|
|
11752
11807
|
}
|
|
11753
11808
|
|
|
11754
|
-
class ShadowRootNodeInserter extends
|
|
11809
|
+
class ShadowRootNodeInserter extends BaseNodeInserter {
|
|
11755
11810
|
static handles(selection) {
|
|
11756
11811
|
return $isShadowRoot(selection?.anchor?.getNode())
|
|
11757
11812
|
}
|
|
@@ -11765,31 +11820,106 @@ class ShadowRootNodeInserter extends NodeInserter {
|
|
|
11765
11820
|
}
|
|
11766
11821
|
}
|
|
11767
11822
|
|
|
11768
|
-
class NodeSelectionNodeInserter extends
|
|
11823
|
+
class NodeSelectionNodeInserter extends BaseNodeInserter {
|
|
11769
11824
|
static handles(selection) {
|
|
11770
11825
|
return Ir(selection)
|
|
11771
11826
|
}
|
|
11772
11827
|
|
|
11773
11828
|
insertNodes(nodes) {
|
|
11774
|
-
const selectedNodes = this.selection.getNodes();
|
|
11775
|
-
|
|
11776
11829
|
// Overrides Lexical's default behavior of _removing_ the currently selected nodes
|
|
11777
11830
|
// https://github.com/facebook/lexical/blob/v0.38.2/packages/lexical/src/LexicalSelection.ts#L412
|
|
11778
|
-
let lastNode =
|
|
11831
|
+
let lastNode = this.selection.getNodes().at(-1);
|
|
11832
|
+
|
|
11779
11833
|
for (const node of nodes) {
|
|
11780
|
-
|
|
11834
|
+
// Inserting after a top-level node would make this one a root child. Inline nodes
|
|
11835
|
+
// can't live there (Lexical error #99), so wrap them in their required parent first.
|
|
11836
|
+
const nodeToInsert = this.#insertsIntoRoot(lastNode) ? $makeSafeForRoot(node) : node;
|
|
11837
|
+
lastNode = lastNode.insertAfter(nodeToInsert);
|
|
11781
11838
|
}
|
|
11782
11839
|
}
|
|
11840
|
+
|
|
11841
|
+
#insertsIntoRoot(node) {
|
|
11842
|
+
return node.is(node.getTopLevelElement())
|
|
11843
|
+
}
|
|
11844
|
+
}
|
|
11845
|
+
|
|
11846
|
+
// A list item can only hold inline content, so a block node (such as an image
|
|
11847
|
+
// attachment) dropped into one corrupts the list: Lexical lifts it into the
|
|
11848
|
+
// wrong list item and orphans an empty bullet. Block nodes belong at the top
|
|
11849
|
+
// level instead, splitting the list around the cursor. Inline content keeps
|
|
11850
|
+
// Lexical's default behavior and stays within the list item.
|
|
11851
|
+
class ListItemNodeInserter extends BaseNodeInserter {
|
|
11852
|
+
static handles(selection) {
|
|
11853
|
+
return Fr(selection) &&
|
|
11854
|
+
At$2(selection.anchor.getNode(), ge$2)
|
|
11855
|
+
}
|
|
11856
|
+
|
|
11857
|
+
insertNodes(nodes) {
|
|
11858
|
+
if (nodes.some(node => this.#isBlockDecorator(node))) {
|
|
11859
|
+
this.#insertAroundList(nodes);
|
|
11860
|
+
} else {
|
|
11861
|
+
this.selection.insertNodes(nodes);
|
|
11862
|
+
}
|
|
11863
|
+
}
|
|
11864
|
+
|
|
11865
|
+
#insertAroundList(nodes) {
|
|
11866
|
+
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
11867
|
+
|
|
11868
|
+
// Break out of any nesting to the outermost list. Splitting an inner list
|
|
11869
|
+
// would leave the block stranded inside an ancestor list item, which is the
|
|
11870
|
+
// very corruption we are avoiding. The block must land at the list's level.
|
|
11871
|
+
const anchorNode = this.selection.anchor.getNode();
|
|
11872
|
+
const outerList = this.#outermostList(anchorNode);
|
|
11873
|
+
const topItem = this.#topLevelItemFor(anchorNode, outerList);
|
|
11874
|
+
|
|
11875
|
+
// A blank top-level bullet is just the insertion point (e.g. the user pressed
|
|
11876
|
+
// Enter to leave the list); break out of it entirely. A bullet with content —
|
|
11877
|
+
// including one wrapping a nested list — splits so its content stays in the list.
|
|
11878
|
+
const splitAfterItem = $isBlankNode(topItem) ? topItem.getPreviousSibling() : topItem;
|
|
11879
|
+
const splitIndex = splitAfterItem ? splitAfterItem.getIndexWithinParent() + 1 : 0;
|
|
11880
|
+
const [ listBefore, listAfter ] = Us(outerList, splitIndex);
|
|
11881
|
+
if ($isBlankNode(topItem)) { topItem.remove(); }
|
|
11882
|
+
|
|
11883
|
+
let anchor = listBefore ?? listAfter;
|
|
11884
|
+
for (const node of nodes) {
|
|
11885
|
+
anchor.insertAfter(node);
|
|
11886
|
+
anchor = node;
|
|
11887
|
+
}
|
|
11888
|
+
|
|
11889
|
+
this.#removeEmptyList(listBefore);
|
|
11890
|
+
this.#removeEmptyList(listAfter);
|
|
11891
|
+
nodes.at(-1).selectNext();
|
|
11892
|
+
}
|
|
11893
|
+
|
|
11894
|
+
#outermostList(node) {
|
|
11895
|
+
return [ node, ...node.getParents() ].reverse().find(Se$1)
|
|
11896
|
+
}
|
|
11897
|
+
|
|
11898
|
+
#topLevelItemFor(node, outerList) {
|
|
11899
|
+
return [ node, ...node.getParents() ].find(ancestor =>
|
|
11900
|
+
pe$2(ancestor) && ancestor.getParent()?.is(outerList)
|
|
11901
|
+
)
|
|
11902
|
+
}
|
|
11903
|
+
|
|
11904
|
+
#removeEmptyList(list) {
|
|
11905
|
+
if (Se$1(list) && list.isEmpty()) list.remove();
|
|
11906
|
+
}
|
|
11907
|
+
|
|
11908
|
+
// Only block decorator nodes (image/file attachments) are intercepted. A list
|
|
11909
|
+
// item cannot hold them, so they must break out. Pasted element blocks
|
|
11910
|
+
// (paragraphs, quotes) keep Lexical's own list-escape semantics.
|
|
11911
|
+
#isBlockDecorator(node) {
|
|
11912
|
+
return ji(node) && !node.isInline()
|
|
11913
|
+
}
|
|
11783
11914
|
}
|
|
11784
11915
|
|
|
11785
11916
|
// Lexical's RangeSelection.insertNodes requires every selection point to have a block
|
|
11786
11917
|
// ancestor with inline children. An element point on a container of block nodes — e.g.
|
|
11787
11918
|
// a quote holding paragraphs — has none, so Lexical throws invariant #211 or #212.
|
|
11788
11919
|
// Descend such points to a leaf position before inserting.
|
|
11789
|
-
class BlockContainerNodeInserter extends
|
|
11920
|
+
class BlockContainerNodeInserter extends BaseNodeInserter {
|
|
11790
11921
|
static handles(selection) {
|
|
11791
|
-
return
|
|
11792
|
-
[ selection.anchor, selection.focus ].some($isPointOnBlockContainer)
|
|
11922
|
+
return $hasPointOnBlockContainer(selection)
|
|
11793
11923
|
}
|
|
11794
11924
|
|
|
11795
11925
|
insertNodes(nodes) {
|
|
@@ -11798,12 +11928,66 @@ class BlockContainerNodeInserter extends NodeInserter {
|
|
|
11798
11928
|
}
|
|
11799
11929
|
}
|
|
11800
11930
|
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11931
|
+
const INSERTERS = [
|
|
11932
|
+
CodeNodeInserter,
|
|
11933
|
+
ShadowRootNodeInserter,
|
|
11934
|
+
NodeSelectionNodeInserter,
|
|
11935
|
+
ListItemNodeInserter,
|
|
11936
|
+
BlockContainerNodeInserter
|
|
11937
|
+
];
|
|
11938
|
+
|
|
11939
|
+
// Defined here rather than on the base class so the base can stay free of any
|
|
11940
|
+
// dependency on its subclasses (they import the base), avoiding an import cycle.
|
|
11941
|
+
BaseNodeInserter.for = (selection) => {
|
|
11942
|
+
const inserterClass = INSERTERS.find(inserter => inserter.handles(selection));
|
|
11943
|
+
return inserterClass ? new inserterClass(selection) : selection
|
|
11944
|
+
};
|
|
11945
|
+
|
|
11946
|
+
class PastedContentFormatter {
|
|
11947
|
+
constructor(doc) {
|
|
11948
|
+
this.doc = doc;
|
|
11949
|
+
}
|
|
11950
|
+
|
|
11951
|
+
format() {
|
|
11952
|
+
this.#unwrapPlaceholderAnchors();
|
|
11953
|
+
this.#stripTableCellColorStyles();
|
|
11954
|
+
this.#stripStrayListChildren();
|
|
11955
|
+
return this.doc
|
|
11956
|
+
}
|
|
11957
|
+
|
|
11958
|
+
// Anchors with non-meaningful hrefs (e.g. "#", "") appear in content copied
|
|
11959
|
+
// from rendered views where mentions and interactive elements are wrapped in
|
|
11960
|
+
// <a href="#"> tags. Unwrap them so their text content pastes as plain text
|
|
11961
|
+
// and real links are preserved.
|
|
11962
|
+
#unwrapPlaceholderAnchors() {
|
|
11963
|
+
for (const anchor of this.doc.querySelectorAll("a")) {
|
|
11964
|
+
const href = anchor.getAttribute("href") || "";
|
|
11965
|
+
if (href === "" || href === "#") {
|
|
11966
|
+
anchor.replaceWith(...anchor.childNodes);
|
|
11967
|
+
}
|
|
11968
|
+
}
|
|
11969
|
+
}
|
|
11970
|
+
|
|
11971
|
+
// Table cells copied from a page inherit the source theme's inline color
|
|
11972
|
+
// styles (e.g. dark-mode backgrounds). Strip them so pasted tables adopt
|
|
11973
|
+
// the current theme instead of carrying stale colors.
|
|
11974
|
+
#stripTableCellColorStyles() {
|
|
11975
|
+
for (const cell of this.doc.querySelectorAll("td, th")) {
|
|
11976
|
+
cell.style.removeProperty("background-color");
|
|
11977
|
+
cell.style.removeProperty("background");
|
|
11978
|
+
cell.style.removeProperty("color");
|
|
11979
|
+
}
|
|
11980
|
+
}
|
|
11981
|
+
|
|
11982
|
+
// Only <li> is a valid child of a list; drop stray <br>/whitespace so the
|
|
11983
|
+
// import doesn't wrap them into an empty leading item.
|
|
11984
|
+
#stripStrayListChildren() {
|
|
11985
|
+
for (const list of this.doc.querySelectorAll("ol, ul")) {
|
|
11986
|
+
for (const child of Array.from(list.childNodes)) {
|
|
11987
|
+
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "LI") continue
|
|
11988
|
+
list.removeChild(child);
|
|
11989
|
+
}
|
|
11990
|
+
}
|
|
11807
11991
|
}
|
|
11808
11992
|
}
|
|
11809
11993
|
|
|
@@ -11838,7 +12022,7 @@ class Contents {
|
|
|
11838
12022
|
|
|
11839
12023
|
insertAtCursor(...nodes) {
|
|
11840
12024
|
const selection = Qr() ?? Zo().selectEnd();
|
|
11841
|
-
const inserter =
|
|
12025
|
+
const inserter = BaseNodeInserter.for(selection);
|
|
11842
12026
|
|
|
11843
12027
|
inserter.insertNodes(nodes);
|
|
11844
12028
|
}
|
|
@@ -11852,7 +12036,7 @@ class Contents {
|
|
|
11852
12036
|
const selection = Qr();
|
|
11853
12037
|
if (!Fr(selection)) return
|
|
11854
12038
|
|
|
11855
|
-
$expandSelectionToLineBreaksAndSplitAtEdges(selection);
|
|
12039
|
+
$expandSelectionToLineBreaksAndSplitAtEdges(selection, (node) => Nt$2(node));
|
|
11856
12040
|
H$6(selection, () => eo());
|
|
11857
12041
|
}
|
|
11858
12042
|
|
|
@@ -11865,13 +12049,11 @@ class Contents {
|
|
|
11865
12049
|
}
|
|
11866
12050
|
|
|
11867
12051
|
applyUnorderedListFormat() {
|
|
11868
|
-
this.#
|
|
11869
|
-
this.editor.dispatchCommand(Ee$2, undefined);
|
|
12052
|
+
this.#applyListFormat("bullet", Ee$2);
|
|
11870
12053
|
}
|
|
11871
12054
|
|
|
11872
12055
|
applyOrderedListFormat() {
|
|
11873
|
-
this.#
|
|
11874
|
-
this.editor.dispatchCommand(Oe$1, undefined);
|
|
12056
|
+
this.#applyListFormat("number", Oe$1);
|
|
11875
12057
|
}
|
|
11876
12058
|
|
|
11877
12059
|
clearFormatting() {
|
|
@@ -11959,7 +12141,7 @@ class Contents {
|
|
|
11959
12141
|
|
|
11960
12142
|
const selection = Qr();
|
|
11961
12143
|
if (Fr(selection)) {
|
|
11962
|
-
selection.insertNodes([ linkNode ]);
|
|
12144
|
+
BaseNodeInserter.for(selection).insertNodes([ linkNode ]);
|
|
11963
12145
|
linkNodeKey = linkNode.getKey();
|
|
11964
12146
|
}
|
|
11965
12147
|
});
|
|
@@ -12154,8 +12336,7 @@ class Contents {
|
|
|
12154
12336
|
}
|
|
12155
12337
|
|
|
12156
12338
|
#formatPastedDOM(doc) {
|
|
12157
|
-
|
|
12158
|
-
this.#stripTableCellColorStyles(doc);
|
|
12339
|
+
new PastedContentFormatter(doc).format();
|
|
12159
12340
|
}
|
|
12160
12341
|
|
|
12161
12342
|
#dispatchPastedNodesCommand(nodes) {
|
|
@@ -12203,6 +12384,45 @@ class Contents {
|
|
|
12203
12384
|
codeNode.remove();
|
|
12204
12385
|
}
|
|
12205
12386
|
|
|
12387
|
+
#applyListFormat(listType, command) {
|
|
12388
|
+
if (this.selection.isInsideBlockQuote) {
|
|
12389
|
+
this.#insertListInsideQuote(listType);
|
|
12390
|
+
} else {
|
|
12391
|
+
this.#splitParagraphsAtLineBreaksUnlessInsideList();
|
|
12392
|
+
this.editor.dispatchCommand(command);
|
|
12393
|
+
}
|
|
12394
|
+
}
|
|
12395
|
+
|
|
12396
|
+
#insertListInsideQuote(listType) {
|
|
12397
|
+
for (const group of $consecutiveSiblingGroups(this.#quotedBlocksInSelection())) {
|
|
12398
|
+
this.#wrapBlocksInList(group, listType);
|
|
12399
|
+
}
|
|
12400
|
+
}
|
|
12401
|
+
|
|
12402
|
+
#quotedBlocksInSelection() {
|
|
12403
|
+
const selection = Qr();
|
|
12404
|
+
if (!Fr(selection)) return []
|
|
12405
|
+
|
|
12406
|
+
const blocks = this.#outermostElements(this.#blockLevelElementsInSelection(selection));
|
|
12407
|
+
return blocks.filter((block) => Mt$2(block.getParent()))
|
|
12408
|
+
}
|
|
12409
|
+
|
|
12410
|
+
#wrapBlocksInList(blocks, listType) {
|
|
12411
|
+
const list = Te$1(listType);
|
|
12412
|
+
blocks[0].insertBefore(list);
|
|
12413
|
+
|
|
12414
|
+
for (const block of blocks) {
|
|
12415
|
+
const listItem = fe$2();
|
|
12416
|
+
if (Se$1(block)) {
|
|
12417
|
+
listItem.append(...block.getChildren().flatMap((item) => item.getChildren()));
|
|
12418
|
+
} else {
|
|
12419
|
+
listItem.append(...block.getChildren());
|
|
12420
|
+
}
|
|
12421
|
+
list.append(listItem);
|
|
12422
|
+
block.remove();
|
|
12423
|
+
}
|
|
12424
|
+
}
|
|
12425
|
+
|
|
12206
12426
|
#splitParagraphsAtLineBreaksUnlessInsideList() {
|
|
12207
12427
|
if (this.selection.isInsideList) return
|
|
12208
12428
|
|
|
@@ -12283,30 +12503,6 @@ class Contents {
|
|
|
12283
12503
|
node.remove();
|
|
12284
12504
|
}
|
|
12285
12505
|
|
|
12286
|
-
// Anchors with non-meaningful hrefs (e.g. "#", "") appear in content copied
|
|
12287
|
-
// from rendered views where mentions and interactive elements are wrapped in
|
|
12288
|
-
// <a href="#"> tags. Unwrap them so their text content pastes as plain text
|
|
12289
|
-
// and real links are preserved.
|
|
12290
|
-
#unwrapPlaceholderAnchors(doc) {
|
|
12291
|
-
for (const anchor of doc.querySelectorAll("a")) {
|
|
12292
|
-
const href = anchor.getAttribute("href") || "";
|
|
12293
|
-
if (href === "" || href === "#") {
|
|
12294
|
-
anchor.replaceWith(...anchor.childNodes);
|
|
12295
|
-
}
|
|
12296
|
-
}
|
|
12297
|
-
}
|
|
12298
|
-
|
|
12299
|
-
// Table cells copied from a page inherit the source theme's inline color
|
|
12300
|
-
// styles (e.g. dark-mode backgrounds). Strip them so pasted tables adopt
|
|
12301
|
-
// the current theme instead of carrying stale colors.
|
|
12302
|
-
#stripTableCellColorStyles(doc) {
|
|
12303
|
-
for (const cell of doc.querySelectorAll("td, th")) {
|
|
12304
|
-
cell.style.removeProperty("background-color");
|
|
12305
|
-
cell.style.removeProperty("background");
|
|
12306
|
-
cell.style.removeProperty("color");
|
|
12307
|
-
}
|
|
12308
|
-
}
|
|
12309
|
-
|
|
12310
12506
|
#getTextAnchorData() {
|
|
12311
12507
|
const selection = Qr();
|
|
12312
12508
|
if (!selection || !selection.isCollapsed()) return { anchorNode: null, offset: 0 }
|
|
@@ -12624,7 +12820,7 @@ class Clipboard {
|
|
|
12624
12820
|
}
|
|
12625
12821
|
|
|
12626
12822
|
const linkNode = $$2(url).append(kr(url));
|
|
12627
|
-
selection.insertNodes([ linkNode ]);
|
|
12823
|
+
BaseNodeInserter.for(selection).insertNodes([ linkNode ]);
|
|
12628
12824
|
|
|
12629
12825
|
Ms(() => this.#dispatchLinkInsertEvent(linkNode.getKey(), { url }));
|
|
12630
12826
|
}
|
|
@@ -14090,7 +14286,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14090
14286
|
static debug = false
|
|
14091
14287
|
static commands = [ "bold", "italic", "strikethrough" ]
|
|
14092
14288
|
|
|
14093
|
-
static observedAttributes = [ "connected", "required" ]
|
|
14289
|
+
static observedAttributes = [ "autocapitalize", "connected", "required" ]
|
|
14094
14290
|
|
|
14095
14291
|
#initialValue = ""
|
|
14096
14292
|
#previousInternalFormValue = null
|
|
@@ -14157,8 +14353,15 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14157
14353
|
}
|
|
14158
14354
|
|
|
14159
14355
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
14160
|
-
if (name === "
|
|
14161
|
-
|
|
14356
|
+
if (typeof this[`${name}ChangedCallback`] === "function") {
|
|
14357
|
+
this[`${name}ChangedCallback`](oldValue, newValue);
|
|
14358
|
+
}
|
|
14359
|
+
}
|
|
14360
|
+
|
|
14361
|
+
autocapitalizeChangedCallback() {
|
|
14362
|
+
if (this.editorContentElement) {
|
|
14363
|
+
this.#transferAttributeToContentEditable(this.editorContentElement, "autocapitalize");
|
|
14364
|
+
}
|
|
14162
14365
|
}
|
|
14163
14366
|
|
|
14164
14367
|
connectedChangedCallback(oldValue, newValue) {
|
|
@@ -14490,25 +14693,33 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14490
14693
|
|
|
14491
14694
|
#createEditorContentElement() {
|
|
14492
14695
|
const editorContentElement = createElement("div", {
|
|
14696
|
+
id: `${this.id}-content`,
|
|
14493
14697
|
classList: "lexxy-editor__content",
|
|
14494
14698
|
contenteditable: true,
|
|
14495
|
-
autocapitalize: "none",
|
|
14496
14699
|
role: "textbox",
|
|
14497
14700
|
"aria-multiline": true,
|
|
14498
14701
|
"aria-label": this.#labelText,
|
|
14499
14702
|
placeholder: this.getAttribute("placeholder")
|
|
14500
14703
|
});
|
|
14501
|
-
|
|
14704
|
+
|
|
14502
14705
|
this.#ariaAttributes.forEach(attribute => editorContentElement.setAttribute(attribute.name, attribute.value));
|
|
14503
14706
|
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14707
|
+
this.#transferAttributeToContentEditable(editorContentElement, "autocapitalize");
|
|
14708
|
+
this.#transferAttributeToContentEditable(editorContentElement, "tabindex", { defaultValue: 0, removeSource: true });
|
|
14709
|
+
|
|
14710
|
+
return editorContentElement
|
|
14711
|
+
}
|
|
14712
|
+
|
|
14713
|
+
#transferAttributeToContentEditable(element, qualifiedName, { defaultValue = null, removeSource = false } = {}) {
|
|
14714
|
+
if (this.hasAttribute(qualifiedName)) {
|
|
14715
|
+
element.setAttribute(qualifiedName, this.getAttribute(qualifiedName));
|
|
14716
|
+
} else if (defaultValue !== null) {
|
|
14717
|
+
element.setAttribute(qualifiedName, defaultValue);
|
|
14507
14718
|
} else {
|
|
14508
|
-
|
|
14719
|
+
element.removeAttribute(qualifiedName);
|
|
14509
14720
|
}
|
|
14510
14721
|
|
|
14511
|
-
|
|
14722
|
+
if (removeSource) this.removeAttribute(qualifiedName);
|
|
14512
14723
|
}
|
|
14513
14724
|
|
|
14514
14725
|
get #labelText() {
|
|
@@ -15125,6 +15336,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
15125
15336
|
this.source = this.#createSource();
|
|
15126
15337
|
|
|
15127
15338
|
this.#addTriggerListener();
|
|
15339
|
+
this.#removePopoverBeforeTurboCaches();
|
|
15128
15340
|
this.toggleAttribute("connected", true);
|
|
15129
15341
|
}
|
|
15130
15342
|
|
|
@@ -15132,7 +15344,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
15132
15344
|
this.#popoverListeners.dispose();
|
|
15133
15345
|
this.#globalListeners.dispose();
|
|
15134
15346
|
this.source = null;
|
|
15135
|
-
this
|
|
15347
|
+
this.#removePopover();
|
|
15136
15348
|
}
|
|
15137
15349
|
|
|
15138
15350
|
|
|
@@ -15428,6 +15640,21 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
15428
15640
|
this.#addTriggerListener();
|
|
15429
15641
|
}
|
|
15430
15642
|
|
|
15643
|
+
// The popover is appended to the <lexxy-editor> subtree, so Turbo serializes it
|
|
15644
|
+
// into the page cache. Removing it before caching prevents an orphaned, unmanaged
|
|
15645
|
+
// popover from being restored on history back/forward.
|
|
15646
|
+
#removePopoverBeforeTurboCaches() {
|
|
15647
|
+
this.#globalListeners.track(
|
|
15648
|
+
registerEventListener(document, "turbo:before-cache", () => this.#removePopover())
|
|
15649
|
+
);
|
|
15650
|
+
}
|
|
15651
|
+
|
|
15652
|
+
#removePopover() {
|
|
15653
|
+
this.#popoverListeners.dispose();
|
|
15654
|
+
this.popoverElement?.remove();
|
|
15655
|
+
this.popoverElement = null;
|
|
15656
|
+
}
|
|
15657
|
+
|
|
15431
15658
|
#filterOptions = async () => {
|
|
15432
15659
|
if (this.initialPrompt) {
|
|
15433
15660
|
this.initialPrompt = false;
|
|
Binary file
|
|
Binary file
|