@37signals/lexxy 0.1.15-beta → 0.1.17-beta
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.
- package/dist/lexxy.esm.js +419 -16
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import DOMPurify from 'dompurify';
|
|
2
|
-
import { $getSelection, $isRangeSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isTextNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode,
|
|
3
|
-
import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, ListNode, ListItemNode, registerList } from '@lexical/list';
|
|
2
|
+
import { $getSelection, $isRangeSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, $createTextNode, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isTextNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, $insertNodes, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, COMMAND_PRIORITY_NORMAL, KEY_TAB_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
|
|
3
|
+
import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $createListNode, ListNode, ListItemNode, registerList } from '@lexical/list';
|
|
4
4
|
import { $isQuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
5
5
|
import { $isCodeNode, CodeNode, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP, normalizeCodeLang } from '@lexical/code';
|
|
6
|
-
import { $isLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
|
|
6
|
+
import { $isLinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
|
|
7
7
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
8
8
|
import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
|
|
9
9
|
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
@@ -1019,7 +1019,19 @@ class CommandDispatcher {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
1021
|
dispatchLink(url) {
|
|
1022
|
-
this
|
|
1022
|
+
this.editor.update(() => {
|
|
1023
|
+
const selection = $getSelection();
|
|
1024
|
+
if (!$isRangeSelection(selection)) return
|
|
1025
|
+
|
|
1026
|
+
if (selection.isCollapsed()) {
|
|
1027
|
+
const autoLinkNode = $createAutoLinkNode(url);
|
|
1028
|
+
const textNode = $createTextNode(url);
|
|
1029
|
+
autoLinkNode.append(textNode);
|
|
1030
|
+
selection.insertNodes([ autoLinkNode ]);
|
|
1031
|
+
} else {
|
|
1032
|
+
$toggleLink(url);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1023
1035
|
}
|
|
1024
1036
|
|
|
1025
1037
|
dispatchUnlink() {
|
|
@@ -1053,7 +1065,7 @@ class CommandDispatcher {
|
|
|
1053
1065
|
}
|
|
1054
1066
|
|
|
1055
1067
|
dispatchInsertQuoteBlock() {
|
|
1056
|
-
this.contents.
|
|
1068
|
+
this.contents.toggleNodeWrappingAllSelectedNodes((node) => $isQuoteNode(node), () => $createQuoteNode());
|
|
1057
1069
|
}
|
|
1058
1070
|
|
|
1059
1071
|
dispatchInsertCodeBlock() {
|
|
@@ -1920,10 +1932,292 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1920
1932
|
}
|
|
1921
1933
|
}
|
|
1922
1934
|
|
|
1935
|
+
class FormatEscaper {
|
|
1936
|
+
constructor(editorElement) {
|
|
1937
|
+
this.editorElement = editorElement;
|
|
1938
|
+
this.editor = editorElement.editor;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
monitor() {
|
|
1942
|
+
this.editor.registerCommand(
|
|
1943
|
+
KEY_ENTER_COMMAND,
|
|
1944
|
+
(event) => this.#handleEnterKey(event),
|
|
1945
|
+
COMMAND_PRIORITY_HIGH
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
#handleEnterKey(event) {
|
|
1950
|
+
const selection = $getSelection();
|
|
1951
|
+
if (!$isRangeSelection(selection)) return false
|
|
1952
|
+
|
|
1953
|
+
const anchorNode = selection.anchor.getNode();
|
|
1954
|
+
|
|
1955
|
+
if (!this.#isInsideBlockquote(anchorNode)) return false
|
|
1956
|
+
|
|
1957
|
+
return this.#handleLists(event, anchorNode)
|
|
1958
|
+
|| this.#handleBlockquotes(event, anchorNode)
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
#handleLists(event, anchorNode) {
|
|
1962
|
+
if (this.#shouldEscapeFromEmptyListItem(anchorNode) || this.#shouldEscapeFromEmptyParagraphInListItem(anchorNode)) {
|
|
1963
|
+
event.preventDefault();
|
|
1964
|
+
this.#escapeFromList(anchorNode);
|
|
1965
|
+
return true
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
return false
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
#handleBlockquotes(event, anchorNode) {
|
|
1972
|
+
if (this.#shouldEscapeFromEmptyParagraphInBlockquote(anchorNode)) {
|
|
1973
|
+
event.preventDefault();
|
|
1974
|
+
this.#escapeFromBlockquote(anchorNode);
|
|
1975
|
+
return true
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
return false
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
#isInsideBlockquote(node) {
|
|
1982
|
+
let currentNode = node;
|
|
1983
|
+
|
|
1984
|
+
while (currentNode) {
|
|
1985
|
+
if ($isQuoteNode(currentNode)) {
|
|
1986
|
+
return true
|
|
1987
|
+
}
|
|
1988
|
+
currentNode = currentNode.getParent();
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
return false
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
#shouldEscapeFromEmptyListItem(node) {
|
|
1995
|
+
const listItem = this.#getListItemNode(node);
|
|
1996
|
+
if (!listItem) return false
|
|
1997
|
+
|
|
1998
|
+
return this.#isNodeEmpty(listItem)
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
#shouldEscapeFromEmptyParagraphInListItem(node) {
|
|
2002
|
+
const paragraph = this.#getParagraphNode(node);
|
|
2003
|
+
if (!paragraph) return false
|
|
2004
|
+
|
|
2005
|
+
if (!this.#isNodeEmpty(paragraph)) return false
|
|
2006
|
+
|
|
2007
|
+
const parent = paragraph.getParent();
|
|
2008
|
+
return parent && $isListItemNode(parent)
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
#isNodeEmpty(node) {
|
|
2012
|
+
if (node.getTextContent().trim() !== "") return false
|
|
2013
|
+
|
|
2014
|
+
const children = node.getChildren();
|
|
2015
|
+
if (children.length === 0) return true
|
|
2016
|
+
|
|
2017
|
+
return children.every(child => {
|
|
2018
|
+
if ($isLineBreakNode(child)) return true
|
|
2019
|
+
return this.#isNodeEmpty(child)
|
|
2020
|
+
})
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
#getListItemNode(node) {
|
|
2024
|
+
let currentNode = node;
|
|
2025
|
+
|
|
2026
|
+
while (currentNode) {
|
|
2027
|
+
if ($isListItemNode(currentNode)) {
|
|
2028
|
+
return currentNode
|
|
2029
|
+
}
|
|
2030
|
+
currentNode = currentNode.getParent();
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
return null
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
#escapeFromList(anchorNode) {
|
|
2037
|
+
const listItem = this.#getListItemNode(anchorNode);
|
|
2038
|
+
if (!listItem) return
|
|
2039
|
+
|
|
2040
|
+
const parentList = listItem.getParent();
|
|
2041
|
+
if (!parentList || !$isListNode(parentList)) return
|
|
2042
|
+
|
|
2043
|
+
const blockquote = parentList.getParent();
|
|
2044
|
+
const isInBlockquote = blockquote && $isQuoteNode(blockquote);
|
|
2045
|
+
|
|
2046
|
+
if (isInBlockquote) {
|
|
2047
|
+
const listItemsAfter = this.#getListItemSiblingsAfter(listItem);
|
|
2048
|
+
const nonEmptyListItems = listItemsAfter.filter(item => !this.#isNodeEmpty(item));
|
|
2049
|
+
|
|
2050
|
+
if (nonEmptyListItems.length > 0) {
|
|
2051
|
+
this.#splitBlockquoteWithList(blockquote, parentList, listItem, nonEmptyListItems);
|
|
2052
|
+
return
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
const paragraph = $createParagraphNode();
|
|
2057
|
+
parentList.insertAfter(paragraph);
|
|
2058
|
+
|
|
2059
|
+
listItem.remove();
|
|
2060
|
+
paragraph.selectStart();
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
#shouldEscapeFromEmptyParagraphInBlockquote(node) {
|
|
2064
|
+
const paragraph = this.#getParagraphNode(node);
|
|
2065
|
+
if (!paragraph) return false
|
|
2066
|
+
|
|
2067
|
+
if (!this.#isNodeEmpty(paragraph)) return false
|
|
2068
|
+
|
|
2069
|
+
const parent = paragraph.getParent();
|
|
2070
|
+
return parent && $isQuoteNode(parent)
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
#getParagraphNode(node) {
|
|
2074
|
+
let currentNode = node;
|
|
2075
|
+
|
|
2076
|
+
while (currentNode) {
|
|
2077
|
+
if ($isParagraphNode(currentNode)) {
|
|
2078
|
+
return currentNode
|
|
2079
|
+
}
|
|
2080
|
+
currentNode = currentNode.getParent();
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return null
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
#escapeFromBlockquote(anchorNode) {
|
|
2087
|
+
const paragraph = this.#getParagraphNode(anchorNode);
|
|
2088
|
+
if (!paragraph) return
|
|
2089
|
+
|
|
2090
|
+
const blockquote = paragraph.getParent();
|
|
2091
|
+
if (!blockquote || !$isQuoteNode(blockquote)) return
|
|
2092
|
+
|
|
2093
|
+
const siblingsAfter = this.#getSiblingsAfter(paragraph);
|
|
2094
|
+
const nonEmptySiblings = siblingsAfter.filter(sibling => !this.#isNodeEmpty(sibling));
|
|
2095
|
+
|
|
2096
|
+
if (nonEmptySiblings.length > 0) {
|
|
2097
|
+
this.#splitBlockquote(blockquote, paragraph, nonEmptySiblings);
|
|
2098
|
+
} else {
|
|
2099
|
+
const newParagraph = $createParagraphNode();
|
|
2100
|
+
blockquote.insertAfter(newParagraph);
|
|
2101
|
+
paragraph.remove();
|
|
2102
|
+
newParagraph.selectStart();
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
#getSiblingsAfter(node) {
|
|
2107
|
+
const siblings = [];
|
|
2108
|
+
let sibling = node.getNextSibling();
|
|
2109
|
+
|
|
2110
|
+
while (sibling) {
|
|
2111
|
+
siblings.push(sibling);
|
|
2112
|
+
sibling = sibling.getNextSibling();
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
return siblings
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
#getListItemSiblingsAfter(listItem) {
|
|
2119
|
+
const siblings = [];
|
|
2120
|
+
let sibling = listItem.getNextSibling();
|
|
2121
|
+
|
|
2122
|
+
while (sibling) {
|
|
2123
|
+
if ($isListItemNode(sibling)) {
|
|
2124
|
+
siblings.push(sibling);
|
|
2125
|
+
}
|
|
2126
|
+
sibling = sibling.getNextSibling();
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
return siblings
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
#splitBlockquoteWithList(blockquote, parentList, emptyListItem, listItemsAfter) {
|
|
2133
|
+
const blockquoteSiblingsAfterList = this.#getSiblingsAfter(parentList);
|
|
2134
|
+
const nonEmptyBlockquoteSiblings = blockquoteSiblingsAfterList.filter(sibling => !this.#isNodeEmpty(sibling));
|
|
2135
|
+
|
|
2136
|
+
const middleParagraph = $createParagraphNode();
|
|
2137
|
+
blockquote.insertAfter(middleParagraph);
|
|
2138
|
+
|
|
2139
|
+
const newList = $createListNode(parentList.getListType());
|
|
2140
|
+
|
|
2141
|
+
const newBlockquote = $createQuoteNode();
|
|
2142
|
+
middleParagraph.insertAfter(newBlockquote);
|
|
2143
|
+
newBlockquote.append(newList);
|
|
2144
|
+
|
|
2145
|
+
listItemsAfter.forEach(item => {
|
|
2146
|
+
newList.append(item);
|
|
2147
|
+
});
|
|
2148
|
+
|
|
2149
|
+
nonEmptyBlockquoteSiblings.forEach(sibling => {
|
|
2150
|
+
newBlockquote.append(sibling);
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
emptyListItem.remove();
|
|
2154
|
+
|
|
2155
|
+
this.#removeTrailingEmptyListItems(parentList);
|
|
2156
|
+
this.#removeTrailingEmptyNodes(newBlockquote);
|
|
2157
|
+
|
|
2158
|
+
if (parentList.getChildrenSize() === 0) {
|
|
2159
|
+
parentList.remove();
|
|
2160
|
+
|
|
2161
|
+
if (blockquote.getChildrenSize() === 0) {
|
|
2162
|
+
blockquote.remove();
|
|
2163
|
+
}
|
|
2164
|
+
} else {
|
|
2165
|
+
this.#removeTrailingEmptyNodes(blockquote);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
middleParagraph.selectStart();
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
#removeTrailingEmptyListItems(list) {
|
|
2172
|
+
const items = list.getChildren();
|
|
2173
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
2174
|
+
const item = items[i];
|
|
2175
|
+
if ($isListItemNode(item) && this.#isNodeEmpty(item)) {
|
|
2176
|
+
item.remove();
|
|
2177
|
+
} else {
|
|
2178
|
+
break
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
#removeTrailingEmptyNodes(blockquote) {
|
|
2184
|
+
const children = blockquote.getChildren();
|
|
2185
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
2186
|
+
const child = children[i];
|
|
2187
|
+
if (this.#isNodeEmpty(child)) {
|
|
2188
|
+
child.remove();
|
|
2189
|
+
} else {
|
|
2190
|
+
break
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
#splitBlockquote(blockquote, emptyParagraph, siblingsAfter) {
|
|
2196
|
+
const newParagraph = $createParagraphNode();
|
|
2197
|
+
blockquote.insertAfter(newParagraph);
|
|
2198
|
+
|
|
2199
|
+
const newBlockquote = $createQuoteNode();
|
|
2200
|
+
newParagraph.insertAfter(newBlockquote);
|
|
2201
|
+
|
|
2202
|
+
siblingsAfter.forEach(sibling => {
|
|
2203
|
+
newBlockquote.append(sibling);
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2206
|
+
emptyParagraph.remove();
|
|
2207
|
+
|
|
2208
|
+
this.#removeTrailingEmptyNodes(blockquote);
|
|
2209
|
+
this.#removeTrailingEmptyNodes(newBlockquote);
|
|
2210
|
+
|
|
2211
|
+
newParagraph.selectStart();
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
1923
2215
|
class Contents {
|
|
1924
2216
|
constructor(editorElement) {
|
|
1925
2217
|
this.editorElement = editorElement;
|
|
1926
2218
|
this.editor = editorElement.editor;
|
|
2219
|
+
|
|
2220
|
+
new FormatEscaper(editorElement).monitor();
|
|
1927
2221
|
}
|
|
1928
2222
|
|
|
1929
2223
|
insertHtml(html) {
|
|
@@ -1984,20 +2278,23 @@ class Contents {
|
|
|
1984
2278
|
if (isFormatAppliedFn(topLevelElement)) {
|
|
1985
2279
|
this.removeFormattingFromSelectedLines();
|
|
1986
2280
|
} else {
|
|
1987
|
-
this
|
|
2281
|
+
this.#insertNodeWrappingAllSelectedLines(newNodeFn);
|
|
1988
2282
|
}
|
|
1989
2283
|
});
|
|
1990
2284
|
}
|
|
1991
2285
|
|
|
1992
|
-
|
|
2286
|
+
toggleNodeWrappingAllSelectedNodes(isFormatAppliedFn, newNodeFn) {
|
|
1993
2287
|
this.editor.update(() => {
|
|
1994
2288
|
const selection = $getSelection();
|
|
1995
2289
|
if (!$isRangeSelection(selection)) return
|
|
1996
2290
|
|
|
1997
|
-
|
|
1998
|
-
|
|
2291
|
+
const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow();
|
|
2292
|
+
|
|
2293
|
+
// Check if format is already applied
|
|
2294
|
+
if (isFormatAppliedFn(topLevelElement)) {
|
|
2295
|
+
this.#unwrap(topLevelElement);
|
|
1999
2296
|
} else {
|
|
2000
|
-
this.#
|
|
2297
|
+
this.#insertNodeWrappingAllSelectedNodes(newNodeFn);
|
|
2001
2298
|
}
|
|
2002
2299
|
});
|
|
2003
2300
|
}
|
|
@@ -2238,6 +2535,80 @@ class Contents {
|
|
|
2238
2535
|
return this.editorElement.selection
|
|
2239
2536
|
}
|
|
2240
2537
|
|
|
2538
|
+
#unwrap(node) {
|
|
2539
|
+
const children = node.getChildren();
|
|
2540
|
+
|
|
2541
|
+
children.forEach((child) => {
|
|
2542
|
+
node.insertBefore(child);
|
|
2543
|
+
});
|
|
2544
|
+
|
|
2545
|
+
node.remove();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
#insertNodeWrappingAllSelectedNodes(newNodeFn) {
|
|
2549
|
+
this.editor.update(() => {
|
|
2550
|
+
const selection = $getSelection();
|
|
2551
|
+
if (!$isRangeSelection(selection)) return
|
|
2552
|
+
|
|
2553
|
+
const selectedNodes = selection.extract();
|
|
2554
|
+
if (selectedNodes.length === 0) return
|
|
2555
|
+
|
|
2556
|
+
const topLevelElements = new Set();
|
|
2557
|
+
selectedNodes.forEach((node) => {
|
|
2558
|
+
const topLevel = node.getTopLevelElementOrThrow();
|
|
2559
|
+
topLevelElements.add(topLevel);
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
const elements = this.#removeTrailingEmptyParagraphs(Array.from(topLevelElements));
|
|
2563
|
+
if (elements.length === 0) return
|
|
2564
|
+
|
|
2565
|
+
const wrappingNode = newNodeFn();
|
|
2566
|
+
elements[0].insertBefore(wrappingNode);
|
|
2567
|
+
elements.forEach((element) => {
|
|
2568
|
+
wrappingNode.append(element);
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2571
|
+
$setSelection(null);
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
#removeTrailingEmptyParagraphs(elements) {
|
|
2576
|
+
let lastNonEmptyIndex = elements.length - 1;
|
|
2577
|
+
|
|
2578
|
+
// Find the last non-empty paragraph
|
|
2579
|
+
while (lastNonEmptyIndex >= 0) {
|
|
2580
|
+
const element = elements[lastNonEmptyIndex];
|
|
2581
|
+
if (!$isParagraphNode(element) || !this.#isElementEmpty(element)) {
|
|
2582
|
+
break
|
|
2583
|
+
}
|
|
2584
|
+
lastNonEmptyIndex--;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
return elements.slice(0, lastNonEmptyIndex + 1)
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
#isElementEmpty(element) {
|
|
2591
|
+
// Check text content first
|
|
2592
|
+
if (element.getTextContent().trim() !== "") return false
|
|
2593
|
+
|
|
2594
|
+
// Check if it only contains line breaks
|
|
2595
|
+
const children = element.getChildren();
|
|
2596
|
+
return children.length === 0 || children.every(child => $isLineBreakNode(child))
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
#insertNodeWrappingAllSelectedLines(newNodeFn) {
|
|
2600
|
+
this.editor.update(() => {
|
|
2601
|
+
const selection = $getSelection();
|
|
2602
|
+
if (!$isRangeSelection(selection)) return
|
|
2603
|
+
|
|
2604
|
+
if (selection.isCollapsed()) {
|
|
2605
|
+
this.#wrapCurrentLine(selection, newNodeFn);
|
|
2606
|
+
} else {
|
|
2607
|
+
this.#wrapMultipleSelectedLines(selection, newNodeFn);
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2241
2612
|
#wrapCurrentLine(selection, newNodeFn) {
|
|
2242
2613
|
const anchorNode = selection.anchor.getNode();
|
|
2243
2614
|
const topLevelElement = anchorNode.getTopLevelElementOrThrow();
|
|
@@ -2566,7 +2937,7 @@ class Clipboard {
|
|
|
2566
2937
|
|
|
2567
2938
|
if (!clipboardData) return false
|
|
2568
2939
|
|
|
2569
|
-
if (this.#isOnlyPlainTextPasted(clipboardData)) {
|
|
2940
|
+
if (this.#isOnlyPlainTextPasted(clipboardData) && !this.#isPastingIntoCodeBlock()) {
|
|
2570
2941
|
this.#pastePlainText(clipboardData);
|
|
2571
2942
|
event.preventDefault();
|
|
2572
2943
|
return true
|
|
@@ -2580,6 +2951,27 @@ class Clipboard {
|
|
|
2580
2951
|
return types.length === 1 && types[0] === "text/plain"
|
|
2581
2952
|
}
|
|
2582
2953
|
|
|
2954
|
+
#isPastingIntoCodeBlock() {
|
|
2955
|
+
let result = false;
|
|
2956
|
+
|
|
2957
|
+
this.editor.getEditorState().read(() => {
|
|
2958
|
+
const selection = $getSelection();
|
|
2959
|
+
if (!$isRangeSelection(selection)) return
|
|
2960
|
+
|
|
2961
|
+
let currentNode = selection.anchor.getNode();
|
|
2962
|
+
|
|
2963
|
+
while (currentNode) {
|
|
2964
|
+
if ($isCodeNode(currentNode)) {
|
|
2965
|
+
result = true;
|
|
2966
|
+
return
|
|
2967
|
+
}
|
|
2968
|
+
currentNode = currentNode.getParent();
|
|
2969
|
+
}
|
|
2970
|
+
});
|
|
2971
|
+
|
|
2972
|
+
return result
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2583
2975
|
#pastePlainText(clipboardData) {
|
|
2584
2976
|
const item = clipboardData.items[0];
|
|
2585
2977
|
item.getAsString((text) => {
|
|
@@ -3316,8 +3708,8 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3316
3708
|
|
|
3317
3709
|
async #showPopover() {
|
|
3318
3710
|
this.popoverElement ??= await this.#buildPopover();
|
|
3711
|
+
this.#resetPopoverPosition();
|
|
3319
3712
|
await this.#filterOptions();
|
|
3320
|
-
this.#positionPopover();
|
|
3321
3713
|
this.popoverElement.classList.toggle("lexxy-prompt-menu--visible", true);
|
|
3322
3714
|
this.#selectFirstOption();
|
|
3323
3715
|
|
|
@@ -3372,19 +3764,29 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3372
3764
|
const contentRect = this.#editorContentElement.getBoundingClientRect();
|
|
3373
3765
|
const verticalOffset = contentRect.top - editorRect.top;
|
|
3374
3766
|
|
|
3375
|
-
this.popoverElement.
|
|
3767
|
+
if (!this.popoverElement.hasAttribute("data-anchored")) {
|
|
3768
|
+
this.popoverElement.style.left = `${x}px`;
|
|
3769
|
+
this.popoverElement.toggleAttribute("data-anchored", true);
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3376
3772
|
this.popoverElement.style.top = `${y + verticalOffset}px`;
|
|
3377
3773
|
this.popoverElement.style.bottom = "auto";
|
|
3378
3774
|
|
|
3379
3775
|
const popoverRect = this.popoverElement.getBoundingClientRect();
|
|
3380
3776
|
const isClippedAtBottom = popoverRect.bottom > window.innerHeight;
|
|
3381
3777
|
|
|
3382
|
-
if (isClippedAtBottom) {
|
|
3383
|
-
this.popoverElement.style.
|
|
3384
|
-
this.popoverElement.style.
|
|
3778
|
+
if (isClippedAtBottom || this.popoverElement.hasAttribute("data-clipped-at-bottom")) {
|
|
3779
|
+
this.popoverElement.style.top = `${y + verticalOffset - popoverRect.height - fontSize}px`;
|
|
3780
|
+
this.popoverElement.style.bottom = "auto";
|
|
3781
|
+
this.popoverElement.toggleAttribute("data-clipped-at-bottom", true);
|
|
3385
3782
|
}
|
|
3386
3783
|
}
|
|
3387
3784
|
|
|
3785
|
+
#resetPopoverPosition() {
|
|
3786
|
+
this.popoverElement.removeAttribute("data-clipped-at-bottom");
|
|
3787
|
+
this.popoverElement.removeAttribute("data-anchored");
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3388
3790
|
async #hidePopover() {
|
|
3389
3791
|
this.#clearSelection();
|
|
3390
3792
|
this.popoverElement.classList.toggle("lexxy-prompt-menu--visible", false);
|
|
@@ -3410,6 +3812,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
3410
3812
|
|
|
3411
3813
|
if (this.#editorContents.containsTextBackUntil(this.trigger)) {
|
|
3412
3814
|
await this.#showFilteredOptions();
|
|
3815
|
+
this.#positionPopover();
|
|
3413
3816
|
} else {
|
|
3414
3817
|
this.#hidePopover();
|
|
3415
3818
|
}
|