@37signals/lexxy 0.9.6-beta.bc0 → 0.9.8-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.
- package/README.md +2 -2
- package/dist/lexxy.esm.js +65 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A modern rich text editor for Rails.
|
|
4
4
|
|
|
5
5
|
> [!IMPORTANT]
|
|
6
|
-
> This is
|
|
6
|
+
> This is a beta. It hasn't been battle-tested yet. Please try it out and report any issues you find.
|
|
7
7
|
|
|
8
8
|
**[Try it out!](https://basecamp.github.io/lexxy/try-it)**
|
|
9
9
|
|
|
@@ -26,7 +26,7 @@ Visit the **[documentation site](https://basecamp.github.io/lexxy)**.
|
|
|
26
26
|
|
|
27
27
|
## Roadmap
|
|
28
28
|
|
|
29
|
-
This is
|
|
29
|
+
This is a beta. Here's what's coming next:
|
|
30
30
|
|
|
31
31
|
- [x] Configurable editors in Action Text: Choose your editor like you choose your database.
|
|
32
32
|
- [x] More editing features:
|
package/dist/lexxy.esm.js
CHANGED
|
@@ -2186,7 +2186,7 @@ class CommandDispatcher {
|
|
|
2186
2186
|
|
|
2187
2187
|
dispatchInsertUnorderedList() {
|
|
2188
2188
|
const selection = $getSelection();
|
|
2189
|
-
if (
|
|
2189
|
+
if (!$isRangeSelection(selection)) return
|
|
2190
2190
|
|
|
2191
2191
|
const anchorNode = selection.anchor.getNode();
|
|
2192
2192
|
|
|
@@ -2199,7 +2199,7 @@ class CommandDispatcher {
|
|
|
2199
2199
|
|
|
2200
2200
|
dispatchInsertOrderedList() {
|
|
2201
2201
|
const selection = $getSelection();
|
|
2202
|
-
if (
|
|
2202
|
+
if (!$isRangeSelection(selection)) return
|
|
2203
2203
|
|
|
2204
2204
|
const anchorNode = selection.anchor.getNode();
|
|
2205
2205
|
|
|
@@ -2491,6 +2491,15 @@ function capitalize(str) {
|
|
|
2491
2491
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
2492
2492
|
}
|
|
2493
2493
|
|
|
2494
|
+
function debounce(fn, wait) {
|
|
2495
|
+
let timeout;
|
|
2496
|
+
|
|
2497
|
+
return (...args) => {
|
|
2498
|
+
clearTimeout(timeout);
|
|
2499
|
+
timeout = setTimeout(() => fn(...args), wait);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2494
2503
|
function debounceAsync(fn, wait) {
|
|
2495
2504
|
let timeout;
|
|
2496
2505
|
|
|
@@ -2533,14 +2542,24 @@ function normalizeFilteredText(string) {
|
|
|
2533
2542
|
.normalize("NFD").replace(/[\u0300-\u036f]/g, "") // Remove diacritics
|
|
2534
2543
|
}
|
|
2535
2544
|
|
|
2536
|
-
function
|
|
2537
|
-
|
|
2545
|
+
function filterMatchPosition(text, potentialMatch) {
|
|
2546
|
+
const normalizedText = normalizeFilteredText(text);
|
|
2547
|
+
const normalizedMatch = normalizeFilteredText(potentialMatch);
|
|
2548
|
+
|
|
2549
|
+
if (!normalizedMatch) return 0
|
|
2550
|
+
|
|
2551
|
+
const match = normalizedText.match(new RegExp(`(?:^|\\b)${escapeForRegExp(normalizedMatch)}`));
|
|
2552
|
+
return match ? match.index : -1
|
|
2538
2553
|
}
|
|
2539
2554
|
|
|
2540
2555
|
function upcaseFirst(string) {
|
|
2541
2556
|
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
2542
2557
|
}
|
|
2543
2558
|
|
|
2559
|
+
function escapeForRegExp(string) {
|
|
2560
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2544
2563
|
// Parses a value that may arrive as a boolean or as a string (e.g. from DOM
|
|
2545
2564
|
// getAttribute) into a proper boolean. Ensures "false" doesn't evaluate as truthy.
|
|
2546
2565
|
function parseBoolean(value) {
|
|
@@ -5914,9 +5933,9 @@ class AttachmentDragAndDrop {
|
|
|
5914
5933
|
// -- Event handlers --------------------------------------------------------
|
|
5915
5934
|
|
|
5916
5935
|
#handleDragStart(event) {
|
|
5917
|
-
if (event.target.closest("textarea")) return false
|
|
5936
|
+
if (event.target.closest?.("textarea")) return false
|
|
5918
5937
|
|
|
5919
|
-
const figure = event.target.closest("figure.attachment[data-lexical-node-key]");
|
|
5938
|
+
const figure = event.target.closest?.("figure.attachment[data-lexical-node-key]");
|
|
5920
5939
|
if (!figure) return false
|
|
5921
5940
|
|
|
5922
5941
|
this.#draggedNodeKey = figure.dataset.lexicalNodeKey;
|
|
@@ -7361,7 +7380,7 @@ class LinkDropdown extends ToolbarDropdown {
|
|
|
7361
7380
|
get #selectedLinkUrl() {
|
|
7362
7381
|
return this.editor.getEditorState().read(() => {
|
|
7363
7382
|
const linkNode = this.editorElement.selection.nearestNodeOfType(LinkNode);
|
|
7364
|
-
return linkNode?.
|
|
7383
|
+
return linkNode?.getURL() ?? ""
|
|
7365
7384
|
})
|
|
7366
7385
|
}
|
|
7367
7386
|
}
|
|
@@ -7507,6 +7526,8 @@ class BaseSource {
|
|
|
7507
7526
|
}
|
|
7508
7527
|
}
|
|
7509
7528
|
|
|
7529
|
+
const MAX_RENDERED_SUGGESTIONS$1 = 100;
|
|
7530
|
+
|
|
7510
7531
|
class LocalFilterSource extends BaseSource {
|
|
7511
7532
|
async buildListItems(filter = "") {
|
|
7512
7533
|
const promptItems = await this.fetchPromptItems();
|
|
@@ -7523,18 +7544,41 @@ class LocalFilterSource extends BaseSource {
|
|
|
7523
7544
|
}
|
|
7524
7545
|
|
|
7525
7546
|
#buildListItemsFromPromptItems(promptItems, filter) {
|
|
7526
|
-
const listItems = [];
|
|
7527
7547
|
this.promptItemByListItem = new WeakMap();
|
|
7528
|
-
promptItems.forEach((promptItem) => {
|
|
7529
|
-
const searchableText = promptItem.getAttribute("search");
|
|
7530
7548
|
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7549
|
+
if (!filter) {
|
|
7550
|
+
return this.#buildAllListItems(promptItems)
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7553
|
+
const matches = [];
|
|
7554
|
+
for (const promptItem of promptItems) {
|
|
7555
|
+
const searchableText = promptItem.getAttribute("search");
|
|
7556
|
+
const position = filterMatchPosition(searchableText, filter);
|
|
7557
|
+
if (position >= 0) {
|
|
7558
|
+
matches.push({ promptItem, position });
|
|
7535
7559
|
}
|
|
7536
|
-
}
|
|
7560
|
+
}
|
|
7537
7561
|
|
|
7562
|
+
matches.sort((a, b) => a.position - b.position);
|
|
7563
|
+
|
|
7564
|
+
const listItems = [];
|
|
7565
|
+
for (const { promptItem } of matches) {
|
|
7566
|
+
if (listItems.length >= MAX_RENDERED_SUGGESTIONS$1) break
|
|
7567
|
+
const listItem = this.buildListItemElementFor(promptItem);
|
|
7568
|
+
this.promptItemByListItem.set(listItem, promptItem);
|
|
7569
|
+
listItems.push(listItem);
|
|
7570
|
+
}
|
|
7571
|
+
return listItems
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
#buildAllListItems(promptItems) {
|
|
7575
|
+
const listItems = [];
|
|
7576
|
+
for (const promptItem of promptItems) {
|
|
7577
|
+
if (listItems.length >= MAX_RENDERED_SUGGESTIONS$1) break
|
|
7578
|
+
const listItem = this.buildListItemElementFor(promptItem);
|
|
7579
|
+
this.promptItemByListItem.set(listItem, promptItem);
|
|
7580
|
+
listItems.push(listItem);
|
|
7581
|
+
}
|
|
7538
7582
|
return listItems
|
|
7539
7583
|
}
|
|
7540
7584
|
}
|
|
@@ -7566,6 +7610,7 @@ class DeferredPromptSource extends LocalFilterSource {
|
|
|
7566
7610
|
}
|
|
7567
7611
|
|
|
7568
7612
|
const DEBOUNCE_INTERVAL = 200;
|
|
7613
|
+
const MAX_RENDERED_SUGGESTIONS = 100;
|
|
7569
7614
|
|
|
7570
7615
|
class RemoteFilterSource extends BaseSource {
|
|
7571
7616
|
constructor(url) {
|
|
@@ -7599,6 +7644,8 @@ class RemoteFilterSource extends BaseSource {
|
|
|
7599
7644
|
this.promptItemByListItem = new WeakMap();
|
|
7600
7645
|
|
|
7601
7646
|
for (const promptItem of promptItems) {
|
|
7647
|
+
if (listItems.length >= MAX_RENDERED_SUGGESTIONS) break
|
|
7648
|
+
|
|
7602
7649
|
const listItem = this.buildListItemElementFor(promptItem);
|
|
7603
7650
|
this.promptItemByListItem.set(listItem, promptItem);
|
|
7604
7651
|
listItems.push(listItem);
|
|
@@ -7609,10 +7656,12 @@ class RemoteFilterSource extends BaseSource {
|
|
|
7609
7656
|
}
|
|
7610
7657
|
|
|
7611
7658
|
const NOTHING_FOUND_DEFAULT_MESSAGE = "Nothing found";
|
|
7659
|
+
const FILTER_DEBOUNCE_INTERVAL = 50;
|
|
7612
7660
|
|
|
7613
7661
|
class LexicalPromptElement extends HTMLElement {
|
|
7614
7662
|
#globalListeners = new ListenerBin()
|
|
7615
7663
|
#popoverListeners = new ListenerBin()
|
|
7664
|
+
#debouncedFilterOptions = debounce(() => this.#filterOptions(), FILTER_DEBOUNCE_INTERVAL)
|
|
7616
7665
|
|
|
7617
7666
|
constructor() {
|
|
7618
7667
|
super();
|
|
@@ -7770,7 +7819,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
7770
7819
|
|
|
7771
7820
|
this.#popoverListeners.track(
|
|
7772
7821
|
registerEventListener(this.#editorElement, "keydown", this.#handleKeydownOnPopover),
|
|
7773
|
-
registerEventListener(this.#editorElement, "lexxy:change", this.#
|
|
7822
|
+
registerEventListener(this.#editorElement, "lexxy:change", this.#debouncedFilterOptions)
|
|
7774
7823
|
);
|
|
7775
7824
|
|
|
7776
7825
|
this.#registerKeyListeners();
|