lexxy 0.9.19 → 0.9.21
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 +248 -69
- 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: 9a161b79f6c66266f236c0bae71c3ea540237a2a403fb2e047d43828e395bfda
|
|
4
|
+
data.tar.gz: '030840a6e183bc84837825ff7b8f646ebfb74fdd2d582c93c3475b38b88e633e'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c03106f2eed700e7521b00a937ace2b854459633b99ee93f6ef99eb323fd017d95fd9d348d174decc8a5e9e3b028fe9b470f5943066563b3ba6a55d03871c2b
|
|
7
|
+
data.tar.gz: b1d3121c8bc34f2b560192523f0e9a5ec885d869df2b9bdf56da88bd16829b8a5b9c0b0e99fa95507c06b65a1ca381665678df5f74dca94501b81937c658189a
|
|
@@ -6348,8 +6348,8 @@ function dispatch(element, eventName, detail = null, cancelable = false) {
|
|
|
6348
6348
|
}
|
|
6349
6349
|
|
|
6350
6350
|
function addBlockSpacing(doc) {
|
|
6351
|
-
const
|
|
6352
|
-
for (const block of
|
|
6351
|
+
const selector = "body > :not(h1, h2, h3, h4, h5, h6) + *, blockquote > :not(h1, h2, h3, h4, h5, h6) + *";
|
|
6352
|
+
for (const block of doc.querySelectorAll(selector)) {
|
|
6353
6353
|
const spacer = doc.createElement("p");
|
|
6354
6354
|
spacer.appendChild(doc.createElement("br"));
|
|
6355
6355
|
block.before(spacer);
|
|
@@ -8018,6 +8018,7 @@ function $splitSelectedParagraphsAtInnerLineBreaks(selection) {
|
|
|
8018
8018
|
|
|
8019
8019
|
function $expandSelectionToLineBreaksAndSplitAtEdges(selection, fallbackAncestor = (node) => node.getTopLevelElement()) {
|
|
8020
8020
|
j$6(selection);
|
|
8021
|
+
$shrinkSelectionPastBlockEdges(selection);
|
|
8021
8022
|
|
|
8022
8023
|
const focusCaret = ql(selection.focus, "next");
|
|
8023
8024
|
const anchorCaret = ql(selection.anchor, "previous");
|
|
@@ -8049,6 +8050,52 @@ function $expandSelectionToLineBreaksAndSplitAtEdges(selection, fallbackAncestor
|
|
|
8049
8050
|
));
|
|
8050
8051
|
}
|
|
8051
8052
|
|
|
8053
|
+
// A selection whose anchor sits at the very end of one block while its focus
|
|
8054
|
+
// lives in a later block (e.g. selecting a pasted paragraph when the browser
|
|
8055
|
+
// anchors at the end of the line above) contributes nothing from the anchor's
|
|
8056
|
+
// block. Pull each endpoint that is flush against a block edge into the block
|
|
8057
|
+
// that actually holds the selected content, so we don't wrap the empty edge
|
|
8058
|
+
// block too.
|
|
8059
|
+
function $shrinkSelectionPastBlockEdges(selection) {
|
|
8060
|
+
if (selection.isCollapsed()) return
|
|
8061
|
+
|
|
8062
|
+
const anchorBlock = selection.anchor.getNode().getTopLevelElement();
|
|
8063
|
+
const focusBlock = selection.focus.getNode().getTopLevelElement();
|
|
8064
|
+
if (!anchorBlock || !focusBlock || anchorBlock.is(focusBlock)) return
|
|
8065
|
+
|
|
8066
|
+
if ($isAtBlockEnd(selection.anchor, anchorBlock)) {
|
|
8067
|
+
const nextBlock = anchorBlock.getNextSibling();
|
|
8068
|
+
if (nextBlock) selection.anchor.set(nextBlock.getKey(), 0, "element");
|
|
8069
|
+
}
|
|
8070
|
+
|
|
8071
|
+
if ($isAtBlockStart(selection.focus, focusBlock)) {
|
|
8072
|
+
const previousBlock = focusBlock.getPreviousSibling();
|
|
8073
|
+
if (previousBlock) selection.focus.set(previousBlock.getKey(), previousBlock.getChildrenSize(), "element");
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
|
|
8077
|
+
function $isAtBlockEnd(point, block) {
|
|
8078
|
+
return $isAtBlockBoundary(ql(point, "next"), block)
|
|
8079
|
+
}
|
|
8080
|
+
|
|
8081
|
+
function $isAtBlockStart(point, block) {
|
|
8082
|
+
return $isAtBlockBoundary(ql(point, "previous"), block)
|
|
8083
|
+
}
|
|
8084
|
+
|
|
8085
|
+
// A text point sitting mid-node still has content ahead of it in the caret's
|
|
8086
|
+
// direction, even though that content is not a sibling node. $getNodeAtCaret
|
|
8087
|
+
// only sees siblings, so check the text edge before walking the block.
|
|
8088
|
+
function $isAtBlockBoundary(caret, block) {
|
|
8089
|
+
if (vl(caret) && ic(caret)) return false
|
|
8090
|
+
|
|
8091
|
+
let cursor = rc(caret);
|
|
8092
|
+
while (cursor && block.isParentOf(cursor.origin)) {
|
|
8093
|
+
if (cursor.getNodeAtCaret()) return false
|
|
8094
|
+
cursor = cursor.getParentCaret();
|
|
8095
|
+
}
|
|
8096
|
+
return true
|
|
8097
|
+
}
|
|
8098
|
+
|
|
8052
8099
|
function $getCaretAtLineBreakBoundary(caret, skipInwardEdge = false) {
|
|
8053
8100
|
const paragraph = caret.origin.getTopLevelElement();
|
|
8054
8101
|
if (!paragraph || !no(paragraph)) return null
|
|
@@ -9469,7 +9516,7 @@ function $registerPreConversion(editor) {
|
|
|
9469
9516
|
// and applied after retokenization via a mutation listener.
|
|
9470
9517
|
function $preConversionWithHighlightsFactory(editor) {
|
|
9471
9518
|
return function $preConversionWithHighlights(domNode) {
|
|
9472
|
-
const highlights = extractHighlightRanges
|
|
9519
|
+
const highlights = extractHighlightRanges(domNode);
|
|
9473
9520
|
if (highlights.length === 0) return null
|
|
9474
9521
|
|
|
9475
9522
|
return {
|
|
@@ -9486,7 +9533,7 @@ function $preConversionWithHighlightsFactory(editor) {
|
|
|
9486
9533
|
|
|
9487
9534
|
// Walk the DOM tree inside a <pre> element and build a list of
|
|
9488
9535
|
// { start, end, style } ranges for every <mark> element found.
|
|
9489
|
-
function extractHighlightRanges
|
|
9536
|
+
function extractHighlightRanges(preElement) {
|
|
9490
9537
|
const ranges = [];
|
|
9491
9538
|
const codeElement = preElement.querySelector("code") || preElement;
|
|
9492
9539
|
|
|
@@ -12019,6 +12066,7 @@ class PastedContentFormatter {
|
|
|
12019
12066
|
format() {
|
|
12020
12067
|
this.#unwrapPlaceholderAnchors();
|
|
12021
12068
|
this.#stripTableCellColorStyles();
|
|
12069
|
+
this.#nestStrayListChildren();
|
|
12022
12070
|
this.#stripStrayListChildren();
|
|
12023
12071
|
return this.doc
|
|
12024
12072
|
}
|
|
@@ -12047,6 +12095,22 @@ class PastedContentFormatter {
|
|
|
12047
12095
|
}
|
|
12048
12096
|
}
|
|
12049
12097
|
|
|
12098
|
+
// Some sources (e.g. Gmail) nest a sublist as a direct child of the parent
|
|
12099
|
+
// <ol>/<ul> instead of inside a <li>. Move each nested list into its
|
|
12100
|
+
// preceding <li> so the import preserves the nesting instead of dropping it.
|
|
12101
|
+
#nestStrayListChildren() {
|
|
12102
|
+
for (const list of this.doc.querySelectorAll("ol, ul")) {
|
|
12103
|
+
for (const child of Array.from(list.children)) {
|
|
12104
|
+
if (child.tagName !== "OL" && child.tagName !== "UL") continue
|
|
12105
|
+
|
|
12106
|
+
const previousItem = child.previousElementSibling;
|
|
12107
|
+
if (previousItem && previousItem.tagName === "LI") {
|
|
12108
|
+
previousItem.appendChild(child);
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
}
|
|
12113
|
+
|
|
12050
12114
|
// Only <li> is a valid child of a list; drop stray <br>/whitespace so the
|
|
12051
12115
|
// import doesn't wrap them into an empty leading item.
|
|
12052
12116
|
#stripStrayListChildren() {
|
|
@@ -12090,7 +12154,11 @@ class Contents {
|
|
|
12090
12154
|
|
|
12091
12155
|
insertText(text, { tag } = {}) {
|
|
12092
12156
|
this.editor.update(() => {
|
|
12093
|
-
const paragraph = eo()
|
|
12157
|
+
const paragraph = eo();
|
|
12158
|
+
text.split("\n").forEach((line, index) => {
|
|
12159
|
+
if (index > 0) paragraph.append(or());
|
|
12160
|
+
paragraph.append(kr(line));
|
|
12161
|
+
});
|
|
12094
12162
|
this.insertAtCursor(paragraph);
|
|
12095
12163
|
}, { tag });
|
|
12096
12164
|
}
|
|
@@ -12816,7 +12884,7 @@ class Clipboard {
|
|
|
12816
12884
|
}
|
|
12817
12885
|
|
|
12818
12886
|
if (this.#isPlainTextOrURLPasted(clipboardData)) {
|
|
12819
|
-
this.#
|
|
12887
|
+
this.#pastePlainTextOrURL(clipboardData);
|
|
12820
12888
|
event.preventDefault();
|
|
12821
12889
|
return true
|
|
12822
12890
|
}
|
|
@@ -12854,14 +12922,34 @@ class Clipboard {
|
|
|
12854
12922
|
return types.length === 1 && types[0] === "text/plain"
|
|
12855
12923
|
}
|
|
12856
12924
|
|
|
12925
|
+
// Browsers expose a copied URL in several shapes:
|
|
12926
|
+
// Safari [ text/plain, text/uri-list ]
|
|
12927
|
+
// App ShareSheet [ text/uri-list ]
|
|
12928
|
+
// Chromium macOS [ text/plain, text/html, (?:text/link-preview) ]
|
|
12857
12929
|
#isOnlyURLPasted(clipboardData) {
|
|
12858
|
-
|
|
12859
|
-
|
|
12930
|
+
if (this.#isLexicalClipboardData(clipboardData)) return false
|
|
12931
|
+
|
|
12860
12932
|
const types = Array.from(clipboardData.types);
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12933
|
+
if (types.includes("text/uri-list")) {
|
|
12934
|
+
return types.every(type => type === "text/plain" || type === "text/uri-list")
|
|
12935
|
+
}
|
|
12936
|
+
|
|
12937
|
+
if (clipboardData.files.length) return false
|
|
12938
|
+
|
|
12939
|
+
const text = clipboardData.getData("text/plain").trim();
|
|
12940
|
+
if (!isAutolinkableURL(text)) return false
|
|
12941
|
+
|
|
12942
|
+
const html = clipboardData.getData("text/html");
|
|
12943
|
+
return !html || this.#htmlIsBareLinkToURL(html, text)
|
|
12944
|
+
}
|
|
12945
|
+
|
|
12946
|
+
#htmlIsBareLinkToURL(html, url) {
|
|
12947
|
+
const doc = parseHtml(html);
|
|
12948
|
+
if (doc.body.textContent.trim() !== url) return false
|
|
12949
|
+
|
|
12950
|
+
const links = doc.body.querySelectorAll("a");
|
|
12951
|
+
if (links.length === 0) return true
|
|
12952
|
+
return links.length === 1 && links[0].getAttribute("href") === url
|
|
12865
12953
|
}
|
|
12866
12954
|
|
|
12867
12955
|
#isPastingIntoCodeBlock() {
|
|
@@ -12895,14 +12983,12 @@ class Clipboard {
|
|
|
12895
12983
|
}, { tag: jn });
|
|
12896
12984
|
}
|
|
12897
12985
|
|
|
12898
|
-
#
|
|
12899
|
-
const item = clipboardData
|
|
12986
|
+
#pastePlainTextOrURL(clipboardData) {
|
|
12987
|
+
const item = this.#plainTextOrURLItem(clipboardData);
|
|
12900
12988
|
item.getAsString((text) => {
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
const nodeKey = this.contents.createLink(text);
|
|
12905
|
-
this.#dispatchLinkInsertEvent(nodeKey, { url: text });
|
|
12989
|
+
const url = text.trim();
|
|
12990
|
+
if (isAutolinkableURL(url)) {
|
|
12991
|
+
this.#pasteURL(url);
|
|
12906
12992
|
} else if (this.editorElement.supportsMarkdown) {
|
|
12907
12993
|
this.#pasteMarkdown(text);
|
|
12908
12994
|
} else {
|
|
@@ -12911,6 +12997,20 @@ class Clipboard {
|
|
|
12911
12997
|
});
|
|
12912
12998
|
}
|
|
12913
12999
|
|
|
13000
|
+
#plainTextOrURLItem(clipboardData) {
|
|
13001
|
+
const items = Array.from(clipboardData.items);
|
|
13002
|
+
return items.find((item) => item.type === "text/plain") || items.find((item) => item.type === "text/uri-list")
|
|
13003
|
+
}
|
|
13004
|
+
|
|
13005
|
+
#pasteURL(url) {
|
|
13006
|
+
if (this.contents.hasSelectedText()) {
|
|
13007
|
+
this.contents.createLinkWithSelectedText(url);
|
|
13008
|
+
} else {
|
|
13009
|
+
const nodeKey = this.contents.createLink(url);
|
|
13010
|
+
this.#dispatchLinkInsertEvent(nodeKey, { url });
|
|
13011
|
+
}
|
|
13012
|
+
}
|
|
13013
|
+
|
|
12914
13014
|
#insertSingleLinkAt(selection, url) {
|
|
12915
13015
|
if (!Fr(selection)) return
|
|
12916
13016
|
|
|
@@ -12958,14 +13058,16 @@ class Clipboard {
|
|
|
12958
13058
|
|
|
12959
13059
|
// Markdown conversion collapses runs of whitespace and unescapes backslashes,
|
|
12960
13060
|
// silently corrupting plain text such as Windows/UNC file paths. When the text
|
|
12961
|
-
// carries no Markdown structure, paste it verbatim instead.
|
|
13061
|
+
// carries no Markdown structure, paste it verbatim instead. A path that wrapped
|
|
13062
|
+
// across lines renders as a single paragraph with <br> line breaks (marked runs
|
|
13063
|
+
// with breaks: true), which is still plain text we should preserve untouched.
|
|
12962
13064
|
#isPlainTextWithoutMarkdown(doc) {
|
|
12963
13065
|
const elements = Array.from(doc.body.children);
|
|
12964
13066
|
if (elements.length !== 1) return false
|
|
12965
13067
|
|
|
12966
13068
|
const paragraph = elements[0];
|
|
12967
13069
|
return paragraph.nodeName === "P"
|
|
12968
|
-
&& Array.from(paragraph.childNodes).every((node) => node.nodeType === Node.TEXT_NODE)
|
|
13070
|
+
&& Array.from(paragraph.childNodes).every((node) => node.nodeType === Node.TEXT_NODE || node.nodeName === "BR")
|
|
12969
13071
|
}
|
|
12970
13072
|
|
|
12971
13073
|
#pasteRichText(clipboardData) {
|
|
@@ -14253,24 +14355,46 @@ class FormatEscapeExtension extends LexxyExtension {
|
|
|
14253
14355
|
}
|
|
14254
14356
|
|
|
14255
14357
|
function $escapeFromBlockquote() {
|
|
14358
|
+
return $escapeBeforeBlockquoteStart() || $escapeFromBlankBlockquoteParagraph()
|
|
14359
|
+
}
|
|
14360
|
+
|
|
14361
|
+
function $escapeBeforeBlockquoteStart() {
|
|
14362
|
+
const selection = Qr();
|
|
14363
|
+
if (!Fr(selection) || !selection.isCollapsed() || selection.anchor.offset !== 0) return false
|
|
14364
|
+
|
|
14365
|
+
const paragraph = At$2(selection.anchor.getNode(), Zi);
|
|
14366
|
+
if (paragraph && !$isBlankNode(paragraph) && !paragraph.getPreviousSibling()) {
|
|
14367
|
+
const blockquote = paragraph.getParent();
|
|
14368
|
+
if (Mt$2(blockquote)) {
|
|
14369
|
+
blockquote.insertBefore(eo());
|
|
14370
|
+
return true
|
|
14371
|
+
}
|
|
14372
|
+
}
|
|
14373
|
+
|
|
14374
|
+
return false
|
|
14375
|
+
}
|
|
14376
|
+
|
|
14377
|
+
function $escapeFromBlankBlockquoteParagraph() {
|
|
14256
14378
|
const anchorNode = Qr().anchor.getNode();
|
|
14257
14379
|
|
|
14258
14380
|
const paragraph = At$2(anchorNode, Zi);
|
|
14259
14381
|
if (!paragraph || !$isBlankNode(paragraph)) return false
|
|
14260
14382
|
|
|
14261
14383
|
const blockquote = paragraph.getParent();
|
|
14262
|
-
if (
|
|
14384
|
+
if (Mt$2(blockquote)) {
|
|
14385
|
+
const nonEmptySiblings = paragraph.getNextSiblings().filter(sibling => !$isBlankNode(sibling));
|
|
14263
14386
|
|
|
14264
|
-
|
|
14387
|
+
if (nonEmptySiblings.length > 0) {
|
|
14388
|
+
$splitQuoteNode(blockquote, paragraph);
|
|
14389
|
+
} else {
|
|
14390
|
+
blockquote.insertAfter(paragraph);
|
|
14391
|
+
paragraph.selectStart();
|
|
14392
|
+
}
|
|
14265
14393
|
|
|
14266
|
-
|
|
14267
|
-
$splitQuoteNode(blockquote, paragraph);
|
|
14268
|
-
} else {
|
|
14269
|
-
blockquote.insertAfter(paragraph);
|
|
14270
|
-
paragraph.selectStart();
|
|
14394
|
+
return true
|
|
14271
14395
|
}
|
|
14272
14396
|
|
|
14273
|
-
return
|
|
14397
|
+
return false
|
|
14274
14398
|
}
|
|
14275
14399
|
|
|
14276
14400
|
function $splitQuoteNode(node, paragraph) {
|
|
@@ -14528,6 +14652,15 @@ class CustomAttachmentDragAndDrop {
|
|
|
14528
14652
|
// they only drop onto an existing line, so snap to the nearest one.
|
|
14529
14653
|
if (caret.node === rootElement) {
|
|
14530
14654
|
return this.#nearestLineCaret(rootElement, event.clientY)
|
|
14655
|
+
}
|
|
14656
|
+
|
|
14657
|
+
// When mentions sit next to each other with no text between them, the caret
|
|
14658
|
+
// lands inside the neighbouring decorator's DOM. Lexical can't resolve a point
|
|
14659
|
+
// inside a decorator to an editable position, and the cursor has no business
|
|
14660
|
+
// showing there anyway, so snap to just before or after that mention.
|
|
14661
|
+
const decorator = this.#decoratorElementContaining(caret.node);
|
|
14662
|
+
if (decorator) {
|
|
14663
|
+
return this.#dropPointBesideDecorator(decorator, event.clientX)
|
|
14531
14664
|
} else {
|
|
14532
14665
|
return caret
|
|
14533
14666
|
}
|
|
@@ -14556,24 +14689,55 @@ class CustomAttachmentDragAndDrop {
|
|
|
14556
14689
|
}
|
|
14557
14690
|
}
|
|
14558
14691
|
|
|
14692
|
+
#decoratorElementContaining(node) {
|
|
14693
|
+
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
14694
|
+
return element?.closest("[data-lexxy-decorator][data-lexical-node-key]")
|
|
14695
|
+
}
|
|
14696
|
+
|
|
14697
|
+
#dropPointBesideDecorator(decorator, clientX) {
|
|
14698
|
+
const rect = decorator.getBoundingClientRect();
|
|
14699
|
+
const placement = clientX > rect.left + rect.width / 2 ? "after" : "before";
|
|
14700
|
+
return { decoratorKey: decorator.dataset.lexicalNodeKey, placement }
|
|
14701
|
+
}
|
|
14702
|
+
|
|
14559
14703
|
#moveAttachment(draggedKey, dropPoint) {
|
|
14560
14704
|
this.#editor.update(() => {
|
|
14561
14705
|
const draggedNode = Yo(draggedKey);
|
|
14562
14706
|
if (!$isCustomActionTextAttachmentNode(draggedNode)) return
|
|
14563
14707
|
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14708
|
+
if (dropPoint.decoratorKey) {
|
|
14709
|
+
this.#moveBesideNode(draggedNode, dropPoint);
|
|
14710
|
+
} else {
|
|
14711
|
+
this.#moveToCaret(draggedNode, dropPoint);
|
|
14712
|
+
}
|
|
14713
|
+
});
|
|
14714
|
+
}
|
|
14571
14715
|
|
|
14572
|
-
|
|
14716
|
+
#moveBesideNode(draggedNode, { decoratorKey, placement }) {
|
|
14717
|
+
const targetNode = Yo(decoratorKey);
|
|
14718
|
+
if (!targetNode || targetNode === draggedNode) return
|
|
14573
14719
|
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14720
|
+
draggedNode.remove();
|
|
14721
|
+
if (placement === "after") {
|
|
14722
|
+
targetNode.insertAfter(draggedNode);
|
|
14723
|
+
} else {
|
|
14724
|
+
targetNode.insertBefore(draggedNode);
|
|
14725
|
+
}
|
|
14726
|
+
}
|
|
14727
|
+
|
|
14728
|
+
#moveToCaret(draggedNode, dropPoint) {
|
|
14729
|
+
const selection = Gr({
|
|
14730
|
+
anchorNode: dropPoint.node,
|
|
14731
|
+
anchorOffset: dropPoint.offset,
|
|
14732
|
+
focusNode: dropPoint.node,
|
|
14733
|
+
focusOffset: dropPoint.offset
|
|
14734
|
+
}, this.#editor);
|
|
14735
|
+
if (!selection) return
|
|
14736
|
+
|
|
14737
|
+
es(selection);
|
|
14738
|
+
|
|
14739
|
+
draggedNode.remove();
|
|
14740
|
+
selection.insertNodes([ draggedNode ]);
|
|
14577
14741
|
}
|
|
14578
14742
|
|
|
14579
14743
|
#updateDropIndicator(event) {
|
|
@@ -14583,7 +14747,12 @@ class CustomAttachmentDragAndDrop {
|
|
|
14583
14747
|
if (dropPoint) this.#showCaret(this.#caretRectFor(dropPoint));
|
|
14584
14748
|
}
|
|
14585
14749
|
|
|
14586
|
-
#caretRectFor(
|
|
14750
|
+
#caretRectFor(dropPoint) {
|
|
14751
|
+
if (dropPoint.decoratorKey) {
|
|
14752
|
+
return this.#decoratorEdgeRect(dropPoint)
|
|
14753
|
+
}
|
|
14754
|
+
|
|
14755
|
+
const { node, offset } = dropPoint;
|
|
14587
14756
|
const rect = caretRect(node, offset);
|
|
14588
14757
|
if (rect) return rect
|
|
14589
14758
|
|
|
@@ -14595,6 +14764,15 @@ class CustomAttachmentDragAndDrop {
|
|
|
14595
14764
|
return { left: lineRect.left, top: lineRect.top, height: lineRect.height }
|
|
14596
14765
|
}
|
|
14597
14766
|
|
|
14767
|
+
#decoratorEdgeRect({ decoratorKey, placement }) {
|
|
14768
|
+
const decorator = this.#editor.getRootElement()?.querySelector(`[data-lexical-node-key="${decoratorKey}"]`);
|
|
14769
|
+
if (!decorator) return null
|
|
14770
|
+
|
|
14771
|
+
const rect = decorator.getBoundingClientRect();
|
|
14772
|
+
const left = placement === "after" ? rect.right : rect.left;
|
|
14773
|
+
return { left, top: rect.top, height: rect.height }
|
|
14774
|
+
}
|
|
14775
|
+
|
|
14598
14776
|
#showCaret(rect) {
|
|
14599
14777
|
if (!rect) return
|
|
14600
14778
|
|
|
@@ -17209,16 +17387,16 @@ function highlightElement(preElement) {
|
|
|
17209
17387
|
if (preElement.dataset.highlighted === "true") return
|
|
17210
17388
|
|
|
17211
17389
|
const language = preElement.getAttribute("data-language");
|
|
17212
|
-
let code = preElement.innerHTML.replace(/<br\s*\/?>/gi, "\n");
|
|
17213
17390
|
|
|
17214
17391
|
const grammar = Prism$1.languages?.[language];
|
|
17215
17392
|
if (!grammar) return
|
|
17216
17393
|
|
|
17217
|
-
//
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
//
|
|
17221
|
-
|
|
17394
|
+
// Read the source text and <mark> ranges in a single walk, before Prism
|
|
17395
|
+
// rewrites the element. Sharing one traversal keeps the highlight offsets
|
|
17396
|
+
// aligned with the code string and preserves leading whitespace — deriving
|
|
17397
|
+
// either of them separately (e.g. textContent through DOMParser) collapses
|
|
17398
|
+
// leading whitespace and shifts every range, re-indenting the rendered block.
|
|
17399
|
+
const { code, highlights } = extractCodeAndHighlights(preElement);
|
|
17222
17400
|
|
|
17223
17401
|
const highlightedHtml = Prism$1.highlight(code, grammar, language);
|
|
17224
17402
|
preElement.innerHTML = highlightedHtml;
|
|
@@ -17230,34 +17408,35 @@ function highlightElement(preElement) {
|
|
|
17230
17408
|
preElement.dataset.highlighted = "true";
|
|
17231
17409
|
}
|
|
17232
17410
|
|
|
17233
|
-
// Walk the
|
|
17234
|
-
//
|
|
17235
|
-
|
|
17236
|
-
|
|
17411
|
+
// Walk the <pre> once, building Prism's source text and the <mark> ranges
|
|
17412
|
+
// together: a text node contributes its text verbatim, a <br> contributes a
|
|
17413
|
+
// newline, and a <mark> records the slice of code it covers. Because both
|
|
17414
|
+
// outputs come from the same walk, every range offset is just a position in
|
|
17415
|
+
// `code` — so the highlights can't drift out of sync with the source, and the
|
|
17416
|
+
// block's leading whitespace survives (HTML parsing would collapse it).
|
|
17417
|
+
function extractCodeAndHighlights(preElement) {
|
|
17237
17418
|
const root = preElement.querySelector("code") || preElement;
|
|
17238
|
-
|
|
17239
|
-
let
|
|
17419
|
+
const highlights = [];
|
|
17420
|
+
let code = "";
|
|
17240
17421
|
|
|
17241
17422
|
function walk(node) {
|
|
17242
17423
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
17243
|
-
|
|
17424
|
+
code += node.textContent;
|
|
17244
17425
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
17245
17426
|
if (node.tagName === "BR") {
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
for (const child of node.childNodes) {
|
|
17254
|
-
walk(child);
|
|
17255
|
-
}
|
|
17256
|
-
|
|
17257
|
-
if (isMark) {
|
|
17427
|
+
code += "\n";
|
|
17428
|
+
} else if (node.tagName === "MARK") {
|
|
17429
|
+
const start = code.length;
|
|
17430
|
+
for (const child of node.childNodes) {
|
|
17431
|
+
walk(child);
|
|
17432
|
+
}
|
|
17258
17433
|
const style = extractStyle(node);
|
|
17259
17434
|
if (style) {
|
|
17260
|
-
|
|
17435
|
+
highlights.push({ start, end: code.length, style });
|
|
17436
|
+
}
|
|
17437
|
+
} else {
|
|
17438
|
+
for (const child of node.childNodes) {
|
|
17439
|
+
walk(child);
|
|
17261
17440
|
}
|
|
17262
17441
|
}
|
|
17263
17442
|
}
|
|
@@ -17267,7 +17446,7 @@ function extractHighlightRanges(preElement) {
|
|
|
17267
17446
|
walk(child);
|
|
17268
17447
|
}
|
|
17269
17448
|
|
|
17270
|
-
return
|
|
17449
|
+
return { code, highlights }
|
|
17271
17450
|
}
|
|
17272
17451
|
|
|
17273
17452
|
function extractStyle(element) {
|
|
@@ -17436,5 +17615,5 @@ const configure = Lexxy.configure;
|
|
|
17436
17615
|
// Pushing elements definition to after the current call stack to allow global configuration to take place first
|
|
17437
17616
|
setTimeout(defineElements, 0);
|
|
17438
17617
|
|
|
17439
|
-
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, $isCustomActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, configure, highlightCode, highlightElement };
|
|
17618
|
+
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, $isCustomActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, REWRITE_HISTORY_COMMAND, configure, highlightCode, highlightElement };
|
|
17440
17619
|
//# sourceMappingURL=lexxy.js.map
|
|
Binary file
|
|
Binary file
|