lexxy 0.1.10.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/app/assets/javascript/lexxy.js +212 -21
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72557f2f915d20c07de190adaad818d1900728371f65482d8f01a51b3b4c5f24
|
4
|
+
data.tar.gz: 0fabe3de15525414a0e7e548f4d8e5c605bff3e26074bc79541c20bbcb92946c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31d13d76af610866ca92fc340c90f71d3b8a44e6a27f616feb76c2f56cf6f074f22323edd67258054982386f480ab37b9d7b32ff7d7e946be3ec2fc8cb7371bd
|
7
|
+
data.tar.gz: 73f85f0ed7d75452cc6d0c28cc3cfd45d0f1bb544068287b25822b060c14926410d09d7923738879c6f15164a644daea3b3097cfff0cd4a34fbbd3a76f0c5c74
|
@@ -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) {
|
@@ -6895,6 +7046,30 @@ class Contents {
|
|
6895
7046
|
});
|
6896
7047
|
}
|
6897
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
|
+
|
6898
7073
|
uploadFile(file) {
|
6899
7074
|
if (!this.editorElement.supportsAttachments) {
|
6900
7075
|
console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
|
@@ -6929,25 +7104,34 @@ class Contents {
|
|
6929
7104
|
deleteSelectedNodes() {
|
6930
7105
|
this.editor.update(() => {
|
6931
7106
|
if (ur(this.#selection.current)) {
|
6932
|
-
|
6933
|
-
|
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) => {
|
6934
7114
|
const parent = node.getParent();
|
7115
|
+
if (!di(parent)) return
|
6935
7116
|
|
6936
|
-
|
7117
|
+
const children = parent.getChildren();
|
7118
|
+
const index = children.indexOf(node);
|
6937
7119
|
|
6938
|
-
if (
|
6939
|
-
parent.
|
7120
|
+
if (index >= 0) {
|
7121
|
+
parent.splice(index, 1, []);
|
6940
7122
|
}
|
6941
|
-
|
6942
|
-
nodesWereRemoved = true;
|
6943
7123
|
});
|
6944
7124
|
|
6945
|
-
if
|
6946
|
-
|
6947
|
-
|
6948
|
-
|
6949
|
-
return true
|
7125
|
+
// Check if root is empty after all removals
|
7126
|
+
const root = ps();
|
7127
|
+
if (root.getChildrenSize() === 0) {
|
7128
|
+
root.append(Pi());
|
6950
7129
|
}
|
7130
|
+
|
7131
|
+
this.#selection.clear();
|
7132
|
+
this.editor.focus();
|
7133
|
+
|
7134
|
+
return true
|
6951
7135
|
}
|
6952
7136
|
});
|
6953
7137
|
}
|
@@ -7435,9 +7619,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
7435
7619
|
static debug = true
|
7436
7620
|
static commands = [ "bold", "italic", "" ]
|
7437
7621
|
|
7438
|
-
static observedAttributes = [ "connected" ]
|
7622
|
+
static observedAttributes = [ "connected", "required" ]
|
7439
7623
|
|
7440
7624
|
#initialValue = ""
|
7625
|
+
#validationTextArea = document.createElement("textarea")
|
7441
7626
|
|
7442
7627
|
constructor() {
|
7443
7628
|
super();
|
@@ -7470,6 +7655,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
7470
7655
|
if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) {
|
7471
7656
|
requestAnimationFrame(() => this.#reconnect());
|
7472
7657
|
}
|
7658
|
+
|
7659
|
+
if (name === "required" && this.isConnected) {
|
7660
|
+
this.#validationTextArea.required = this.hasAttribute("required");
|
7661
|
+
this.#setValidity();
|
7662
|
+
}
|
7473
7663
|
}
|
7474
7664
|
|
7475
7665
|
formResetCallback() {
|
@@ -7523,7 +7713,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
7523
7713
|
Ys(Mi);
|
7524
7714
|
const root = ps();
|
7525
7715
|
root.clear();
|
7526
|
-
root.append(...this.#parseHtmlIntoLexicalNodes(html));
|
7716
|
+
if (html !== "") root.append(...this.#parseHtmlIntoLexicalNodes(html));
|
7527
7717
|
root.select();
|
7528
7718
|
|
7529
7719
|
this.#toggleEmptyStatus();
|
@@ -7636,6 +7826,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
7636
7826
|
|
7637
7827
|
this.internals.setFormValue(html);
|
7638
7828
|
this._internalFormValue = html;
|
7829
|
+
this.#validationTextArea.value = this.#isEmpty ? "" : html;
|
7639
7830
|
|
7640
7831
|
if (changed) {
|
7641
7832
|
dispatch(this, "lexxy:change");
|
@@ -7664,7 +7855,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
7664
7855
|
this.cachedValue = null;
|
7665
7856
|
this.#internalFormValue = this.value;
|
7666
7857
|
this.#toggleEmptyStatus();
|
7667
|
-
this.#
|
7858
|
+
this.#setValidity();
|
7668
7859
|
}));
|
7669
7860
|
}
|
7670
7861
|
|
@@ -7771,11 +7962,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
7771
7962
|
return !this.editorContentElement.textContent.trim() && !containsVisuallyRelevantChildren(this.editorContentElement)
|
7772
7963
|
}
|
7773
7964
|
|
7774
|
-
#
|
7775
|
-
if (this.
|
7776
|
-
this.internals.setValidity({ valueMissing: true }, "Please fill out this field.", this.editorContentElement);
|
7777
|
-
} else {
|
7965
|
+
#setValidity() {
|
7966
|
+
if (this.#validationTextArea.validity.valid) {
|
7778
7967
|
this.internals.setValidity({});
|
7968
|
+
} else {
|
7969
|
+
this.internals.setValidity(this.#validationTextArea.validity, this.#validationTextArea.validationMessage, this.editorContentElement);
|
7779
7970
|
}
|
7780
7971
|
}
|
7781
7972
|
|
Binary file
|
Binary file
|