lexxy 0.1.10.beta → 0.1.12.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 +216 -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: 18d165a2a610d44b12147598b0d876c26ac560805e026812b28bedf906fc79ce
|
|
4
|
+
data.tar.gz: b7f6432b4efa2b5674186f11f408ba0d9bebad133a35e6df45b9b70d87102bc5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7283442089ee6b769f928a9551c048da6c69e242fe5f7f7213bee79e03fb6567abaaa14319669bf3de62be571f5bbc60b825b5fa639a0ba0236f0559695498de
|
|
7
|
+
data.tar.gz: d9e94ff8f7a1a1493c9d3073aaa6e4bb56a21431ca42f6b61d6d37d4edca3286764f613dcd4b4ab65dce9c5d515349b1d3f4bbc8d38473ab99a85d010c42a6e6
|
|
@@ -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();
|
|
@@ -5551,6 +5562,7 @@ class ActionTextAttachmentNode extends gi {
|
|
|
5551
5562
|
conversion: () => ({
|
|
5552
5563
|
node: new ActionTextAttachmentNode({
|
|
5553
5564
|
src: img.getAttribute("src"),
|
|
5565
|
+
caption: img.getAttribute("alt") || "",
|
|
5554
5566
|
contentType: "image/*",
|
|
5555
5567
|
width: img.getAttribute("width"),
|
|
5556
5568
|
height: img.getAttribute("height")
|
|
@@ -5950,6 +5962,8 @@ class CommandDispatcher {
|
|
|
5950
5962
|
|
|
5951
5963
|
dispatchInsertUnorderedList() {
|
|
5952
5964
|
const selection = Nr();
|
|
5965
|
+
if (!selection) return;
|
|
5966
|
+
|
|
5953
5967
|
const anchorNode = selection.anchor.getNode();
|
|
5954
5968
|
|
|
5955
5969
|
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "bullet") {
|
|
@@ -5961,6 +5975,8 @@ class CommandDispatcher {
|
|
|
5961
5975
|
|
|
5962
5976
|
dispatchInsertOrderedList() {
|
|
5963
5977
|
const selection = Nr();
|
|
5978
|
+
if (!selection) return;
|
|
5979
|
+
|
|
5964
5980
|
const anchorNode = selection.anchor.getNode();
|
|
5965
5981
|
|
|
5966
5982
|
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "number") {
|
|
@@ -6133,11 +6149,14 @@ function nextFrame() {
|
|
|
6133
6149
|
class Selection {
|
|
6134
6150
|
constructor(editorElement) {
|
|
6135
6151
|
this.editorElement = editorElement;
|
|
6152
|
+
this.editorContentElement = editorElement.editorContentElement;
|
|
6136
6153
|
this.editor = this.editorElement.editor;
|
|
6137
6154
|
this.previouslySelectedKeys = new Set();
|
|
6138
6155
|
|
|
6139
6156
|
this.#listenForNodeSelections();
|
|
6140
6157
|
this.#processSelectionChangeCommands();
|
|
6158
|
+
this.#handleInputWhenDecoratorNodesSelected();
|
|
6159
|
+
this.#containEditorFocus();
|
|
6141
6160
|
}
|
|
6142
6161
|
|
|
6143
6162
|
clear() {
|
|
@@ -6231,6 +6250,21 @@ class Selection {
|
|
|
6231
6250
|
return this.#findNextSiblingUp(anchorNode)
|
|
6232
6251
|
}
|
|
6233
6252
|
|
|
6253
|
+
get topLevelNodeAfterCursor() {
|
|
6254
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
6255
|
+
if (!anchorNode) return null
|
|
6256
|
+
|
|
6257
|
+
if (Qn(anchorNode)) {
|
|
6258
|
+
return this.#getNextNodeFromTextEnd(anchorNode)
|
|
6259
|
+
}
|
|
6260
|
+
|
|
6261
|
+
if (di(anchorNode)) {
|
|
6262
|
+
return this.#getNodeAfterElementNode(anchorNode, offset)
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
return this.#findNextSiblingUp(anchorNode)
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6234
6268
|
get nodeBeforeCursor() {
|
|
6235
6269
|
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
6236
6270
|
if (!anchorNode) return null
|
|
@@ -6246,6 +6280,21 @@ class Selection {
|
|
|
6246
6280
|
return this.#findPreviousSiblingUp(anchorNode)
|
|
6247
6281
|
}
|
|
6248
6282
|
|
|
6283
|
+
get topLevelNodeBeforeCursor() {
|
|
6284
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
6285
|
+
if (!anchorNode) return null
|
|
6286
|
+
|
|
6287
|
+
if (Qn(anchorNode)) {
|
|
6288
|
+
return this.#getPreviousNodeFromTextStart(anchorNode)
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
if (di(anchorNode)) {
|
|
6292
|
+
return this.#getNodeBeforeElementNode(anchorNode, offset)
|
|
6293
|
+
}
|
|
6294
|
+
|
|
6295
|
+
return this.#findPreviousSiblingUp(anchorNode)
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6249
6298
|
get #contents() {
|
|
6250
6299
|
return this.editorElement.contents
|
|
6251
6300
|
}
|
|
@@ -6266,9 +6315,9 @@ class Selection {
|
|
|
6266
6315
|
|
|
6267
6316
|
#processSelectionChangeCommands() {
|
|
6268
6317
|
this.editor.registerCommand(Te$1, this.#selectPreviousNode.bind(this), Ii);
|
|
6269
|
-
this.editor.registerCommand(Ne$1, this.#selectPreviousNode.bind(this), Ii);
|
|
6270
6318
|
this.editor.registerCommand(ve$1, this.#selectNextNode.bind(this), Ii);
|
|
6271
|
-
this.editor.registerCommand(
|
|
6319
|
+
this.editor.registerCommand(Ne$1, this.#selectPreviousTopLevelNode.bind(this), Ii);
|
|
6320
|
+
this.editor.registerCommand(we$1, this.#selectNextTopLevelNode.bind(this), Ii);
|
|
6272
6321
|
|
|
6273
6322
|
this.editor.registerCommand(De$1, this.#deleteSelectedOrNext.bind(this), Ii);
|
|
6274
6323
|
this.editor.registerCommand(Ae$1, this.#deletePreviousOrNext.bind(this), Ii);
|
|
@@ -6299,6 +6348,93 @@ class Selection {
|
|
|
6299
6348
|
});
|
|
6300
6349
|
}
|
|
6301
6350
|
|
|
6351
|
+
// In Safari, when the only node in the document is an attachment, it won't let you enter text
|
|
6352
|
+
// before/below it. There is probably a better fix here, but this workaround solves the problem until
|
|
6353
|
+
// we find it.
|
|
6354
|
+
#handleInputWhenDecoratorNodesSelected() {
|
|
6355
|
+
this.editor.getRootElement().addEventListener("keydown", (event) => {
|
|
6356
|
+
if (isPrintableCharacter(event)) {
|
|
6357
|
+
this.editor.update(() => {
|
|
6358
|
+
const selection = Nr();
|
|
6359
|
+
|
|
6360
|
+
if (cr(selection) && selection.isCollapsed()) {
|
|
6361
|
+
const anchorNode = selection.anchor.getNode();
|
|
6362
|
+
const offset = selection.anchor.offset;
|
|
6363
|
+
|
|
6364
|
+
const nodeBefore = this.#getNodeBeforePosition(anchorNode, offset);
|
|
6365
|
+
const nodeAfter = this.#getNodeAfterPosition(anchorNode, offset);
|
|
6366
|
+
|
|
6367
|
+
if (nodeBefore instanceof gi && !nodeBefore.isInline()) {
|
|
6368
|
+
event.preventDefault();
|
|
6369
|
+
this.#contents.createParagraphAfterNode(nodeBefore, event.key);
|
|
6370
|
+
return
|
|
6371
|
+
} else if (nodeAfter instanceof gi && !nodeAfter.isInline()) {
|
|
6372
|
+
event.preventDefault();
|
|
6373
|
+
this.#contents.createParagraphBeforeNode(nodeAfter, event.key);
|
|
6374
|
+
return
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
});
|
|
6378
|
+
}
|
|
6379
|
+
}, true);
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
#getNodeBeforePosition(node, offset) {
|
|
6383
|
+
if (Qn(node) && offset === 0) {
|
|
6384
|
+
return node.getPreviousSibling()
|
|
6385
|
+
}
|
|
6386
|
+
if (di(node) && offset > 0) {
|
|
6387
|
+
return node.getChildAtIndex(offset - 1)
|
|
6388
|
+
}
|
|
6389
|
+
return null
|
|
6390
|
+
}
|
|
6391
|
+
|
|
6392
|
+
#getNodeAfterPosition(node, offset) {
|
|
6393
|
+
if (Qn(node) && offset === node.getTextContentSize()) {
|
|
6394
|
+
return node.getNextSibling()
|
|
6395
|
+
}
|
|
6396
|
+
if (di(node)) {
|
|
6397
|
+
return node.getChildAtIndex(offset)
|
|
6398
|
+
}
|
|
6399
|
+
return null
|
|
6400
|
+
}
|
|
6401
|
+
|
|
6402
|
+
#containEditorFocus() {
|
|
6403
|
+
// Workaround for a bizarre Chrome bug where the cursor abandons the editor to focus on not-focusable elements
|
|
6404
|
+
// above when navigating UP/DOWN when Lexical shows its fake cursor on custom decorator nodes.
|
|
6405
|
+
this.editorContentElement.addEventListener("keydown", (event) => {
|
|
6406
|
+
if (event.key === "ArrowUp") {
|
|
6407
|
+
const lexicalCursor = this.editor.getRootElement().querySelector('[data-lexical-cursor]');
|
|
6408
|
+
|
|
6409
|
+
if (lexicalCursor) {
|
|
6410
|
+
let currentElement = lexicalCursor.previousElementSibling;
|
|
6411
|
+
while (currentElement && currentElement.hasAttribute('data-lexical-cursor')) {
|
|
6412
|
+
currentElement = currentElement.previousElementSibling;
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6415
|
+
if (!currentElement) {
|
|
6416
|
+
event.preventDefault();
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
}
|
|
6420
|
+
|
|
6421
|
+
if (event.key === "ArrowDown") {
|
|
6422
|
+
const lexicalCursor = this.editor.getRootElement().querySelector('[data-lexical-cursor]');
|
|
6423
|
+
|
|
6424
|
+
if (lexicalCursor) {
|
|
6425
|
+
let currentElement = lexicalCursor.nextElementSibling;
|
|
6426
|
+
while (currentElement && currentElement.hasAttribute('data-lexical-cursor')) {
|
|
6427
|
+
currentElement = currentElement.nextElementSibling;
|
|
6428
|
+
}
|
|
6429
|
+
|
|
6430
|
+
if (!currentElement) {
|
|
6431
|
+
event.preventDefault();
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
}, true);
|
|
6436
|
+
}
|
|
6437
|
+
|
|
6302
6438
|
#syncSelectedClasses() {
|
|
6303
6439
|
this.#clearPreviouslyHighlightedItems();
|
|
6304
6440
|
this.#highlightNewItems();
|
|
@@ -6341,6 +6477,22 @@ class Selection {
|
|
|
6341
6477
|
}
|
|
6342
6478
|
}
|
|
6343
6479
|
|
|
6480
|
+
async #selectPreviousTopLevelNode() {
|
|
6481
|
+
if (this.current) {
|
|
6482
|
+
await this.#withCurrentNode((currentNode) => currentNode.selectPrevious());
|
|
6483
|
+
} else {
|
|
6484
|
+
this.#selectInLexical(this.topLevelNodeBeforeCursor);
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6488
|
+
async #selectNextTopLevelNode() {
|
|
6489
|
+
if (this.current) {
|
|
6490
|
+
await this.#withCurrentNode((currentNode) => currentNode.selectNext(0, 0));
|
|
6491
|
+
} else {
|
|
6492
|
+
this.#selectInLexical(this.topLevelNodeAfterCursor);
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6344
6496
|
async #withCurrentNode(fn) {
|
|
6345
6497
|
await nextFrame();
|
|
6346
6498
|
if (this.current) {
|
|
@@ -6895,6 +7047,30 @@ class Contents {
|
|
|
6895
7047
|
});
|
|
6896
7048
|
}
|
|
6897
7049
|
|
|
7050
|
+
createParagraphAfterNode(node, text) {
|
|
7051
|
+
const newParagraph = Pi();
|
|
7052
|
+
node.insertAfter(newParagraph);
|
|
7053
|
+
newParagraph.selectStart();
|
|
7054
|
+
|
|
7055
|
+
// Insert the typed text
|
|
7056
|
+
if (text) {
|
|
7057
|
+
newParagraph.append(Xn(text));
|
|
7058
|
+
newParagraph.select(1, 1); // Place cursor after the text
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
|
|
7062
|
+
createParagraphBeforeNode(node, text) {
|
|
7063
|
+
const newParagraph = Pi();
|
|
7064
|
+
node.insertBefore(newParagraph);
|
|
7065
|
+
newParagraph.selectStart();
|
|
7066
|
+
|
|
7067
|
+
// Insert the typed text
|
|
7068
|
+
if (text) {
|
|
7069
|
+
newParagraph.append(Xn(text));
|
|
7070
|
+
newParagraph.select(1, 1); // Place cursor after the text
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
|
|
6898
7074
|
uploadFile(file) {
|
|
6899
7075
|
if (!this.editorElement.supportsAttachments) {
|
|
6900
7076
|
console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
|
|
@@ -6929,25 +7105,34 @@ class Contents {
|
|
|
6929
7105
|
deleteSelectedNodes() {
|
|
6930
7106
|
this.editor.update(() => {
|
|
6931
7107
|
if (ur(this.#selection.current)) {
|
|
6932
|
-
|
|
6933
|
-
|
|
7108
|
+
const nodesToRemove = this.#selection.current.getNodes();
|
|
7109
|
+
if (nodesToRemove.length === 0) return
|
|
7110
|
+
|
|
7111
|
+
// Use splice() instead of node.remove() for proper removal and
|
|
7112
|
+
// reconciliation. Would have issues with removing unintended decorator nodes
|
|
7113
|
+
// with node.remove()
|
|
7114
|
+
nodesToRemove.forEach((node) => {
|
|
6934
7115
|
const parent = node.getParent();
|
|
7116
|
+
if (!di(parent)) return
|
|
6935
7117
|
|
|
6936
|
-
|
|
7118
|
+
const children = parent.getChildren();
|
|
7119
|
+
const index = children.indexOf(node);
|
|
6937
7120
|
|
|
6938
|
-
if (
|
|
6939
|
-
parent.
|
|
7121
|
+
if (index >= 0) {
|
|
7122
|
+
parent.splice(index, 1, []);
|
|
6940
7123
|
}
|
|
6941
|
-
|
|
6942
|
-
nodesWereRemoved = true;
|
|
6943
7124
|
});
|
|
6944
7125
|
|
|
6945
|
-
if
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
return true
|
|
7126
|
+
// Check if root is empty after all removals
|
|
7127
|
+
const root = ps();
|
|
7128
|
+
if (root.getChildrenSize() === 0) {
|
|
7129
|
+
root.append(Pi());
|
|
6950
7130
|
}
|
|
7131
|
+
|
|
7132
|
+
this.#selection.clear();
|
|
7133
|
+
this.editor.focus();
|
|
7134
|
+
|
|
7135
|
+
return true
|
|
6951
7136
|
}
|
|
6952
7137
|
});
|
|
6953
7138
|
}
|
|
@@ -7406,6 +7591,9 @@ class Clipboard {
|
|
|
7406
7591
|
#handlePastedFiles(clipboardData) {
|
|
7407
7592
|
if (!this.editorElement.supportsAttachments) return
|
|
7408
7593
|
|
|
7594
|
+
const html = clipboardData.getData('text/html');
|
|
7595
|
+
if (html) return // Ignore if image copied from browser since we will load it as a remote image
|
|
7596
|
+
|
|
7409
7597
|
this.#preservingScrollPosition(() => {
|
|
7410
7598
|
for (const item of clipboardData.items) {
|
|
7411
7599
|
const file = item.getAsFile();
|
|
@@ -7435,9 +7623,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7435
7623
|
static debug = true
|
|
7436
7624
|
static commands = [ "bold", "italic", "" ]
|
|
7437
7625
|
|
|
7438
|
-
static observedAttributes = [ "connected" ]
|
|
7626
|
+
static observedAttributes = [ "connected", "required" ]
|
|
7439
7627
|
|
|
7440
7628
|
#initialValue = ""
|
|
7629
|
+
#validationTextArea = document.createElement("textarea")
|
|
7441
7630
|
|
|
7442
7631
|
constructor() {
|
|
7443
7632
|
super();
|
|
@@ -7470,6 +7659,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7470
7659
|
if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) {
|
|
7471
7660
|
requestAnimationFrame(() => this.#reconnect());
|
|
7472
7661
|
}
|
|
7662
|
+
|
|
7663
|
+
if (name === "required" && this.isConnected) {
|
|
7664
|
+
this.#validationTextArea.required = this.hasAttribute("required");
|
|
7665
|
+
this.#setValidity();
|
|
7666
|
+
}
|
|
7473
7667
|
}
|
|
7474
7668
|
|
|
7475
7669
|
formResetCallback() {
|
|
@@ -7523,7 +7717,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7523
7717
|
Ys(Mi);
|
|
7524
7718
|
const root = ps();
|
|
7525
7719
|
root.clear();
|
|
7526
|
-
root.append(...this.#parseHtmlIntoLexicalNodes(html));
|
|
7720
|
+
if (html !== "") root.append(...this.#parseHtmlIntoLexicalNodes(html));
|
|
7527
7721
|
root.select();
|
|
7528
7722
|
|
|
7529
7723
|
this.#toggleEmptyStatus();
|
|
@@ -7636,6 +7830,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7636
7830
|
|
|
7637
7831
|
this.internals.setFormValue(html);
|
|
7638
7832
|
this._internalFormValue = html;
|
|
7833
|
+
this.#validationTextArea.value = this.#isEmpty ? "" : html;
|
|
7639
7834
|
|
|
7640
7835
|
if (changed) {
|
|
7641
7836
|
dispatch(this, "lexxy:change");
|
|
@@ -7664,7 +7859,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7664
7859
|
this.cachedValue = null;
|
|
7665
7860
|
this.#internalFormValue = this.value;
|
|
7666
7861
|
this.#toggleEmptyStatus();
|
|
7667
|
-
this.#
|
|
7862
|
+
this.#setValidity();
|
|
7668
7863
|
}));
|
|
7669
7864
|
}
|
|
7670
7865
|
|
|
@@ -7771,11 +7966,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7771
7966
|
return !this.editorContentElement.textContent.trim() && !containsVisuallyRelevantChildren(this.editorContentElement)
|
|
7772
7967
|
}
|
|
7773
7968
|
|
|
7774
|
-
#
|
|
7775
|
-
if (this.
|
|
7776
|
-
this.internals.setValidity({ valueMissing: true }, "Please fill out this field.", this.editorContentElement);
|
|
7777
|
-
} else {
|
|
7969
|
+
#setValidity() {
|
|
7970
|
+
if (this.#validationTextArea.validity.valid) {
|
|
7778
7971
|
this.internals.setValidity({});
|
|
7972
|
+
} else {
|
|
7973
|
+
this.internals.setValidity(this.#validationTextArea.validity, this.#validationTextArea.validationMessage, this.editorContentElement);
|
|
7779
7974
|
}
|
|
7780
7975
|
}
|
|
7781
7976
|
|
|
Binary file
|
|
Binary file
|