lexxy 0.1.9.beta → 0.1.11.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.
- checksums.yaml +4 -4
- data/README.md +78 -3
- data/app/assets/javascript/lexxy.js +403 -114
- data/app/assets/javascript/lexxy.js.br +0 -0
- data/app/assets/javascript/lexxy.js.gz +0 -0
- 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/rich_text_area_tag.rb +6 -7
- data/lib/lexxy/version.rb +1 -1
- metadata +2 -3
- data/app/mailers/actiontext/lexical/application_mailer.rb +0 -8
|
@@ -5091,6 +5091,17 @@ function getListType(node) {
|
|
|
5091
5091
|
return null
|
|
5092
5092
|
}
|
|
5093
5093
|
|
|
5094
|
+
function isPrintableCharacter(event) {
|
|
5095
|
+
// Ignore if modifier keys are pressed (except Shift for uppercase)
|
|
5096
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return false
|
|
5097
|
+
|
|
5098
|
+
// Ignore special keys
|
|
5099
|
+
if (event.key.length > 1 && event.key !== 'Enter' && event.key !== 'Space') return false
|
|
5100
|
+
|
|
5101
|
+
// Accept single character keys (letters, numbers, punctuation)
|
|
5102
|
+
return event.key.length === 1
|
|
5103
|
+
}
|
|
5104
|
+
|
|
5094
5105
|
class LexicalToolbarElement extends HTMLElement {
|
|
5095
5106
|
constructor() {
|
|
5096
5107
|
super();
|
|
@@ -5950,6 +5961,8 @@ class CommandDispatcher {
|
|
|
5950
5961
|
|
|
5951
5962
|
dispatchInsertUnorderedList() {
|
|
5952
5963
|
const selection = Nr();
|
|
5964
|
+
if (!selection) return;
|
|
5965
|
+
|
|
5953
5966
|
const anchorNode = selection.anchor.getNode();
|
|
5954
5967
|
|
|
5955
5968
|
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "bullet") {
|
|
@@ -5961,6 +5974,8 @@ class CommandDispatcher {
|
|
|
5961
5974
|
|
|
5962
5975
|
dispatchInsertOrderedList() {
|
|
5963
5976
|
const selection = Nr();
|
|
5977
|
+
if (!selection) return;
|
|
5978
|
+
|
|
5964
5979
|
const anchorNode = selection.anchor.getNode();
|
|
5965
5980
|
|
|
5966
5981
|
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "number") {
|
|
@@ -6133,11 +6148,14 @@ function nextFrame() {
|
|
|
6133
6148
|
class Selection {
|
|
6134
6149
|
constructor(editorElement) {
|
|
6135
6150
|
this.editorElement = editorElement;
|
|
6151
|
+
this.editorContentElement = editorElement.editorContentElement;
|
|
6136
6152
|
this.editor = this.editorElement.editor;
|
|
6137
6153
|
this.previouslySelectedKeys = new Set();
|
|
6138
6154
|
|
|
6139
6155
|
this.#listenForNodeSelections();
|
|
6140
6156
|
this.#processSelectionChangeCommands();
|
|
6157
|
+
this.#handleInputWhenDecoratorNodesSelected();
|
|
6158
|
+
this.#containEditorFocus();
|
|
6141
6159
|
}
|
|
6142
6160
|
|
|
6143
6161
|
clear() {
|
|
@@ -6231,6 +6249,21 @@ class Selection {
|
|
|
6231
6249
|
return this.#findNextSiblingUp(anchorNode)
|
|
6232
6250
|
}
|
|
6233
6251
|
|
|
6252
|
+
get topLevelNodeAfterCursor() {
|
|
6253
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
6254
|
+
if (!anchorNode) return null
|
|
6255
|
+
|
|
6256
|
+
if (Qn(anchorNode)) {
|
|
6257
|
+
return this.#getNextNodeFromTextEnd(anchorNode)
|
|
6258
|
+
}
|
|
6259
|
+
|
|
6260
|
+
if (di(anchorNode)) {
|
|
6261
|
+
return this.#getNodeAfterElementNode(anchorNode, offset)
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
return this.#findNextSiblingUp(anchorNode)
|
|
6265
|
+
}
|
|
6266
|
+
|
|
6234
6267
|
get nodeBeforeCursor() {
|
|
6235
6268
|
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
6236
6269
|
if (!anchorNode) return null
|
|
@@ -6246,6 +6279,21 @@ class Selection {
|
|
|
6246
6279
|
return this.#findPreviousSiblingUp(anchorNode)
|
|
6247
6280
|
}
|
|
6248
6281
|
|
|
6282
|
+
get topLevelNodeBeforeCursor() {
|
|
6283
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
6284
|
+
if (!anchorNode) return null
|
|
6285
|
+
|
|
6286
|
+
if (Qn(anchorNode)) {
|
|
6287
|
+
return this.#getPreviousNodeFromTextStart(anchorNode)
|
|
6288
|
+
}
|
|
6289
|
+
|
|
6290
|
+
if (di(anchorNode)) {
|
|
6291
|
+
return this.#getNodeBeforeElementNode(anchorNode, offset)
|
|
6292
|
+
}
|
|
6293
|
+
|
|
6294
|
+
return this.#findPreviousSiblingUp(anchorNode)
|
|
6295
|
+
}
|
|
6296
|
+
|
|
6249
6297
|
get #contents() {
|
|
6250
6298
|
return this.editorElement.contents
|
|
6251
6299
|
}
|
|
@@ -6266,9 +6314,9 @@ class Selection {
|
|
|
6266
6314
|
|
|
6267
6315
|
#processSelectionChangeCommands() {
|
|
6268
6316
|
this.editor.registerCommand(Te$1, this.#selectPreviousNode.bind(this), Ii);
|
|
6269
|
-
this.editor.registerCommand(Ne$1, this.#selectPreviousNode.bind(this), Ii);
|
|
6270
6317
|
this.editor.registerCommand(ve$1, this.#selectNextNode.bind(this), Ii);
|
|
6271
|
-
this.editor.registerCommand(
|
|
6318
|
+
this.editor.registerCommand(Ne$1, this.#selectPreviousTopLevelNode.bind(this), Ii);
|
|
6319
|
+
this.editor.registerCommand(we$1, this.#selectNextTopLevelNode.bind(this), Ii);
|
|
6272
6320
|
|
|
6273
6321
|
this.editor.registerCommand(De$1, this.#deleteSelectedOrNext.bind(this), Ii);
|
|
6274
6322
|
this.editor.registerCommand(Ae$1, this.#deletePreviousOrNext.bind(this), Ii);
|
|
@@ -6299,6 +6347,93 @@ class Selection {
|
|
|
6299
6347
|
});
|
|
6300
6348
|
}
|
|
6301
6349
|
|
|
6350
|
+
// In Safari, when the only node in the document is an attachment, it won't let you enter text
|
|
6351
|
+
// before/below it. There is probably a better fix here, but this workaround solves the problem until
|
|
6352
|
+
// we find it.
|
|
6353
|
+
#handleInputWhenDecoratorNodesSelected() {
|
|
6354
|
+
this.editor.getRootElement().addEventListener("keydown", (event) => {
|
|
6355
|
+
if (isPrintableCharacter(event)) {
|
|
6356
|
+
this.editor.update(() => {
|
|
6357
|
+
const selection = Nr();
|
|
6358
|
+
|
|
6359
|
+
if (cr(selection) && selection.isCollapsed()) {
|
|
6360
|
+
const anchorNode = selection.anchor.getNode();
|
|
6361
|
+
const offset = selection.anchor.offset;
|
|
6362
|
+
|
|
6363
|
+
const nodeBefore = this.#getNodeBeforePosition(anchorNode, offset);
|
|
6364
|
+
const nodeAfter = this.#getNodeAfterPosition(anchorNode, offset);
|
|
6365
|
+
|
|
6366
|
+
if (nodeBefore instanceof gi && !nodeBefore.isInline()) {
|
|
6367
|
+
event.preventDefault();
|
|
6368
|
+
this.#contents.createParagraphAfterNode(nodeBefore, event.key);
|
|
6369
|
+
return
|
|
6370
|
+
} else if (nodeAfter instanceof gi && !nodeAfter.isInline()) {
|
|
6371
|
+
event.preventDefault();
|
|
6372
|
+
this.#contents.createParagraphBeforeNode(nodeAfter, event.key);
|
|
6373
|
+
return
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
});
|
|
6377
|
+
}
|
|
6378
|
+
}, true);
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
#getNodeBeforePosition(node, offset) {
|
|
6382
|
+
if (Qn(node) && offset === 0) {
|
|
6383
|
+
return node.getPreviousSibling()
|
|
6384
|
+
}
|
|
6385
|
+
if (di(node) && offset > 0) {
|
|
6386
|
+
return node.getChildAtIndex(offset - 1)
|
|
6387
|
+
}
|
|
6388
|
+
return null
|
|
6389
|
+
}
|
|
6390
|
+
|
|
6391
|
+
#getNodeAfterPosition(node, offset) {
|
|
6392
|
+
if (Qn(node) && offset === node.getTextContentSize()) {
|
|
6393
|
+
return node.getNextSibling()
|
|
6394
|
+
}
|
|
6395
|
+
if (di(node)) {
|
|
6396
|
+
return node.getChildAtIndex(offset)
|
|
6397
|
+
}
|
|
6398
|
+
return null
|
|
6399
|
+
}
|
|
6400
|
+
|
|
6401
|
+
#containEditorFocus() {
|
|
6402
|
+
// Workaround for a bizarre Chrome bug where the cursor abandons the editor to focus on not-focusable elements
|
|
6403
|
+
// above when navigating UP/DOWN when Lexical shows its fake cursor on custom decorator nodes.
|
|
6404
|
+
this.editorContentElement.addEventListener("keydown", (event) => {
|
|
6405
|
+
if (event.key === "ArrowUp") {
|
|
6406
|
+
const lexicalCursor = this.editor.getRootElement().querySelector('[data-lexical-cursor]');
|
|
6407
|
+
|
|
6408
|
+
if (lexicalCursor) {
|
|
6409
|
+
let currentElement = lexicalCursor.previousElementSibling;
|
|
6410
|
+
while (currentElement && currentElement.hasAttribute('data-lexical-cursor')) {
|
|
6411
|
+
currentElement = currentElement.previousElementSibling;
|
|
6412
|
+
}
|
|
6413
|
+
|
|
6414
|
+
if (!currentElement) {
|
|
6415
|
+
event.preventDefault();
|
|
6416
|
+
}
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
|
|
6420
|
+
if (event.key === "ArrowDown") {
|
|
6421
|
+
const lexicalCursor = this.editor.getRootElement().querySelector('[data-lexical-cursor]');
|
|
6422
|
+
|
|
6423
|
+
if (lexicalCursor) {
|
|
6424
|
+
let currentElement = lexicalCursor.nextElementSibling;
|
|
6425
|
+
while (currentElement && currentElement.hasAttribute('data-lexical-cursor')) {
|
|
6426
|
+
currentElement = currentElement.nextElementSibling;
|
|
6427
|
+
}
|
|
6428
|
+
|
|
6429
|
+
if (!currentElement) {
|
|
6430
|
+
event.preventDefault();
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
}, true);
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6302
6437
|
#syncSelectedClasses() {
|
|
6303
6438
|
this.#clearPreviouslyHighlightedItems();
|
|
6304
6439
|
this.#highlightNewItems();
|
|
@@ -6341,6 +6476,22 @@ class Selection {
|
|
|
6341
6476
|
}
|
|
6342
6477
|
}
|
|
6343
6478
|
|
|
6479
|
+
async #selectPreviousTopLevelNode() {
|
|
6480
|
+
if (this.current) {
|
|
6481
|
+
await this.#withCurrentNode((currentNode) => currentNode.selectPrevious());
|
|
6482
|
+
} else {
|
|
6483
|
+
this.#selectInLexical(this.topLevelNodeBeforeCursor);
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
|
|
6487
|
+
async #selectNextTopLevelNode() {
|
|
6488
|
+
if (this.current) {
|
|
6489
|
+
await this.#withCurrentNode((currentNode) => currentNode.selectNext(0, 0));
|
|
6490
|
+
} else {
|
|
6491
|
+
this.#selectInLexical(this.topLevelNodeAfterCursor);
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
|
|
6344
6495
|
async #withCurrentNode(fn) {
|
|
6345
6496
|
await nextFrame();
|
|
6346
6497
|
if (this.current) {
|
|
@@ -6589,6 +6740,105 @@ class Selection {
|
|
|
6589
6740
|
}
|
|
6590
6741
|
}
|
|
6591
6742
|
|
|
6743
|
+
class CustomActionTextAttachmentNode extends gi {
|
|
6744
|
+
static getType() {
|
|
6745
|
+
return "custom_action_text_attachment"
|
|
6746
|
+
}
|
|
6747
|
+
|
|
6748
|
+
static clone(node) {
|
|
6749
|
+
return new CustomActionTextAttachmentNode({ ...node }, node.__key)
|
|
6750
|
+
}
|
|
6751
|
+
|
|
6752
|
+
static importJSON(serializedNode) {
|
|
6753
|
+
return new CustomActionTextAttachmentNode({ ...serializedNode })
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6756
|
+
static importDOM() {
|
|
6757
|
+
return {
|
|
6758
|
+
"action-text-attachment": (attachment) => {
|
|
6759
|
+
const content = attachment.getAttribute("content");
|
|
6760
|
+
if (!attachment.getAttribute("content")) {
|
|
6761
|
+
return null
|
|
6762
|
+
}
|
|
6763
|
+
|
|
6764
|
+
return {
|
|
6765
|
+
conversion: () => {
|
|
6766
|
+
// Preserve initial space if present since Lexical removes it
|
|
6767
|
+
const nodes = [];
|
|
6768
|
+
const previousSibling = attachment.previousSibling;
|
|
6769
|
+
if (previousSibling && previousSibling.nodeType === Node.TEXT_NODE && /\s$/.test(previousSibling.textContent)) {
|
|
6770
|
+
nodes.push(Xn(" "));
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
nodes.push(new CustomActionTextAttachmentNode({
|
|
6774
|
+
sgid: attachment.getAttribute("sgid"),
|
|
6775
|
+
innerHtml: JSON.parse(content),
|
|
6776
|
+
contentType: attachment.getAttribute("content-type")
|
|
6777
|
+
}));
|
|
6778
|
+
|
|
6779
|
+
nodes.push(Xn(" "));
|
|
6780
|
+
|
|
6781
|
+
return { node: nodes }
|
|
6782
|
+
},
|
|
6783
|
+
priority: 2
|
|
6784
|
+
}
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
|
|
6789
|
+
constructor({ sgid, contentType, innerHtml }, key) {
|
|
6790
|
+
super(key);
|
|
6791
|
+
|
|
6792
|
+
this.sgid = sgid;
|
|
6793
|
+
this.contentType = contentType || "application/vnd.actiontext.unknown";
|
|
6794
|
+
this.innerHtml = innerHtml;
|
|
6795
|
+
}
|
|
6796
|
+
|
|
6797
|
+
createDOM() {
|
|
6798
|
+
const figure = createElement("action-text-attachment", { "content-type": this.contentType, "data-lexxy-decorator": true });
|
|
6799
|
+
|
|
6800
|
+
figure.addEventListener("click", (event) => {
|
|
6801
|
+
dispatchCustomEvent(figure, "lexxy:internal:select-node", { key: this.getKey() });
|
|
6802
|
+
});
|
|
6803
|
+
|
|
6804
|
+
figure.insertAdjacentHTML("beforeend", this.innerHtml);
|
|
6805
|
+
|
|
6806
|
+
return figure
|
|
6807
|
+
}
|
|
6808
|
+
|
|
6809
|
+
updateDOM() {
|
|
6810
|
+
return true
|
|
6811
|
+
}
|
|
6812
|
+
|
|
6813
|
+
isInline() {
|
|
6814
|
+
return true
|
|
6815
|
+
}
|
|
6816
|
+
|
|
6817
|
+
exportDOM() {
|
|
6818
|
+
const attachment = createElement("action-text-attachment", {
|
|
6819
|
+
sgid: this.sgid,
|
|
6820
|
+
content: JSON.stringify(this.innerHtml),
|
|
6821
|
+
"content-type": this.contentType
|
|
6822
|
+
});
|
|
6823
|
+
|
|
6824
|
+
return { element: attachment }
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6827
|
+
exportJSON() {
|
|
6828
|
+
return {
|
|
6829
|
+
type: "custom_action_text_attachment",
|
|
6830
|
+
version: 1,
|
|
6831
|
+
sgid: this.sgid,
|
|
6832
|
+
contentType: this.contentType,
|
|
6833
|
+
innerHtml: this.innerHtml
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
|
|
6837
|
+
decorate() {
|
|
6838
|
+
return null
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
|
|
6592
6842
|
class Contents {
|
|
6593
6843
|
constructor(editorElement) {
|
|
6594
6844
|
this.editorElement = editorElement;
|
|
@@ -6707,6 +6957,24 @@ class Contents {
|
|
|
6707
6957
|
});
|
|
6708
6958
|
}
|
|
6709
6959
|
|
|
6960
|
+
createLink(url) {
|
|
6961
|
+
let linkNodeKey = null;
|
|
6962
|
+
|
|
6963
|
+
this.editor.update(() => {
|
|
6964
|
+
const textNode = Xn(url);
|
|
6965
|
+
const linkNode = d$1(url);
|
|
6966
|
+
linkNode.append(textNode);
|
|
6967
|
+
|
|
6968
|
+
const selection = Nr();
|
|
6969
|
+
if (cr(selection)) {
|
|
6970
|
+
selection.insertNodes([linkNode]);
|
|
6971
|
+
linkNodeKey = linkNode.getKey();
|
|
6972
|
+
}
|
|
6973
|
+
});
|
|
6974
|
+
|
|
6975
|
+
return linkNodeKey
|
|
6976
|
+
}
|
|
6977
|
+
|
|
6710
6978
|
createLinkWithSelectedText(url) {
|
|
6711
6979
|
if (!this.hasSelectedText()) return
|
|
6712
6980
|
|
|
@@ -6778,6 +7046,30 @@ class Contents {
|
|
|
6778
7046
|
});
|
|
6779
7047
|
}
|
|
6780
7048
|
|
|
7049
|
+
createParagraphAfterNode(node, text) {
|
|
7050
|
+
const newParagraph = Pi();
|
|
7051
|
+
node.insertAfter(newParagraph);
|
|
7052
|
+
newParagraph.selectStart();
|
|
7053
|
+
|
|
7054
|
+
// Insert the typed text
|
|
7055
|
+
if (text) {
|
|
7056
|
+
newParagraph.append(Xn(text));
|
|
7057
|
+
newParagraph.select(1, 1); // Place cursor after the text
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
|
|
7061
|
+
createParagraphBeforeNode(node, text) {
|
|
7062
|
+
const newParagraph = Pi();
|
|
7063
|
+
node.insertBefore(newParagraph);
|
|
7064
|
+
newParagraph.selectStart();
|
|
7065
|
+
|
|
7066
|
+
// Insert the typed text
|
|
7067
|
+
if (text) {
|
|
7068
|
+
newParagraph.append(Xn(text));
|
|
7069
|
+
newParagraph.select(1, 1); // Place cursor after the text
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
|
|
6781
7073
|
uploadFile(file) {
|
|
6782
7074
|
if (!this.editorElement.supportsAttachments) {
|
|
6783
7075
|
console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
|
|
@@ -6812,26 +7104,76 @@ class Contents {
|
|
|
6812
7104
|
deleteSelectedNodes() {
|
|
6813
7105
|
this.editor.update(() => {
|
|
6814
7106
|
if (ur(this.#selection.current)) {
|
|
6815
|
-
|
|
6816
|
-
|
|
7107
|
+
const nodesToRemove = this.#selection.current.getNodes();
|
|
7108
|
+
if (nodesToRemove.length === 0) return
|
|
7109
|
+
|
|
7110
|
+
// Use splice() instead of node.remove() for proper removal and
|
|
7111
|
+
// reconciliation. Would have issues with removing unintended decorator nodes
|
|
7112
|
+
// with node.remove()
|
|
7113
|
+
nodesToRemove.forEach((node) => {
|
|
6817
7114
|
const parent = node.getParent();
|
|
7115
|
+
if (!di(parent)) return
|
|
6818
7116
|
|
|
6819
|
-
|
|
7117
|
+
const children = parent.getChildren();
|
|
7118
|
+
const index = children.indexOf(node);
|
|
6820
7119
|
|
|
6821
|
-
if (
|
|
6822
|
-
parent.
|
|
7120
|
+
if (index >= 0) {
|
|
7121
|
+
parent.splice(index, 1, []);
|
|
6823
7122
|
}
|
|
6824
|
-
|
|
6825
|
-
nodesWereRemoved = true;
|
|
6826
7123
|
});
|
|
6827
7124
|
|
|
6828
|
-
if
|
|
6829
|
-
|
|
6830
|
-
|
|
7125
|
+
// Check if root is empty after all removals
|
|
7126
|
+
const root = ps();
|
|
7127
|
+
if (root.getChildrenSize() === 0) {
|
|
7128
|
+
root.append(Pi());
|
|
7129
|
+
}
|
|
6831
7130
|
|
|
6832
|
-
|
|
7131
|
+
this.#selection.clear();
|
|
7132
|
+
this.editor.focus();
|
|
7133
|
+
|
|
7134
|
+
return true
|
|
7135
|
+
}
|
|
7136
|
+
});
|
|
7137
|
+
}
|
|
7138
|
+
|
|
7139
|
+
replaceNodeWithHTML(nodeKey, html, options = {}) {
|
|
7140
|
+
this.editor.update(() => {
|
|
7141
|
+
const node = us(nodeKey);
|
|
7142
|
+
if (!node) return
|
|
7143
|
+
|
|
7144
|
+
const selection = Nr();
|
|
7145
|
+
let wasSelected = false;
|
|
7146
|
+
|
|
7147
|
+
if (cr(selection)) {
|
|
7148
|
+
const selectedNodes = selection.getNodes();
|
|
7149
|
+
wasSelected = selectedNodes.includes(node) || selectedNodes.some(n => n.getParent() === node);
|
|
7150
|
+
|
|
7151
|
+
if (wasSelected) {
|
|
7152
|
+
ms(null);
|
|
6833
7153
|
}
|
|
6834
7154
|
}
|
|
7155
|
+
|
|
7156
|
+
const replacementNode = options.attachment ? this.#createCustomAttachmentNodeWithHtml(html, options.attachment) : this.#createHtmlNodeWith(html);
|
|
7157
|
+
node.replace(replacementNode);
|
|
7158
|
+
|
|
7159
|
+
if (wasSelected) {
|
|
7160
|
+
replacementNode.selectEnd();
|
|
7161
|
+
}
|
|
7162
|
+
});
|
|
7163
|
+
}
|
|
7164
|
+
|
|
7165
|
+
insertHTMLBelowNode(nodeKey, html, options = {}) {
|
|
7166
|
+
this.editor.update(() => {
|
|
7167
|
+
const node = us(nodeKey);
|
|
7168
|
+
if (!node) return
|
|
7169
|
+
|
|
7170
|
+
let previousNode = node;
|
|
7171
|
+
try {
|
|
7172
|
+
previousNode = node.getTopLevelElementOrThrow();
|
|
7173
|
+
} catch {}
|
|
7174
|
+
|
|
7175
|
+
const newNode = options.attachment ? this.#createCustomAttachmentNodeWithHtml(html, options.attachment) : this.#createHtmlNodeWith(html);
|
|
7176
|
+
previousNode.insertAfter(newNode);
|
|
6835
7177
|
});
|
|
6836
7178
|
}
|
|
6837
7179
|
|
|
@@ -7077,6 +7419,21 @@ class Contents {
|
|
|
7077
7419
|
}
|
|
7078
7420
|
}
|
|
7079
7421
|
|
|
7422
|
+
#createCustomAttachmentNodeWithHtml(html, options = {}) {
|
|
7423
|
+
const attachmentConfig = typeof options === 'object' ? options : {};
|
|
7424
|
+
|
|
7425
|
+
return new CustomActionTextAttachmentNode({
|
|
7426
|
+
sgid: attachmentConfig.sgid || null,
|
|
7427
|
+
contentType: "text/html",
|
|
7428
|
+
innerHtml: html
|
|
7429
|
+
})
|
|
7430
|
+
}
|
|
7431
|
+
|
|
7432
|
+
#createHtmlNodeWith(html) {
|
|
7433
|
+
const htmlNodes = h$3(this.editor, parseHtml(html));
|
|
7434
|
+
return htmlNodes[0] || Pi()
|
|
7435
|
+
}
|
|
7436
|
+
|
|
7080
7437
|
#shouldUploadFile(file) {
|
|
7081
7438
|
return dispatch(this.editorElement, 'lexxy:file-accept', { file }, true)
|
|
7082
7439
|
}
|
|
@@ -7204,12 +7561,27 @@ class Clipboard {
|
|
|
7204
7561
|
item.getAsString((text) => {
|
|
7205
7562
|
if (isUrl(text) && this.contents.hasSelectedText()) {
|
|
7206
7563
|
this.contents.createLinkWithSelectedText(text);
|
|
7564
|
+
} else if (isUrl(text)) {
|
|
7565
|
+
const nodeKey = this.contents.createLink(text);
|
|
7566
|
+
this.#dispatchLinkInsertEvent(nodeKey, { url: text });
|
|
7207
7567
|
} else {
|
|
7208
7568
|
this.#pasteMarkdown(text);
|
|
7209
7569
|
}
|
|
7210
7570
|
});
|
|
7211
7571
|
}
|
|
7212
7572
|
|
|
7573
|
+
#dispatchLinkInsertEvent(nodeKey, payload) {
|
|
7574
|
+
const linkManipulationMethods = {
|
|
7575
|
+
replaceLinkWith: (html, options) => this.contents.replaceNodeWithHTML(nodeKey, html, options),
|
|
7576
|
+
insertBelowLink: (html, options) => this.contents.insertHTMLBelowNode(nodeKey, html, options)
|
|
7577
|
+
};
|
|
7578
|
+
|
|
7579
|
+
dispatch(this.editorElement, "lexxy:insert-link", {
|
|
7580
|
+
...payload,
|
|
7581
|
+
...linkManipulationMethods
|
|
7582
|
+
});
|
|
7583
|
+
}
|
|
7584
|
+
|
|
7213
7585
|
#pasteMarkdown(text) {
|
|
7214
7586
|
const html = d(text);
|
|
7215
7587
|
this.contents.insertHtml(html);
|
|
@@ -7242,113 +7614,15 @@ class Clipboard {
|
|
|
7242
7614
|
}
|
|
7243
7615
|
}
|
|
7244
7616
|
|
|
7245
|
-
class CustomActionTextAttachmentNode extends gi {
|
|
7246
|
-
static getType() {
|
|
7247
|
-
return "custom_action_text_attachment"
|
|
7248
|
-
}
|
|
7249
|
-
|
|
7250
|
-
static clone(node) {
|
|
7251
|
-
return new CustomActionTextAttachmentNode({ ...node }, node.__key)
|
|
7252
|
-
}
|
|
7253
|
-
|
|
7254
|
-
static importJSON(serializedNode) {
|
|
7255
|
-
return new CustomActionTextAttachmentNode({ ...serializedNode })
|
|
7256
|
-
}
|
|
7257
|
-
|
|
7258
|
-
static importDOM() {
|
|
7259
|
-
return {
|
|
7260
|
-
"action-text-attachment": (attachment) => {
|
|
7261
|
-
const content = attachment.getAttribute("content");
|
|
7262
|
-
if (!attachment.getAttribute("content")) {
|
|
7263
|
-
return null
|
|
7264
|
-
}
|
|
7265
|
-
|
|
7266
|
-
return {
|
|
7267
|
-
conversion: () => {
|
|
7268
|
-
// Preserve initial space if present since Lexical removes it
|
|
7269
|
-
const nodes = [];
|
|
7270
|
-
const previousSibling = attachment.previousSibling;
|
|
7271
|
-
if (previousSibling && previousSibling.nodeType === Node.TEXT_NODE && /\s$/.test(previousSibling.textContent)) {
|
|
7272
|
-
nodes.push(Xn(" "));
|
|
7273
|
-
}
|
|
7274
|
-
|
|
7275
|
-
nodes.push(new CustomActionTextAttachmentNode({
|
|
7276
|
-
sgid: attachment.getAttribute("sgid"),
|
|
7277
|
-
innerHtml: JSON.parse(content),
|
|
7278
|
-
contentType: attachment.getAttribute("content-type")
|
|
7279
|
-
}));
|
|
7280
|
-
|
|
7281
|
-
nodes.push(Xn(" "));
|
|
7282
|
-
|
|
7283
|
-
return { node: nodes }
|
|
7284
|
-
},
|
|
7285
|
-
priority: 2
|
|
7286
|
-
}
|
|
7287
|
-
}
|
|
7288
|
-
}
|
|
7289
|
-
}
|
|
7290
|
-
|
|
7291
|
-
constructor({ sgid, contentType, innerHtml }, key) {
|
|
7292
|
-
super(key);
|
|
7293
|
-
|
|
7294
|
-
this.sgid = sgid;
|
|
7295
|
-
this.contentType = contentType || "application/vnd.actiontext.unknown";
|
|
7296
|
-
this.innerHtml = innerHtml;
|
|
7297
|
-
}
|
|
7298
|
-
|
|
7299
|
-
createDOM() {
|
|
7300
|
-
const figure = createElement("action-text-attachment", { "content-type": this.contentType, "data-lexxy-decorator": true });
|
|
7301
|
-
|
|
7302
|
-
figure.addEventListener("click", (event) => {
|
|
7303
|
-
dispatchCustomEvent(figure, "lexxy:internal:select-node", { key: this.getKey() });
|
|
7304
|
-
});
|
|
7305
|
-
|
|
7306
|
-
figure.insertAdjacentHTML("beforeend", this.innerHtml);
|
|
7307
|
-
|
|
7308
|
-
return figure
|
|
7309
|
-
}
|
|
7310
|
-
|
|
7311
|
-
updateDOM() {
|
|
7312
|
-
return true
|
|
7313
|
-
}
|
|
7314
|
-
|
|
7315
|
-
isInline() {
|
|
7316
|
-
return true
|
|
7317
|
-
}
|
|
7318
|
-
|
|
7319
|
-
exportDOM() {
|
|
7320
|
-
const attachment = createElement("action-text-attachment", {
|
|
7321
|
-
sgid: this.sgid,
|
|
7322
|
-
content: JSON.stringify(this.innerHtml),
|
|
7323
|
-
"content-type": this.contentType
|
|
7324
|
-
});
|
|
7325
|
-
|
|
7326
|
-
return { element: attachment }
|
|
7327
|
-
}
|
|
7328
|
-
|
|
7329
|
-
exportJSON() {
|
|
7330
|
-
return {
|
|
7331
|
-
type: "custom_action_text_attachment",
|
|
7332
|
-
version: 1,
|
|
7333
|
-
sgid: this.sgid,
|
|
7334
|
-
contentType: this.contentType,
|
|
7335
|
-
innerHtml: this.innerHtml
|
|
7336
|
-
}
|
|
7337
|
-
}
|
|
7338
|
-
|
|
7339
|
-
decorate() {
|
|
7340
|
-
return null
|
|
7341
|
-
}
|
|
7342
|
-
}
|
|
7343
|
-
|
|
7344
7617
|
class LexicalEditorElement extends HTMLElement {
|
|
7345
7618
|
static formAssociated = true
|
|
7346
7619
|
static debug = true
|
|
7347
7620
|
static commands = [ "bold", "italic", "" ]
|
|
7348
7621
|
|
|
7349
|
-
static observedAttributes = [ "connected" ]
|
|
7622
|
+
static observedAttributes = [ "connected", "required" ]
|
|
7350
7623
|
|
|
7351
7624
|
#initialValue = ""
|
|
7625
|
+
#validationTextArea = document.createElement("textarea")
|
|
7352
7626
|
|
|
7353
7627
|
constructor() {
|
|
7354
7628
|
super();
|
|
@@ -7381,6 +7655,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7381
7655
|
if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) {
|
|
7382
7656
|
requestAnimationFrame(() => this.#reconnect());
|
|
7383
7657
|
}
|
|
7658
|
+
|
|
7659
|
+
if (name === "required" && this.isConnected) {
|
|
7660
|
+
this.#validationTextArea.required = this.hasAttribute("required");
|
|
7661
|
+
this.#setValidity();
|
|
7662
|
+
}
|
|
7384
7663
|
}
|
|
7385
7664
|
|
|
7386
7665
|
formResetCallback() {
|
|
@@ -7434,7 +7713,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7434
7713
|
Ys(Mi);
|
|
7435
7714
|
const root = ps();
|
|
7436
7715
|
root.clear();
|
|
7437
|
-
root.append(...this.#parseHtmlIntoLexicalNodes(html));
|
|
7716
|
+
if (html !== "") root.append(...this.#parseHtmlIntoLexicalNodes(html));
|
|
7438
7717
|
root.select();
|
|
7439
7718
|
|
|
7440
7719
|
this.#toggleEmptyStatus();
|
|
@@ -7547,6 +7826,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7547
7826
|
|
|
7548
7827
|
this.internals.setFormValue(html);
|
|
7549
7828
|
this._internalFormValue = html;
|
|
7829
|
+
this.#validationTextArea.value = this.#isEmpty ? "" : html;
|
|
7550
7830
|
|
|
7551
7831
|
if (changed) {
|
|
7552
7832
|
dispatch(this, "lexxy:change");
|
|
@@ -7575,6 +7855,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7575
7855
|
this.cachedValue = null;
|
|
7576
7856
|
this.#internalFormValue = this.value;
|
|
7577
7857
|
this.#toggleEmptyStatus();
|
|
7858
|
+
this.#setValidity();
|
|
7578
7859
|
}));
|
|
7579
7860
|
}
|
|
7580
7861
|
|
|
@@ -7681,6 +7962,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7681
7962
|
return !this.editorContentElement.textContent.trim() && !containsVisuallyRelevantChildren(this.editorContentElement)
|
|
7682
7963
|
}
|
|
7683
7964
|
|
|
7965
|
+
#setValidity() {
|
|
7966
|
+
if (this.#validationTextArea.validity.valid) {
|
|
7967
|
+
this.internals.setValidity({});
|
|
7968
|
+
} else {
|
|
7969
|
+
this.internals.setValidity(this.#validationTextArea.validity, this.#validationTextArea.validationMessage, this.editorContentElement);
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
|
|
7684
7973
|
#reset() {
|
|
7685
7974
|
this.#unregisterHandlers();
|
|
7686
7975
|
|
|
Binary file
|
|
Binary file
|