lexxy 0.9.19.alpha.2 → 0.9.19
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 +520 -112
- 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/app/assets/stylesheets/lexxy-editor.css +17 -1
- data/lib/lexxy/version.rb +1 -1
- metadata +1 -1
|
@@ -6301,6 +6301,14 @@ class ListenerBin {
|
|
|
6301
6301
|
}
|
|
6302
6302
|
}
|
|
6303
6303
|
|
|
6304
|
+
function handlingDefault(handler) {
|
|
6305
|
+
return event => {
|
|
6306
|
+
const handled = handler(event);
|
|
6307
|
+
if (handled) event.preventDefault();
|
|
6308
|
+
return handled
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
|
|
6304
6312
|
function createElement(name, properties, content = "") {
|
|
6305
6313
|
const element = document.createElement(name);
|
|
6306
6314
|
for (const [ key, value ] of Object.entries(properties || {})) {
|
|
@@ -7642,7 +7650,8 @@ class CustomActionTextAttachmentNode extends Ji {
|
|
|
7642
7650
|
}
|
|
7643
7651
|
|
|
7644
7652
|
createDOM() {
|
|
7645
|
-
const figure = createElement(this.tagName, { "content-type": this.contentType, "data-lexxy-decorator": true });
|
|
7653
|
+
const figure = createElement(this.tagName, { "content-type": this.contentType, "data-lexxy-decorator": true, draggable: true });
|
|
7654
|
+
figure.dataset.lexicalNodeKey = this.__key;
|
|
7646
7655
|
|
|
7647
7656
|
figure.insertAdjacentHTML("beforeend", sanitize(this.innerHtml));
|
|
7648
7657
|
|
|
@@ -7695,6 +7704,10 @@ class CustomActionTextAttachmentNode extends Ji {
|
|
|
7695
7704
|
}
|
|
7696
7705
|
}
|
|
7697
7706
|
|
|
7707
|
+
function $isCustomActionTextAttachmentNode(node) {
|
|
7708
|
+
return node instanceof CustomActionTextAttachmentNode
|
|
7709
|
+
}
|
|
7710
|
+
|
|
7698
7711
|
function dasherize(value) {
|
|
7699
7712
|
return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`)
|
|
7700
7713
|
}
|
|
@@ -9189,6 +9202,30 @@ var theme = {
|
|
|
9189
9202
|
}
|
|
9190
9203
|
};
|
|
9191
9204
|
|
|
9205
|
+
class UploadRequests {
|
|
9206
|
+
#requestsByKey = new Map()
|
|
9207
|
+
|
|
9208
|
+
track(key, request) {
|
|
9209
|
+
this.#requestsByKey.set(key, request);
|
|
9210
|
+
}
|
|
9211
|
+
|
|
9212
|
+
forget(key) {
|
|
9213
|
+
this.#requestsByKey.delete(key);
|
|
9214
|
+
}
|
|
9215
|
+
|
|
9216
|
+
abort(key) {
|
|
9217
|
+
const request = this.#requestsByKey.get(key);
|
|
9218
|
+
if (request) {
|
|
9219
|
+
this.#requestsByKey.delete(key);
|
|
9220
|
+
request.abort();
|
|
9221
|
+
}
|
|
9222
|
+
}
|
|
9223
|
+
|
|
9224
|
+
clear() {
|
|
9225
|
+
this.#requestsByKey.clear();
|
|
9226
|
+
}
|
|
9227
|
+
}
|
|
9228
|
+
|
|
9192
9229
|
/**
|
|
9193
9230
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
9194
9231
|
*
|
|
@@ -10222,7 +10259,7 @@ class CommandDispatcher {
|
|
|
10222
10259
|
}
|
|
10223
10260
|
|
|
10224
10261
|
#isInternalDrag(event) {
|
|
10225
|
-
return event.dataTransfer?.types.
|
|
10262
|
+
return event.dataTransfer?.types.some((type) => type.startsWith("application/x-lexxy-"))
|
|
10226
10263
|
}
|
|
10227
10264
|
|
|
10228
10265
|
#handleTabKey(event) {
|
|
@@ -10276,14 +10313,14 @@ class Selection {
|
|
|
10276
10313
|
}
|
|
10277
10314
|
|
|
10278
10315
|
get cursorPosition() {
|
|
10279
|
-
let position =
|
|
10316
|
+
let position = null;
|
|
10280
10317
|
|
|
10281
10318
|
this.editor.getEditorState().read(() => {
|
|
10282
10319
|
const range = this.#getValidSelectionRange();
|
|
10283
10320
|
if (!range) return
|
|
10284
10321
|
|
|
10285
10322
|
const rect = this.#getReliableRectFromRange(range);
|
|
10286
|
-
if (
|
|
10323
|
+
if (this.#isRectUnreliable(rect)) return
|
|
10287
10324
|
|
|
10288
10325
|
position = this.#calculateCursorPosition(rect, range);
|
|
10289
10326
|
});
|
|
@@ -10574,10 +10611,10 @@ class Selection {
|
|
|
10574
10611
|
|
|
10575
10612
|
#processSelectionChangeCommands() {
|
|
10576
10613
|
this.#listeners.track(
|
|
10577
|
-
this.editor.registerCommand(Ne$1, this.#selectPreviousNode.bind(this), io),
|
|
10578
|
-
this.editor.registerCommand(ke$2, this.#selectNextNode.bind(this), io),
|
|
10579
|
-
this.editor.registerCommand(we$1, this.#selectPreviousTopLevelNode.bind(this), io),
|
|
10580
|
-
this.editor.registerCommand(Ee$3, this.#selectNextTopLevelNode.bind(this), io),
|
|
10614
|
+
this.editor.registerCommand(Ne$1, handlingDefault(this.#selectPreviousNode.bind(this)), io),
|
|
10615
|
+
this.editor.registerCommand(ke$2, handlingDefault(this.#selectNextNode.bind(this)), io),
|
|
10616
|
+
this.editor.registerCommand(we$1, handlingDefault(this.#selectPreviousTopLevelNode.bind(this)), io),
|
|
10617
|
+
this.editor.registerCommand(Ee$3, handlingDefault(this.#selectNextTopLevelNode.bind(this)), io),
|
|
10581
10618
|
|
|
10582
10619
|
this.editor.registerCommand(fe$4, this.#selectDecoratorNodeBeforeDeletion.bind(this), io),
|
|
10583
10620
|
|
|
@@ -10676,49 +10713,58 @@ class Selection {
|
|
|
10676
10713
|
}
|
|
10677
10714
|
}
|
|
10678
10715
|
|
|
10679
|
-
|
|
10680
|
-
if (event
|
|
10716
|
+
#selectPreviousNode(event) {
|
|
10717
|
+
if (event.shiftKey) {
|
|
10718
|
+
return this.#withCurrentNodeSelectionNode((currentNode) => {
|
|
10719
|
+
const selection = this.#rangeSelectDecorator(currentNode, "forward");
|
|
10681
10720
|
|
|
10682
|
-
|
|
10683
|
-
|
|
10721
|
+
// Can't rely on native pass-through with Playwright on firefox
|
|
10722
|
+
selection.modify("extend", true, "character");
|
|
10723
|
+
return true
|
|
10724
|
+
})
|
|
10684
10725
|
} else {
|
|
10685
|
-
return this.#
|
|
10726
|
+
return this.#withCurrentNodeSelectionNode((currentNode) => currentNode.selectPrevious())
|
|
10727
|
+
|| this.#selectInLexical(this.nodeBeforeCursor)
|
|
10686
10728
|
}
|
|
10687
10729
|
}
|
|
10688
10730
|
|
|
10689
|
-
|
|
10690
|
-
if (event
|
|
10731
|
+
#selectNextNode(event) {
|
|
10732
|
+
if (event.shiftKey) {
|
|
10733
|
+
return this.#withCurrentNodeSelectionNode((currentNode) => {
|
|
10734
|
+
const selection = this.#rangeSelectDecorator(currentNode, "forward");
|
|
10691
10735
|
|
|
10692
|
-
|
|
10693
|
-
|
|
10736
|
+
// Can't rely on native pass-through with Playwright on firefox
|
|
10737
|
+
selection.modify("extend", false, "character");
|
|
10738
|
+
return true
|
|
10739
|
+
})
|
|
10694
10740
|
} else {
|
|
10695
|
-
return this.#
|
|
10741
|
+
return this.#withCurrentNodeSelectionNode((currentNode) => currentNode.selectNext(0, 0))
|
|
10742
|
+
|| this.#selectInLexical(this.nodeAfterCursor)
|
|
10696
10743
|
}
|
|
10697
10744
|
}
|
|
10698
10745
|
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10746
|
+
#selectPreviousTopLevelNode() {
|
|
10747
|
+
return this.#withCurrentNodeSelectionNode((currentNode) => currentNode.getTopLevelElement().selectPrevious())
|
|
10748
|
+
|| this.#selectInLexical(this.topLevelNodeBeforeCursor)
|
|
10749
|
+
}
|
|
10750
|
+
|
|
10751
|
+
#selectNextTopLevelNode() {
|
|
10752
|
+
return this.#withCurrentNodeSelectionNode((currentNode) => currentNode.getTopLevelElement().selectNext(0, 0))
|
|
10753
|
+
|| this.#selectInLexical(this.topLevelNodeAfterCursor)
|
|
10705
10754
|
}
|
|
10706
10755
|
|
|
10707
|
-
|
|
10756
|
+
#withCurrentNodeSelectionNode(fn) {
|
|
10708
10757
|
if (this.hasNodeSelection) {
|
|
10709
|
-
return
|
|
10710
|
-
} else {
|
|
10711
|
-
return this.#selectInLexical(this.topLevelNodeAfterCursor)
|
|
10758
|
+
return fn(Qr().getNodes()[0])
|
|
10712
10759
|
}
|
|
10713
10760
|
}
|
|
10714
10761
|
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
});
|
|
10762
|
+
#rangeSelectDecorator(node, direction = "forward") {
|
|
10763
|
+
if (ji(node)) {
|
|
10764
|
+
const [ anchorOffset, focusOffset ] = direction === "forward" ? [ 0, 1 ] : [ 1, 0 ];
|
|
10765
|
+
const indexAtNode = node.getIndexWithinParent();
|
|
10766
|
+
|
|
10767
|
+
return node.getParent().select(indexAtNode + anchorOffset, indexAtNode + focusOffset)
|
|
10722
10768
|
}
|
|
10723
10769
|
}
|
|
10724
10770
|
|
|
@@ -11340,9 +11386,9 @@ function $findOrCreateGalleryForImage(node) {
|
|
|
11340
11386
|
class Uploader {
|
|
11341
11387
|
#files
|
|
11342
11388
|
|
|
11343
|
-
static for(editorElement, files) {
|
|
11389
|
+
static for(editorElement, files, options = {}) {
|
|
11344
11390
|
const UploaderKlass = GalleryUploader.handle(editorElement, files) ? GalleryUploader : Uploader;
|
|
11345
|
-
return new UploaderKlass(editorElement, files)
|
|
11391
|
+
return new UploaderKlass(editorElement, files, options)
|
|
11346
11392
|
}
|
|
11347
11393
|
|
|
11348
11394
|
constructor(editorElement, files, options = {}) {
|
|
@@ -11364,7 +11410,13 @@ class Uploader {
|
|
|
11364
11410
|
}
|
|
11365
11411
|
|
|
11366
11412
|
$createUploadNodes() {
|
|
11367
|
-
this.nodes = this.files.map(file => this
|
|
11413
|
+
this.nodes = this.files.map(file => this.#createUploadNode(file));
|
|
11414
|
+
}
|
|
11415
|
+
|
|
11416
|
+
#createUploadNode(file) {
|
|
11417
|
+
return this.options.pending
|
|
11418
|
+
? this.contents.$createPendingUploadNode(file)
|
|
11419
|
+
: this.contents.$createUploadNode(file)
|
|
11368
11420
|
}
|
|
11369
11421
|
|
|
11370
11422
|
$insertUploadNodes() {
|
|
@@ -11622,6 +11674,8 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
11622
11674
|
this.#dispatchEvent("lexxy:upload-start", { file: this.file });
|
|
11623
11675
|
|
|
11624
11676
|
upload.create((error, blob) => {
|
|
11677
|
+
this.#forgetUploadRequest();
|
|
11678
|
+
|
|
11625
11679
|
if (error) {
|
|
11626
11680
|
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error });
|
|
11627
11681
|
this.#handleUploadError(error);
|
|
@@ -11644,12 +11698,26 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
11644
11698
|
directUploadWillStoreFileWithXHR: (request) => {
|
|
11645
11699
|
if (shouldAuthenticateUploads) request.withCredentials = true;
|
|
11646
11700
|
|
|
11701
|
+
this.#rememberUploadRequest(request);
|
|
11702
|
+
|
|
11647
11703
|
const uploadProgressHandler = (event) => this.#handleUploadProgress(event, request);
|
|
11648
11704
|
request.upload.addEventListener("progress", uploadProgressHandler);
|
|
11649
11705
|
}
|
|
11650
11706
|
}
|
|
11651
11707
|
}
|
|
11652
11708
|
|
|
11709
|
+
#forgetUploadRequest() {
|
|
11710
|
+
this.#editorElement.uploadRequests.forget(this.getKey());
|
|
11711
|
+
}
|
|
11712
|
+
|
|
11713
|
+
#rememberUploadRequest(request) {
|
|
11714
|
+
this.#editorElement.uploadRequests.track(this.getKey(), request);
|
|
11715
|
+
}
|
|
11716
|
+
|
|
11717
|
+
get #editorElement() {
|
|
11718
|
+
return this.editor.getRootElement()?.closest("lexxy-editor")
|
|
11719
|
+
}
|
|
11720
|
+
|
|
11653
11721
|
#setUploadStarted() {
|
|
11654
11722
|
this.#setProgress(1);
|
|
11655
11723
|
}
|
|
@@ -11822,7 +11890,7 @@ class ShadowRootNodeInserter extends BaseNodeInserter {
|
|
|
11822
11890
|
|
|
11823
11891
|
class NodeSelectionNodeInserter extends BaseNodeInserter {
|
|
11824
11892
|
static handles(selection) {
|
|
11825
|
-
return Ir(selection)
|
|
11893
|
+
return Ir(selection) && selection.getNodes().length > 0
|
|
11826
11894
|
}
|
|
11827
11895
|
|
|
11828
11896
|
insertNodes(nodes) {
|
|
@@ -12020,18 +12088,20 @@ class Contents {
|
|
|
12020
12088
|
}, { tag });
|
|
12021
12089
|
}
|
|
12022
12090
|
|
|
12091
|
+
insertText(text, { tag } = {}) {
|
|
12092
|
+
this.editor.update(() => {
|
|
12093
|
+
const paragraph = eo().append(kr(text));
|
|
12094
|
+
this.insertAtCursor(paragraph);
|
|
12095
|
+
}, { tag });
|
|
12096
|
+
}
|
|
12097
|
+
|
|
12023
12098
|
insertAtCursor(...nodes) {
|
|
12024
|
-
const selection =
|
|
12099
|
+
const selection = this.#insertableSelection();
|
|
12025
12100
|
const inserter = BaseNodeInserter.for(selection);
|
|
12026
12101
|
|
|
12027
12102
|
inserter.insertNodes(nodes);
|
|
12028
12103
|
}
|
|
12029
12104
|
|
|
12030
|
-
insertAtCursorEnsuringLineBelow(node) {
|
|
12031
|
-
this.insertAtCursor(node);
|
|
12032
|
-
this.#insertLineBelowIfLastNode(node);
|
|
12033
|
-
}
|
|
12034
|
-
|
|
12035
12105
|
applyParagraphFormat() {
|
|
12036
12106
|
const selection = Qr();
|
|
12037
12107
|
if (!Fr(selection)) return
|
|
@@ -12173,17 +12243,27 @@ class Contents {
|
|
|
12173
12243
|
const fullText = anchorNode.getTextContent();
|
|
12174
12244
|
const offset = anchor.offset;
|
|
12175
12245
|
|
|
12176
|
-
const
|
|
12177
|
-
|
|
12178
|
-
const lastIndex = textBeforeCursor.lastIndexOf(string);
|
|
12246
|
+
const lastIndex = fullText.slice(0, offset).lastIndexOf(string);
|
|
12179
12247
|
if (lastIndex !== -1) {
|
|
12180
|
-
result =
|
|
12248
|
+
result = fullText.slice(lastIndex + string.length, this.#endOffsetAt(fullText, offset));
|
|
12181
12249
|
}
|
|
12182
12250
|
});
|
|
12183
12251
|
|
|
12184
12252
|
return result
|
|
12185
12253
|
}
|
|
12186
12254
|
|
|
12255
|
+
// The query runs from the trigger up to the next whitespace, even when the
|
|
12256
|
+
// cursor sits inside an existing word — inserting "@" before "Jack" must
|
|
12257
|
+
// filter by "Jack" rather than treating the prompt as empty.
|
|
12258
|
+
#endOffsetAt(fullText, cursorOffset) {
|
|
12259
|
+
const whitespaceOffset = fullText.slice(cursorOffset).search(/\s/);
|
|
12260
|
+
if (whitespaceOffset === -1) {
|
|
12261
|
+
return fullText.length
|
|
12262
|
+
} else {
|
|
12263
|
+
return cursorOffset + whitespaceOffset
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
|
|
12187
12267
|
containsTextBackUntil(string) {
|
|
12188
12268
|
let result = false;
|
|
12189
12269
|
|
|
@@ -12210,14 +12290,13 @@ class Contents {
|
|
|
12210
12290
|
replaceTextBackUntil(stringToReplace, replacementNodes) {
|
|
12211
12291
|
replacementNodes = Array.isArray(replacementNodes) ? replacementNodes : [ replacementNodes ];
|
|
12212
12292
|
|
|
12213
|
-
const selection = Qr();
|
|
12214
12293
|
const { anchorNode, offset } = this.#getTextAnchorData();
|
|
12215
12294
|
if (!anchorNode) return
|
|
12216
12295
|
|
|
12217
|
-
const lastIndex = this.#
|
|
12296
|
+
const lastIndex = this.#findReplacementStart(anchorNode, offset, stringToReplace);
|
|
12218
12297
|
if (lastIndex === -1) return
|
|
12219
12298
|
|
|
12220
|
-
this.#performTextReplacement(anchorNode,
|
|
12299
|
+
this.#performTextReplacement(anchorNode, lastIndex, stringToReplace, replacementNodes);
|
|
12221
12300
|
}
|
|
12222
12301
|
|
|
12223
12302
|
uploadFiles(files, { selectLast } = {}) {
|
|
@@ -12250,24 +12329,46 @@ class Contents {
|
|
|
12250
12329
|
})
|
|
12251
12330
|
}
|
|
12252
12331
|
|
|
12332
|
+
$createPendingUploadNode(file) {
|
|
12333
|
+
return $createActionTextAttachmentUploadNode({
|
|
12334
|
+
file,
|
|
12335
|
+
uploadUrl: null,
|
|
12336
|
+
blobUrlTemplate: this.editorElement.blobUrlTemplate,
|
|
12337
|
+
contentType: file.type,
|
|
12338
|
+
})
|
|
12339
|
+
}
|
|
12340
|
+
|
|
12253
12341
|
insertPendingAttachment(file) {
|
|
12254
12342
|
if (!this.editorElement.supportsAttachments) return null
|
|
12255
12343
|
|
|
12256
12344
|
let nodeKey = null;
|
|
12257
12345
|
this.editor.update(() => {
|
|
12258
|
-
const uploadNode =
|
|
12259
|
-
file,
|
|
12260
|
-
uploadUrl: null,
|
|
12261
|
-
blobUrlTemplate: this.editorElement.blobUrlTemplate,
|
|
12262
|
-
editor: this.editor
|
|
12263
|
-
});
|
|
12346
|
+
const uploadNode = this.$createPendingUploadNode(file);
|
|
12264
12347
|
this.insertAtCursor(uploadNode);
|
|
12265
12348
|
nodeKey = uploadNode.getKey();
|
|
12266
|
-
}
|
|
12349
|
+
});
|
|
12267
12350
|
|
|
12268
|
-
|
|
12351
|
+
return nodeKey ? this.#pendingAttachmentHandle(nodeKey) : null
|
|
12352
|
+
}
|
|
12269
12353
|
|
|
12354
|
+
insertPendingAttachments(files) {
|
|
12355
|
+
const fileList = Array.from(files);
|
|
12356
|
+
if (!this.editorElement.supportsAttachments || fileList.length === 0) return []
|
|
12357
|
+
|
|
12358
|
+
let nodeKeys = [];
|
|
12359
|
+
this.editor.update(() => {
|
|
12360
|
+
const uploader = Uploader.for(this.editorElement, fileList, { pending: true });
|
|
12361
|
+
uploader.$uploadFiles();
|
|
12362
|
+
nodeKeys = (uploader.nodes ?? []).map(node => node.getKey());
|
|
12363
|
+
});
|
|
12364
|
+
|
|
12365
|
+
return nodeKeys.map(nodeKey => this.#pendingAttachmentHandle(nodeKey))
|
|
12366
|
+
}
|
|
12367
|
+
|
|
12368
|
+
#pendingAttachmentHandle(initialNodeKey) {
|
|
12270
12369
|
const editor = this.editor;
|
|
12370
|
+
let nodeKey = initialNodeKey;
|
|
12371
|
+
|
|
12271
12372
|
return {
|
|
12272
12373
|
setAttributes(blob) {
|
|
12273
12374
|
editor.update(() => {
|
|
@@ -12335,6 +12436,15 @@ class Contents {
|
|
|
12335
12436
|
});
|
|
12336
12437
|
}
|
|
12337
12438
|
|
|
12439
|
+
#insertableSelection() {
|
|
12440
|
+
const selection = Qr();
|
|
12441
|
+
if (Ir(selection) && selection.getNodes().length === 0) {
|
|
12442
|
+
return Zo().selectEnd()
|
|
12443
|
+
}
|
|
12444
|
+
|
|
12445
|
+
return selection ?? Zo().selectEnd()
|
|
12446
|
+
}
|
|
12447
|
+
|
|
12338
12448
|
#formatPastedDOM(doc) {
|
|
12339
12449
|
new PastedContentFormatter(doc).format();
|
|
12340
12450
|
}
|
|
@@ -12472,17 +12582,6 @@ class Contents {
|
|
|
12472
12582
|
}
|
|
12473
12583
|
}
|
|
12474
12584
|
|
|
12475
|
-
#insertLineBelowIfLastNode(node) {
|
|
12476
|
-
this.editor.update(() => {
|
|
12477
|
-
const nextSibling = node.getNextSibling();
|
|
12478
|
-
if (!nextSibling) {
|
|
12479
|
-
const newParagraph = eo();
|
|
12480
|
-
node.insertAfter(newParagraph);
|
|
12481
|
-
newParagraph.selectStart();
|
|
12482
|
-
}
|
|
12483
|
-
});
|
|
12484
|
-
}
|
|
12485
|
-
|
|
12486
12585
|
#unwrap(node) {
|
|
12487
12586
|
const children = node.getChildren();
|
|
12488
12587
|
|
|
@@ -12515,19 +12614,27 @@ class Contents {
|
|
|
12515
12614
|
return { anchorNode, offset: anchor.offset }
|
|
12516
12615
|
}
|
|
12517
12616
|
|
|
12518
|
-
|
|
12617
|
+
// The replaced span can straddle the cursor (e.g. "@Jack" when "@" was just
|
|
12618
|
+
// inserted before "Jack"), so we anchor on the trigger before the cursor and
|
|
12619
|
+
// verify the whole string matches there rather than searching text up to it.
|
|
12620
|
+
#findReplacementStart(anchorNode, offset, stringToReplace) {
|
|
12519
12621
|
const fullText = anchorNode.getTextContent();
|
|
12520
|
-
const
|
|
12521
|
-
|
|
12622
|
+
const triggerIndex = fullText.slice(0, offset).lastIndexOf(stringToReplace[0]);
|
|
12623
|
+
|
|
12624
|
+
if (triggerIndex !== -1 && fullText.startsWith(stringToReplace, triggerIndex)) {
|
|
12625
|
+
return triggerIndex
|
|
12626
|
+
} else {
|
|
12627
|
+
return -1
|
|
12628
|
+
}
|
|
12522
12629
|
}
|
|
12523
12630
|
|
|
12524
|
-
#performTextReplacement(anchorNode,
|
|
12631
|
+
#performTextReplacement(anchorNode, startIndex, stringToReplace, replacementNodes) {
|
|
12525
12632
|
const fullText = anchorNode.getTextContent();
|
|
12526
|
-
const textBeforeString = fullText.slice(0,
|
|
12527
|
-
const
|
|
12633
|
+
const textBeforeString = fullText.slice(0, startIndex);
|
|
12634
|
+
const textAfterString = fullText.slice(startIndex + stringToReplace.length);
|
|
12528
12635
|
|
|
12529
|
-
const textNodeBefore = this.#cloneTextNodeFormatting(anchorNode,
|
|
12530
|
-
const textNodeAfter = this.#cloneTextNodeFormatting(anchorNode,
|
|
12636
|
+
const textNodeBefore = this.#cloneTextNodeFormatting(anchorNode, textBeforeString);
|
|
12637
|
+
const textNodeAfter = this.#cloneTextNodeFormatting(anchorNode, textAfterString || " ");
|
|
12531
12638
|
|
|
12532
12639
|
anchorNode.replace(textNodeBefore);
|
|
12533
12640
|
|
|
@@ -12535,22 +12642,16 @@ class Contents {
|
|
|
12535
12642
|
lastInsertedNode.insertAfter(textNodeAfter);
|
|
12536
12643
|
|
|
12537
12644
|
this.#appendLineBreakIfNeeded(textNodeAfter.getParentOrThrow());
|
|
12538
|
-
const cursorOffset =
|
|
12645
|
+
const cursorOffset = textAfterString ? 0 : 1;
|
|
12539
12646
|
textNodeAfter.select(cursorOffset, cursorOffset);
|
|
12540
12647
|
}
|
|
12541
12648
|
|
|
12542
|
-
#cloneTextNodeFormatting(anchorNode,
|
|
12543
|
-
const parent = anchorNode.getParent();
|
|
12544
|
-
const fallbackFormat = parent?.getTextFormat?.() || 0;
|
|
12545
|
-
const fallbackStyle = parent?.getTextStyle?.() || "";
|
|
12546
|
-
const format = Fr(selection) && selection.format ? selection.format : (anchorNode.getFormat() || fallbackFormat);
|
|
12547
|
-
const style = Fr(selection) && selection.style ? selection.style : (anchorNode.getStyle() || fallbackStyle);
|
|
12548
|
-
|
|
12649
|
+
#cloneTextNodeFormatting(anchorNode, text) {
|
|
12549
12650
|
return kr(text)
|
|
12550
|
-
.setFormat(
|
|
12651
|
+
.setFormat(anchorNode.getFormat())
|
|
12551
12652
|
.setDetail(anchorNode.getDetail())
|
|
12552
12653
|
.setMode(anchorNode.getMode())
|
|
12553
|
-
.setStyle(
|
|
12654
|
+
.setStyle(anchorNode.getStyle())
|
|
12554
12655
|
}
|
|
12555
12656
|
|
|
12556
12657
|
#insertReplacementNodes(startNode, replacementNodes) {
|
|
@@ -12840,14 +12941,31 @@ class Clipboard {
|
|
|
12840
12941
|
#pasteMarkdown(text) {
|
|
12841
12942
|
const html = k(text, { breaks: true });
|
|
12842
12943
|
const doc = parseHtml(html);
|
|
12843
|
-
const detail = Object.freeze({
|
|
12844
|
-
markdown: text,
|
|
12845
|
-
document: doc,
|
|
12846
|
-
addBlockSpacing: () => addBlockSpacing(doc)
|
|
12847
|
-
});
|
|
12848
12944
|
|
|
12849
|
-
|
|
12850
|
-
|
|
12945
|
+
if (this.#isPlainTextWithoutMarkdown(doc)) {
|
|
12946
|
+
this.contents.insertText(text, { tag: jn });
|
|
12947
|
+
} else {
|
|
12948
|
+
const detail = Object.freeze({
|
|
12949
|
+
markdown: text,
|
|
12950
|
+
document: doc,
|
|
12951
|
+
addBlockSpacing: () => addBlockSpacing(doc)
|
|
12952
|
+
});
|
|
12953
|
+
|
|
12954
|
+
dispatch(this.editorElement, "lexxy:insert-markdown", detail);
|
|
12955
|
+
this.contents.insertDOM(doc, { tag: jn });
|
|
12956
|
+
}
|
|
12957
|
+
}
|
|
12958
|
+
|
|
12959
|
+
// Markdown conversion collapses runs of whitespace and unescapes backslashes,
|
|
12960
|
+
// silently corrupting plain text such as Windows/UNC file paths. When the text
|
|
12961
|
+
// carries no Markdown structure, paste it verbatim instead.
|
|
12962
|
+
#isPlainTextWithoutMarkdown(doc) {
|
|
12963
|
+
const elements = Array.from(doc.body.children);
|
|
12964
|
+
if (elements.length !== 1) return false
|
|
12965
|
+
|
|
12966
|
+
const paragraph = elements[0];
|
|
12967
|
+
return paragraph.nodeName === "P"
|
|
12968
|
+
&& Array.from(paragraph.childNodes).every((node) => node.nodeType === Node.TEXT_NODE)
|
|
12851
12969
|
}
|
|
12852
12970
|
|
|
12853
12971
|
#pasteRichText(clipboardData) {
|
|
@@ -13455,7 +13573,7 @@ class TablesExtension extends LexxyExtension {
|
|
|
13455
13573
|
}
|
|
13456
13574
|
}
|
|
13457
13575
|
|
|
13458
|
-
const MIME_TYPE = "application/x-lexxy-node-key";
|
|
13576
|
+
const MIME_TYPE$1 = "application/x-lexxy-node-key";
|
|
13459
13577
|
|
|
13460
13578
|
class AttachmentDragAndDrop {
|
|
13461
13579
|
#editor
|
|
@@ -13502,7 +13620,7 @@ class AttachmentDragAndDrop {
|
|
|
13502
13620
|
if (!figure) return false
|
|
13503
13621
|
|
|
13504
13622
|
this.#draggedNodeKey = figure.dataset.lexicalNodeKey;
|
|
13505
|
-
event.dataTransfer.setData(MIME_TYPE, this.#draggedNodeKey);
|
|
13623
|
+
event.dataTransfer.setData(MIME_TYPE$1, this.#draggedNodeKey);
|
|
13506
13624
|
event.dataTransfer.effectAllowed = "move";
|
|
13507
13625
|
|
|
13508
13626
|
// Add dragging class after a tick so it doesn't affect the drag image
|
|
@@ -13851,11 +13969,12 @@ class AttachmentsExtension extends LexxyExtension {
|
|
|
13851
13969
|
|
|
13852
13970
|
#handleUploadMutations(mutations) {
|
|
13853
13971
|
const previousUploadsCount = this.#uploadsCount;
|
|
13854
|
-
for (const [ , mutation ] of mutations) {
|
|
13972
|
+
for (const [ key, mutation ] of mutations) {
|
|
13855
13973
|
if (mutation === "created") {
|
|
13856
13974
|
this.#uploadsCount++;
|
|
13857
13975
|
} else if (mutation === "destroyed") {
|
|
13858
13976
|
this.#uploadsCount--;
|
|
13977
|
+
this.editorElement.uploadRequests.abort(key);
|
|
13859
13978
|
}
|
|
13860
13979
|
}
|
|
13861
13980
|
|
|
@@ -14281,6 +14400,268 @@ class PreventLexicalTripleClickExtension extends LexxyExtension {
|
|
|
14281
14400
|
}
|
|
14282
14401
|
}
|
|
14283
14402
|
|
|
14403
|
+
function caretRect(node, offset) {
|
|
14404
|
+
const range = document.createRange();
|
|
14405
|
+
range.setStart(node, offset);
|
|
14406
|
+
range.collapse(true);
|
|
14407
|
+
|
|
14408
|
+
const rect = range.getBoundingClientRect();
|
|
14409
|
+
if (rect.height > 0) {
|
|
14410
|
+
return rect
|
|
14411
|
+
} else {
|
|
14412
|
+
return null
|
|
14413
|
+
}
|
|
14414
|
+
}
|
|
14415
|
+
|
|
14416
|
+
function caretFromPoint(clientX, clientY) {
|
|
14417
|
+
if (document.caretPositionFromPoint) {
|
|
14418
|
+
const position = document.caretPositionFromPoint(clientX, clientY);
|
|
14419
|
+
if (position) return { node: position.offsetNode, offset: position.offset }
|
|
14420
|
+
} else if (document.caretRangeFromPoint) {
|
|
14421
|
+
const range = document.caretRangeFromPoint(clientX, clientY);
|
|
14422
|
+
if (range) return { node: range.startContainer, offset: range.startOffset }
|
|
14423
|
+
}
|
|
14424
|
+
|
|
14425
|
+
return null
|
|
14426
|
+
}
|
|
14427
|
+
|
|
14428
|
+
const MIME_TYPE = "application/x-lexxy-custom-attachment-key";
|
|
14429
|
+
|
|
14430
|
+
// Custom inline attachments reorder by dropping at a text caret, unlike block
|
|
14431
|
+
// attachments which insert between blocks or into galleries.
|
|
14432
|
+
class CustomAttachmentDragAndDrop {
|
|
14433
|
+
#editor
|
|
14434
|
+
#draggedNodeKey = null
|
|
14435
|
+
#draggingRafId = null
|
|
14436
|
+
#dragOverRafId = null
|
|
14437
|
+
#dropIndicator = null
|
|
14438
|
+
#listeners = new ListenerBin()
|
|
14439
|
+
|
|
14440
|
+
constructor(editor) {
|
|
14441
|
+
this.#editor = editor;
|
|
14442
|
+
|
|
14443
|
+
// Register at HIGH priority to intercept before the base @lexical/rich-text
|
|
14444
|
+
// handlers, which consume drag events. The block-attachment handler also
|
|
14445
|
+
// registers here but bails for inline custom attachments, so we get our turn.
|
|
14446
|
+
this.#listeners.track(
|
|
14447
|
+
editor.registerCommand(Be$2, (event) => this.#handleDragStart(event), so),
|
|
14448
|
+
editor.registerCommand(ze$2, (event) => this.#handleDrop(event), so)
|
|
14449
|
+
);
|
|
14450
|
+
|
|
14451
|
+
this.#listeners.track(editor.registerRootListener((root, prevRoot) => {
|
|
14452
|
+
if (prevRoot) {
|
|
14453
|
+
prevRoot.removeEventListener("dragover", this.#onDragOver);
|
|
14454
|
+
prevRoot.removeEventListener("dragend", this.#onDragEnd);
|
|
14455
|
+
}
|
|
14456
|
+
if (root) {
|
|
14457
|
+
root.addEventListener("dragover", this.#onDragOver);
|
|
14458
|
+
root.addEventListener("dragend", this.#onDragEnd);
|
|
14459
|
+
}
|
|
14460
|
+
}));
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14463
|
+
destroy() {
|
|
14464
|
+
this.#cleanup();
|
|
14465
|
+
this.#dropIndicator?.remove();
|
|
14466
|
+
this.#dropIndicator = null;
|
|
14467
|
+
this.#listeners.dispose();
|
|
14468
|
+
}
|
|
14469
|
+
|
|
14470
|
+
#handleDragStart(event) {
|
|
14471
|
+
const attachment = this.#customAttachmentElementFrom(event.target);
|
|
14472
|
+
if (!attachment) return false
|
|
14473
|
+
|
|
14474
|
+
this.#draggedNodeKey = attachment.dataset.lexicalNodeKey;
|
|
14475
|
+
event.dataTransfer.setData(MIME_TYPE, this.#draggedNodeKey);
|
|
14476
|
+
event.dataTransfer.effectAllowed = "move";
|
|
14477
|
+
|
|
14478
|
+
this.#draggingRafId = requestAnimationFrame(() => {
|
|
14479
|
+
this.#draggingRafId = null;
|
|
14480
|
+
attachment.classList.add("lexxy-dragging");
|
|
14481
|
+
});
|
|
14482
|
+
|
|
14483
|
+
return true
|
|
14484
|
+
}
|
|
14485
|
+
|
|
14486
|
+
#onDragOver = (event) => {
|
|
14487
|
+
if (!this.#draggedNodeKey) return
|
|
14488
|
+
|
|
14489
|
+
event.preventDefault();
|
|
14490
|
+
event.dataTransfer.dropEffect = "move";
|
|
14491
|
+
|
|
14492
|
+
if (!this.#dragOverRafId) {
|
|
14493
|
+
this.#dragOverRafId = requestAnimationFrame(() => {
|
|
14494
|
+
this.#dragOverRafId = null;
|
|
14495
|
+
this.#updateDropIndicator(event);
|
|
14496
|
+
});
|
|
14497
|
+
}
|
|
14498
|
+
}
|
|
14499
|
+
|
|
14500
|
+
#onDragEnd = () => {
|
|
14501
|
+
this.#cleanup();
|
|
14502
|
+
}
|
|
14503
|
+
|
|
14504
|
+
#handleDrop(event) {
|
|
14505
|
+
if (!this.#draggedNodeKey) return false
|
|
14506
|
+
|
|
14507
|
+
event.preventDefault();
|
|
14508
|
+
|
|
14509
|
+
const dropPoint = this.#resolveDropPoint(event);
|
|
14510
|
+
const draggedKey = this.#draggedNodeKey;
|
|
14511
|
+
this.#cleanup();
|
|
14512
|
+
|
|
14513
|
+
if (dropPoint) {
|
|
14514
|
+
this.#moveAttachment(draggedKey, dropPoint);
|
|
14515
|
+
}
|
|
14516
|
+
|
|
14517
|
+
return true
|
|
14518
|
+
}
|
|
14519
|
+
|
|
14520
|
+
#resolveDropPoint(event) {
|
|
14521
|
+
const rootElement = this.#editor.getRootElement();
|
|
14522
|
+
if (!rootElement) return null
|
|
14523
|
+
|
|
14524
|
+
const caret = caretFromPoint(event.clientX, event.clientY);
|
|
14525
|
+
if (!caret || !rootElement.contains(caret.node)) return null
|
|
14526
|
+
|
|
14527
|
+
// A caret on the root itself points between blocks. Mentions behave like text:
|
|
14528
|
+
// they only drop onto an existing line, so snap to the nearest one.
|
|
14529
|
+
if (caret.node === rootElement) {
|
|
14530
|
+
return this.#nearestLineCaret(rootElement, event.clientY)
|
|
14531
|
+
} else {
|
|
14532
|
+
return caret
|
|
14533
|
+
}
|
|
14534
|
+
}
|
|
14535
|
+
|
|
14536
|
+
#nearestLineCaret(rootElement, clientY) {
|
|
14537
|
+
let nearestLine = null;
|
|
14538
|
+
let nearestDistance = Infinity;
|
|
14539
|
+
|
|
14540
|
+
for (const line of rootElement.children) {
|
|
14541
|
+
const rect = line.getBoundingClientRect();
|
|
14542
|
+
const distance = Math.min(Math.abs(clientY - rect.top), Math.abs(clientY - rect.bottom));
|
|
14543
|
+
if (distance < nearestDistance) {
|
|
14544
|
+
nearestDistance = distance;
|
|
14545
|
+
nearestLine = line;
|
|
14546
|
+
}
|
|
14547
|
+
}
|
|
14548
|
+
|
|
14549
|
+
if (!nearestLine) return null
|
|
14550
|
+
|
|
14551
|
+
const rect = nearestLine.getBoundingClientRect();
|
|
14552
|
+
if (clientY < rect.top) {
|
|
14553
|
+
return { node: nearestLine, offset: 0 }
|
|
14554
|
+
} else {
|
|
14555
|
+
return { node: nearestLine, offset: nearestLine.childNodes.length }
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
|
|
14559
|
+
#moveAttachment(draggedKey, dropPoint) {
|
|
14560
|
+
this.#editor.update(() => {
|
|
14561
|
+
const draggedNode = Yo(draggedKey);
|
|
14562
|
+
if (!$isCustomActionTextAttachmentNode(draggedNode)) return
|
|
14563
|
+
|
|
14564
|
+
const selection = Gr({
|
|
14565
|
+
anchorNode: dropPoint.node,
|
|
14566
|
+
anchorOffset: dropPoint.offset,
|
|
14567
|
+
focusNode: dropPoint.node,
|
|
14568
|
+
focusOffset: dropPoint.offset
|
|
14569
|
+
}, this.#editor);
|
|
14570
|
+
if (!selection) return
|
|
14571
|
+
|
|
14572
|
+
es(selection);
|
|
14573
|
+
|
|
14574
|
+
draggedNode.remove();
|
|
14575
|
+
selection.insertNodes([ draggedNode ]);
|
|
14576
|
+
});
|
|
14577
|
+
}
|
|
14578
|
+
|
|
14579
|
+
#updateDropIndicator(event) {
|
|
14580
|
+
this.#hideCaret();
|
|
14581
|
+
|
|
14582
|
+
const dropPoint = this.#resolveDropPoint(event);
|
|
14583
|
+
if (dropPoint) this.#showCaret(this.#caretRectFor(dropPoint));
|
|
14584
|
+
}
|
|
14585
|
+
|
|
14586
|
+
#caretRectFor({ node, offset }) {
|
|
14587
|
+
const rect = caretRect(node, offset);
|
|
14588
|
+
if (rect) return rect
|
|
14589
|
+
|
|
14590
|
+
// A blank line has no text to measure, so fall back to the line's own box.
|
|
14591
|
+
const line = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
|
|
14592
|
+
if (!line) return null
|
|
14593
|
+
|
|
14594
|
+
const lineRect = line.getBoundingClientRect();
|
|
14595
|
+
return { left: lineRect.left, top: lineRect.top, height: lineRect.height }
|
|
14596
|
+
}
|
|
14597
|
+
|
|
14598
|
+
#showCaret(rect) {
|
|
14599
|
+
if (!rect) return
|
|
14600
|
+
|
|
14601
|
+
const caret = this.#ensureCaretIndicator();
|
|
14602
|
+
caret.style.blockSize = `${rect.height}px`;
|
|
14603
|
+
caret.style.insetInlineStart = `${rect.left}px`;
|
|
14604
|
+
caret.style.insetBlockStart = `${rect.top}px`;
|
|
14605
|
+
}
|
|
14606
|
+
|
|
14607
|
+
#ensureCaretIndicator() {
|
|
14608
|
+
this.#dropIndicator ||= createElement("div", { className: "lexxy-drop-caret" });
|
|
14609
|
+
|
|
14610
|
+
this.#editorElement().appendChild(this.#dropIndicator);
|
|
14611
|
+
this.#dropIndicator.style.display = "block";
|
|
14612
|
+
return this.#dropIndicator
|
|
14613
|
+
}
|
|
14614
|
+
|
|
14615
|
+
#editorElement() {
|
|
14616
|
+
return this.#editor.getRootElement().closest("lexxy-editor")
|
|
14617
|
+
}
|
|
14618
|
+
|
|
14619
|
+
#hideCaret() {
|
|
14620
|
+
if (this.#dropIndicator) this.#dropIndicator.style.display = "none";
|
|
14621
|
+
}
|
|
14622
|
+
|
|
14623
|
+
#customAttachmentElementFrom(target) {
|
|
14624
|
+
return target?.closest?.("[data-lexxy-decorator][data-lexical-node-key]")
|
|
14625
|
+
}
|
|
14626
|
+
|
|
14627
|
+
#cleanup() {
|
|
14628
|
+
if (this.#draggedNodeKey) {
|
|
14629
|
+
const rootElement = this.#editor.getRootElement();
|
|
14630
|
+
const attachment = rootElement?.querySelector(`[data-lexical-node-key="${this.#draggedNodeKey}"]`);
|
|
14631
|
+
attachment?.classList.remove("lexxy-dragging");
|
|
14632
|
+
}
|
|
14633
|
+
|
|
14634
|
+
this.#hideCaret();
|
|
14635
|
+
this.#draggedNodeKey = null;
|
|
14636
|
+
|
|
14637
|
+
if (this.#draggingRafId) {
|
|
14638
|
+
cancelAnimationFrame(this.#draggingRafId);
|
|
14639
|
+
this.#draggingRafId = null;
|
|
14640
|
+
}
|
|
14641
|
+
|
|
14642
|
+
if (this.#dragOverRafId) {
|
|
14643
|
+
cancelAnimationFrame(this.#dragOverRafId);
|
|
14644
|
+
this.#dragOverRafId = null;
|
|
14645
|
+
}
|
|
14646
|
+
}
|
|
14647
|
+
}
|
|
14648
|
+
|
|
14649
|
+
class CustomAttachmentDragAndDropExtension extends LexxyExtension {
|
|
14650
|
+
get enabled() {
|
|
14651
|
+
return this.editorElement.supportsRichText
|
|
14652
|
+
}
|
|
14653
|
+
|
|
14654
|
+
get lexicalExtension() {
|
|
14655
|
+
return dc({
|
|
14656
|
+
name: "lexxy/custom-attachment-drag-and-drop",
|
|
14657
|
+
register: (editor) => {
|
|
14658
|
+
const dragAndDrop = new CustomAttachmentDragAndDrop(editor);
|
|
14659
|
+
return () => dragAndDrop.destroy()
|
|
14660
|
+
}
|
|
14661
|
+
})
|
|
14662
|
+
}
|
|
14663
|
+
}
|
|
14664
|
+
|
|
14284
14665
|
class LexicalEditorElement extends HTMLElement {
|
|
14285
14666
|
static formAssociated = true
|
|
14286
14667
|
static debug = false
|
|
@@ -14299,6 +14680,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14299
14680
|
|
|
14300
14681
|
#validity = new Map()
|
|
14301
14682
|
#validationTextArea = document.createElement("textarea")
|
|
14683
|
+
#uploadRequests
|
|
14302
14684
|
|
|
14303
14685
|
constructor() {
|
|
14304
14686
|
super();
|
|
@@ -14306,6 +14688,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14306
14688
|
this.internals.role = "presentation";
|
|
14307
14689
|
}
|
|
14308
14690
|
|
|
14691
|
+
get uploadRequests() {
|
|
14692
|
+
return this.#uploadRequests
|
|
14693
|
+
}
|
|
14694
|
+
|
|
14309
14695
|
connectedCallback() {
|
|
14310
14696
|
this.id ||= generateDomId("lexxy-editor");
|
|
14311
14697
|
this.config = new EditorConfiguration(this);
|
|
@@ -14326,6 +14712,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14326
14712
|
this.#disposables.push(this.clipboard);
|
|
14327
14713
|
|
|
14328
14714
|
this.adapter = new BrowserAdapter();
|
|
14715
|
+
this.#uploadRequests = new UploadRequests();
|
|
14329
14716
|
|
|
14330
14717
|
const commandDispatcher = CommandDispatcher.configureFor(this);
|
|
14331
14718
|
this.#disposables.push(commandDispatcher);
|
|
@@ -14431,7 +14818,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
14431
14818
|
AttachmentsExtension,
|
|
14432
14819
|
FormatEscapeExtension,
|
|
14433
14820
|
LinkOpenerExtension,
|
|
14434
|
-
PreventLexicalTripleClickExtension
|
|
14821
|
+
PreventLexicalTripleClickExtension,
|
|
14822
|
+
CustomAttachmentDragAndDropExtension
|
|
14435
14823
|
]
|
|
14436
14824
|
}
|
|
14437
14825
|
|
|
@@ -15099,6 +15487,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
15099
15487
|
#reset() {
|
|
15100
15488
|
this.#dispose();
|
|
15101
15489
|
this.#resetValidity();
|
|
15490
|
+
this.#uploadRequests?.clear();
|
|
15102
15491
|
this.editorContentElement?.remove();
|
|
15103
15492
|
this.editorContentElement = null;
|
|
15104
15493
|
|
|
@@ -15465,24 +15854,35 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
15465
15854
|
const { node, offset } = this.#selection.selectedNodeWithOffset();
|
|
15466
15855
|
if (!node) return
|
|
15467
15856
|
|
|
15468
|
-
if (
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
const lastTriggerIndex = textBeforeCursor.lastIndexOf(this.trigger);
|
|
15472
|
-
const triggerEndIndex = lastTriggerIndex + this.trigger.length - 1;
|
|
15473
|
-
|
|
15474
|
-
// If trigger is not found, or cursor is at or before the trigger end position, hide popover
|
|
15475
|
-
if (lastTriggerIndex === -1 || offset <= triggerEndIndex) {
|
|
15476
|
-
this.#hidePopover();
|
|
15857
|
+
if (this.#cursorIsTypingSearchTerm(node, offset)) {
|
|
15858
|
+
if (!this.popoverElement.hasAttribute("data-anchored")) {
|
|
15859
|
+
this.#positionPopover();
|
|
15477
15860
|
}
|
|
15478
15861
|
} else {
|
|
15479
|
-
// Cursor is not in a text node or at offset 0, hide popover
|
|
15480
15862
|
this.#hidePopover();
|
|
15481
15863
|
}
|
|
15482
15864
|
});
|
|
15483
15865
|
}));
|
|
15484
15866
|
}
|
|
15485
15867
|
|
|
15868
|
+
// The popover should stay open only while the cursor sits at the end of the
|
|
15869
|
+
// trigger and its search term. When the cursor moves away — before the
|
|
15870
|
+
// trigger, or past the token into later text — the text between the trigger
|
|
15871
|
+
// and the cursor breaks that run and we dismiss. A newline always breaks the
|
|
15872
|
+
// run; a space breaks it only for triggers that don't support spaces in
|
|
15873
|
+
// searches, since those that do (e.g. `person:`) expect multi-word terms.
|
|
15874
|
+
#cursorIsTypingSearchTerm(node, offset) {
|
|
15875
|
+
if (!Tr(node) || offset === 0) return false
|
|
15876
|
+
|
|
15877
|
+
const textBeforeCursor = node.getTextContent().slice(0, offset);
|
|
15878
|
+
const lastTriggerIndex = textBeforeCursor.lastIndexOf(this.trigger);
|
|
15879
|
+
if (lastTriggerIndex === -1) return false
|
|
15880
|
+
|
|
15881
|
+
const searchTerm = textBeforeCursor.slice(lastTriggerIndex + this.trigger.length);
|
|
15882
|
+
const breakPattern = this.supportsSpaceInSearches ? /\n/ : /[ \n]/;
|
|
15883
|
+
return !breakPattern.test(searchTerm)
|
|
15884
|
+
}
|
|
15885
|
+
|
|
15486
15886
|
get #editor() {
|
|
15487
15887
|
return this.#editorElement.editor
|
|
15488
15888
|
}
|
|
@@ -15588,8 +15988,16 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
15588
15988
|
}
|
|
15589
15989
|
}
|
|
15590
15990
|
|
|
15991
|
+
// Right after a Turbo history restore the editor reconnects before the DOM selection
|
|
15992
|
+
// is re-established, so the cursor geometry is momentarily unavailable. Anchoring then
|
|
15993
|
+
// would pin the menu to the editor's left edge for the rest of the open cycle, so we
|
|
15994
|
+
// skip it and let a later reposition anchor it once the selection is ready. The menu
|
|
15995
|
+
// stays hidden until anchored (see the `[data-anchored]` rule in the stylesheet).
|
|
15591
15996
|
#positionPopover() {
|
|
15592
|
-
const
|
|
15997
|
+
const cursorPosition = this.#selection.cursorPosition;
|
|
15998
|
+
if (!cursorPosition) return
|
|
15999
|
+
|
|
16000
|
+
const { x, y, fontSize } = cursorPosition;
|
|
15593
16001
|
const editorRect = this.#editorElement.getBoundingClientRect();
|
|
15594
16002
|
const contentRect = this.#editorContentElement.getBoundingClientRect();
|
|
15595
16003
|
const verticalOffset = contentRect.top - editorRect.top;
|
|
@@ -17028,5 +17436,5 @@ const configure = Lexxy.configure;
|
|
|
17028
17436
|
// Pushing elements definition to after the current call stack to allow global configuration to take place first
|
|
17029
17437
|
setTimeout(defineElements, 0);
|
|
17030
17438
|
|
|
17031
|
-
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, configure, highlightCode, highlightElement };
|
|
17439
|
+
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, $isCustomActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, configure, highlightCode, highlightElement };
|
|
17032
17440
|
//# sourceMappingURL=lexxy.js.map
|