@37signals/lexxy 0.1.22-beta → 0.1.24-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 +1 -1
- package/dist/lexxy.esm.js +217 -183
- package/dist/stylesheets/lexxy-content.css +400 -0
- package/dist/stylesheets/lexxy-editor.css +451 -0
- package/dist/stylesheets/lexxy-variables.css +73 -0
- package/dist/stylesheets/lexxy.css +2 -0
- package/package.json +5 -2
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { $isQuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
6
|
-
import { $isCodeNode, CodeNode, normalizeCodeLang, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
|
|
7
|
-
import { $isLinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
|
|
1
|
+
import Prism from 'prismjs';
|
|
2
|
+
import 'prismjs/components/prism-clike';
|
|
3
|
+
import 'prismjs/components/prism-markup';
|
|
4
|
+
import 'prismjs/components/prism-markup-templating';
|
|
8
5
|
import 'prismjs/components/prism-ruby';
|
|
9
6
|
import 'prismjs/components/prism-php';
|
|
10
7
|
import 'prismjs/components/prism-go';
|
|
11
8
|
import 'prismjs/components/prism-bash';
|
|
12
9
|
import 'prismjs/components/prism-json';
|
|
13
10
|
import 'prismjs/components/prism-diff';
|
|
11
|
+
import DOMPurify from 'dompurify';
|
|
12
|
+
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection';
|
|
13
|
+
import { $isTextNode, TextNode, $isRangeSelection, $getSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, $createTextNode, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, KEY_TAB_COMMAND, COMMAND_PRIORITY_NORMAL, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, $getRoot, $isLineBreakNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, $insertNodes, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, BLUR_COMMAND, FOCUS_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
|
|
14
|
+
import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $createListNode, ListNode, ListItemNode, registerList } from '@lexical/list';
|
|
15
|
+
import { $isQuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
16
|
+
import { $isCodeNode, CodeNode, normalizeCodeLang, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
|
|
17
|
+
import { $isLinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
|
|
14
18
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
15
19
|
import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
|
|
16
20
|
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
17
21
|
import { DirectUpload } from '@rails/activestorage';
|
|
18
22
|
import { marked } from 'marked';
|
|
19
23
|
|
|
24
|
+
// Configure Prism for manual highlighting mode
|
|
25
|
+
// This must be set before importing prismjs
|
|
26
|
+
window.Prism = window.Prism || {};
|
|
27
|
+
window.Prism.manual = true;
|
|
28
|
+
|
|
20
29
|
const ALLOWED_HTML_TAGS = [ "a", "action-text-attachment", "b", "blockquote", "br", "code", "em",
|
|
21
30
|
"figcaption", "figure", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "li", "mark", "ol", "p", "pre", "q", "s", "strong", "ul" ];
|
|
22
31
|
|
|
@@ -136,6 +145,8 @@ function hasHighlightStyles(cssOrStyles) {
|
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
class LexicalToolbarElement extends HTMLElement {
|
|
148
|
+
static observedAttributes = [ "connected" ]
|
|
149
|
+
|
|
139
150
|
constructor() {
|
|
140
151
|
super();
|
|
141
152
|
this.internals = this.attachInternals();
|
|
@@ -154,6 +165,13 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
154
165
|
this._resizeObserver.disconnect();
|
|
155
166
|
this._resizeObserver = null;
|
|
156
167
|
}
|
|
168
|
+
this.#unbindHotkeys();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
172
|
+
if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) {
|
|
173
|
+
requestAnimationFrame(() => this.#reconnect());
|
|
174
|
+
}
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
setEditor(editorElement) {
|
|
@@ -161,7 +179,8 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
161
179
|
this.editor = editorElement.editor;
|
|
162
180
|
this.#bindButtons();
|
|
163
181
|
this.#bindHotkeys();
|
|
164
|
-
this.#
|
|
182
|
+
this.#setTabIndexValues();
|
|
183
|
+
this.#setItemPositionValues();
|
|
165
184
|
this.#monitorSelectionChanges();
|
|
166
185
|
this.#monitorHistoryChanges();
|
|
167
186
|
this.#refreshToolbarOverflow();
|
|
@@ -169,10 +188,9 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
169
188
|
this.toggleAttribute("connected", true);
|
|
170
189
|
}
|
|
171
190
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return Array.from(this.querySelectorAll(dialogTags))
|
|
191
|
+
#reconnect() {
|
|
192
|
+
this.disconnectedCallback();
|
|
193
|
+
this.connectedCallback();
|
|
176
194
|
}
|
|
177
195
|
|
|
178
196
|
#bindButtons() {
|
|
@@ -181,7 +199,6 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
181
199
|
|
|
182
200
|
#handleButtonClicked({ target }) {
|
|
183
201
|
this.#handleTargetClicked(target, "[data-command]", this.#dispatchButtonCommand.bind(this));
|
|
184
|
-
this.#handleTargetClicked(target, "[data-dialog-target]", this.#toggleDialog.bind(this));
|
|
185
202
|
}
|
|
186
203
|
|
|
187
204
|
#handleTargetClicked(target, selector, callback) {
|
|
@@ -196,38 +213,23 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
196
213
|
this.editor.dispatchCommand(command, payload);
|
|
197
214
|
}
|
|
198
215
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const dialogTarget = button.dataset.dialogTarget;
|
|
202
|
-
const dialog = this.querySelector("lexxy-" + dialogTarget);
|
|
203
|
-
if (!dialog) return
|
|
204
|
-
|
|
205
|
-
if (dialog.open) {
|
|
206
|
-
dialog.close();
|
|
207
|
-
} else {
|
|
208
|
-
this.#closeOpenDialogs();
|
|
209
|
-
dialog.show(button);
|
|
210
|
-
}
|
|
216
|
+
#bindHotkeys() {
|
|
217
|
+
this.editorElement.addEventListener("keydown", this.#handleHotkey);
|
|
211
218
|
}
|
|
212
219
|
|
|
213
|
-
#
|
|
214
|
-
|
|
215
|
-
openDialogs.forEach(openDialog => {
|
|
216
|
-
openDialog.closest(".lexxy-dialog").close();
|
|
217
|
-
});
|
|
220
|
+
#unbindHotkeys() {
|
|
221
|
+
this.editorElement?.removeEventListener("keydown", this.#handleHotkey);
|
|
218
222
|
}
|
|
219
223
|
|
|
220
|
-
#
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
});
|
|
224
|
+
#handleHotkey = (event) => {
|
|
225
|
+
const buttons = this.querySelectorAll("[data-hotkey]");
|
|
226
|
+
buttons.forEach((button) => {
|
|
227
|
+
const hotkeys = button.dataset.hotkey.toLowerCase().split(/\s+/);
|
|
228
|
+
if (hotkeys.includes(this.#keyCombinationFor(event))) {
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
event.stopPropagation();
|
|
231
|
+
button.click();
|
|
232
|
+
}
|
|
231
233
|
});
|
|
232
234
|
}
|
|
233
235
|
|
|
@@ -243,10 +245,9 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
243
245
|
return [ ...modifiers, pressedKey ].join("+")
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
button.setAttribute("tabindex", `${baseTabIndex + index + 1}`);
|
|
248
|
+
#setTabIndexValues() {
|
|
249
|
+
this.#buttons.forEach((button) => {
|
|
250
|
+
button.setAttribute("tabindex", 0);
|
|
250
251
|
});
|
|
251
252
|
}
|
|
252
253
|
|
|
@@ -254,7 +255,6 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
254
255
|
this.editor.registerUpdateListener(() => {
|
|
255
256
|
this.editor.getEditorState().read(() => {
|
|
256
257
|
this.#updateButtonStates();
|
|
257
|
-
this.#updateDialogStates();
|
|
258
258
|
});
|
|
259
259
|
});
|
|
260
260
|
}
|
|
@@ -309,10 +309,6 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
309
309
|
this.#updateUndoRedoButtonStates();
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
#updateDialogStates() {
|
|
313
|
-
this.#dialogs.forEach(dialog => dialog.updateStateCallback());
|
|
314
|
-
}
|
|
315
|
-
|
|
316
312
|
#isInList(node) {
|
|
317
313
|
let current = node;
|
|
318
314
|
while (current) {
|
|
@@ -361,22 +357,8 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
361
357
|
this.toggleAttribute("overflowing", isOverflowing);
|
|
362
358
|
}
|
|
363
359
|
|
|
364
|
-
get #overflow() {
|
|
365
|
-
return this.querySelector(".lexxy-editor__toolbar-overflow")
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
get #overflowMenu() {
|
|
369
|
-
return this.querySelector(".lexxy-editor__toolbar-overflow-menu")
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
#resetToolbar() {
|
|
373
|
-
while (this.#overflowMenu.children.length > 0) {
|
|
374
|
-
this.insertBefore(this.#overflowMenu.children[0], this.#overflow);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
360
|
#compactMenu() {
|
|
379
|
-
const buttons = this.#
|
|
361
|
+
const buttons = this.#buttons.reverse();
|
|
380
362
|
let movedToOverflow = false;
|
|
381
363
|
|
|
382
364
|
for (const button of buttons) {
|
|
@@ -390,12 +372,42 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
390
372
|
}
|
|
391
373
|
}
|
|
392
374
|
|
|
375
|
+
#resetToolbar() {
|
|
376
|
+
const items = Array.from(this.#overflowMenu.children);
|
|
377
|
+
items.sort((a, b) => this.#itemPosition(b) - this.#itemPosition(a));
|
|
378
|
+
|
|
379
|
+
items.forEach((item) => {
|
|
380
|
+
const nextItem = this.querySelector(`[data-position="${this.#itemPosition(item) + 1}"]`) ?? this.#overflow;
|
|
381
|
+
this.insertBefore(item, nextItem);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#itemPosition(item) {
|
|
386
|
+
return parseInt(item.dataset.position ?? "999")
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#setItemPositionValues() {
|
|
390
|
+
this.#toolbarItems.forEach((item, index) => {
|
|
391
|
+
if (item.dataset.position === undefined) {
|
|
392
|
+
item.dataset.position = index;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
get #overflow() {
|
|
398
|
+
return this.querySelector(".lexxy-editor__toolbar-overflow")
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
get #overflowMenu() {
|
|
402
|
+
return this.querySelector(".lexxy-editor__toolbar-overflow-menu")
|
|
403
|
+
}
|
|
404
|
+
|
|
393
405
|
get #buttons() {
|
|
394
406
|
return Array.from(this.querySelectorAll(":scope > button"))
|
|
395
407
|
}
|
|
396
408
|
|
|
397
|
-
get #
|
|
398
|
-
return Array.from(this.querySelectorAll(":scope >
|
|
409
|
+
get #toolbarItems() {
|
|
410
|
+
return Array.from(this.querySelectorAll(":scope > *:not(.lexxy-editor__toolbar-overflow)"))
|
|
399
411
|
}
|
|
400
412
|
|
|
401
413
|
static get defaultTemplate() {
|
|
@@ -414,35 +426,31 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
414
426
|
</svg>
|
|
415
427
|
</button>
|
|
416
428
|
|
|
417
|
-
<
|
|
418
|
-
<
|
|
419
|
-
<
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="highlight" title="Color highlight" data-dialog-target="highlight-dialog">
|
|
428
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.65422 0.711575C7.1856 0.242951 6.42579 0.242951 5.95717 0.711575C5.48853 1.18021 5.48853 1.94 5.95717 2.40864L8.70864 5.16011L2.85422 11.0145C1.44834 12.4204 1.44833 14.6998 2.85422 16.1057L7.86011 21.1115C9.26599 22.5174 11.5454 22.5174 12.9513 21.1115L19.6542 14.4087C20.1228 13.94 20.1228 13.1802 19.6542 12.7115L11.8544 4.91171L11.2542 4.31158L7.65422 0.711575ZM4.55127 12.7115L10.4057 6.85716L17.1087 13.56H4.19981C4.19981 13.253 4.31696 12.9459 4.55127 12.7115ZM23.6057 20.76C23.6057 22.0856 22.5311 23.16 21.2057 23.16C19.8802 23.16 18.8057 22.0856 18.8057 20.76C18.8057 19.5408 19.8212 18.5339 20.918 17.4462C21.0135 17.3516 21.1096 17.2563 21.2057 17.16C21.3018 17.2563 21.398 17.3516 21.4935 17.4462C22.5903 18.5339 23.6057 19.5408 23.6057 20.76Z"/></svg>
|
|
429
|
-
</button>
|
|
429
|
+
<details class="lexxy-editor__toolbar-dropdown" name="lexxy-dropdown">
|
|
430
|
+
<summary class="lexxy-editor__toolbar-button" name="highlight" title="Color highlight">
|
|
431
|
+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.65422 0.711575C7.1856 0.242951 6.42579 0.242951 5.95717 0.711575C5.48853 1.18021 5.48853 1.94 5.95717 2.40864L8.70864 5.16011L2.85422 11.0145C1.44834 12.4204 1.44833 14.6998 2.85422 16.1057L7.86011 21.1115C9.26599 22.5174 11.5454 22.5174 12.9513 21.1115L19.6542 14.4087C20.1228 13.94 20.1228 13.1802 19.6542 12.7115L11.8544 4.91171L11.2542 4.31158L7.65422 0.711575ZM4.55127 12.7115L10.4057 6.85716L17.1087 13.56H4.19981C4.19981 13.253 4.31696 12.9459 4.55127 12.7115ZM23.6057 20.76C23.6057 22.0856 22.5311 23.16 21.2057 23.16C19.8802 23.16 18.8057 22.0856 18.8057 20.76C18.8057 19.5408 19.8212 18.5339 20.918 17.4462C21.0135 17.3516 21.1096 17.2563 21.2057 17.16C21.3018 17.2563 21.398 17.3516 21.4935 17.4462C22.5903 18.5339 23.6057 19.5408 23.6057 20.76Z"/></svg>
|
|
432
|
+
</summary>
|
|
433
|
+
<lexxy-highlight-dropdown class="lexxy-editor__toolbar-dropdown-content">
|
|
434
|
+
<div data-button-group="color" data-values="var(--highlight-1); var(--highlight-2); var(--highlight-3); var(--highlight-4); var(--highlight-5); var(--highlight-6); var(--highlight-7); var(--highlight-8); var(--highlight-9)"></div>
|
|
435
|
+
<div data-button-group="background-color" data-values="var(--highlight-bg-1); var(--highlight-bg-2); var(--highlight-bg-3); var(--highlight-bg-4); var(--highlight-bg-5); var(--highlight-bg-6); var(--highlight-bg-7); var(--highlight-bg-8); var(--highlight-bg-9)"></div>
|
|
436
|
+
<button data-command="removeHighlight" class="lexxy-editor__toolbar-dropdown-reset">Remove all coloring</button>
|
|
437
|
+
</lexxy-highlight-dropdown>
|
|
438
|
+
</details>
|
|
430
439
|
|
|
431
|
-
<
|
|
432
|
-
<
|
|
440
|
+
<details class="lexxy-editor__toolbar-dropdown" name="lexxy-dropdown">
|
|
441
|
+
<summary class="lexxy-editor__toolbar-button" name="link" title="Link" data-hotkey="cmd+k ctrl+k">
|
|
442
|
+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.111 9.546a1.5 1.5 0 012.121 0 5.5 5.5 0 010 7.778l-2.828 2.828a5.5 5.5 0 01-7.778 0 5.498 5.498 0 010-7.777l2.828-2.83a1.5 1.5 0 01.355-.262 6.52 6.52 0 00.351 3.799l-1.413 1.414a2.499 2.499 0 000 3.535 2.499 2.499 0 003.535 0l2.83-2.828a2.5 2.5 0 000-3.536 1.5 1.5 0 010-2.121z"/><path d="M12.111 3.89a5.5 5.5 0 117.778 7.777l-2.828 2.829a1.496 1.496 0 01-.355.262 6.522 6.522 0 00-.351-3.8l1.413-1.412a2.5 2.5 0 10-3.536-3.535l-2.828 2.828a2.5 2.5 0 000 3.536 1.5 1.5 0 01-2.122 2.12 5.5 5.5 0 010-7.777l2.83-2.829z"/></svg>
|
|
443
|
+
</summary>
|
|
444
|
+
<lexxy-link-dropdown class="lexxy-editor__toolbar-dropdown-content">
|
|
433
445
|
<form method="dialog">
|
|
434
|
-
<input type="url" placeholder="Enter a URL…" class="input"
|
|
435
|
-
<div class="lexxy-
|
|
446
|
+
<input type="url" placeholder="Enter a URL…" class="input">
|
|
447
|
+
<div class="lexxy-editor__toolbar-dropdown-actions">
|
|
436
448
|
<button type="submit" class="btn" value="link">Link</button>
|
|
437
449
|
<button type="button" class="btn" value="unlink">Unlink</button>
|
|
438
450
|
</div>
|
|
439
451
|
</form>
|
|
440
|
-
</
|
|
441
|
-
</
|
|
442
|
-
|
|
443
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="link" title="Link" data-dialog-target="link-dialog" data-hotkey="cmd+k ctrl+k">
|
|
444
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.111 9.546a1.5 1.5 0 012.121 0 5.5 5.5 0 010 7.778l-2.828 2.828a5.5 5.5 0 01-7.778 0 5.498 5.498 0 010-7.777l2.828-2.83a1.5 1.5 0 01.355-.262 6.52 6.52 0 00.351 3.799l-1.413 1.414a2.499 2.499 0 000 3.535 2.499 2.499 0 003.535 0l2.83-2.828a2.5 2.5 0 000-3.536 1.5 1.5 0 010-2.121z"/><path d="M12.111 3.89a5.5 5.5 0 117.778 7.777l-2.828 2.829a1.496 1.496 0 01-.355.262 6.522 6.522 0 00-.351-3.8l1.413-1.412a2.5 2.5 0 10-3.536-3.535l-2.828 2.828a2.5 2.5 0 000 3.536 1.5 1.5 0 01-2.122 2.12 5.5 5.5 0 010-7.777l2.83-2.829z"/></svg>
|
|
445
|
-
</button>
|
|
452
|
+
</lexxy-link-dropdown>
|
|
453
|
+
</details>
|
|
446
454
|
|
|
447
455
|
<button class="lexxy-editor__toolbar-button" type="button" name="quote" data-command="insertQuoteBlock" title="Quote">
|
|
448
456
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.5 5C8.985 5 11 7.09 11 9.667c0 2.694-.962 5.005-2.187 6.644-.613.82-1.3 1.481-1.978 1.943-.668.454-1.375.746-2.022.746a.563.563 0 01-.52-.36.602.602 0 01.067-.57l.055-.066.009-.009.041-.048a4.25 4.25 0 00.168-.21c.143-.188.336-.47.53-.84a6.743 6.743 0 00.75-2.605C3.705 13.994 2 12.038 2 9.667 2 7.089 4.015 5 6.5 5zM17.5 5C19.985 5 22 7.09 22 9.667c0 2.694-.962 5.005-2.187 6.644-.613.82-1.3 1.481-1.978 1.943-.668.454-1.375.746-2.023.746a.563.563 0 01-.52-.36.602.602 0 01.068-.57l.055-.066.009-.009.041-.048c.039-.045.097-.115.168-.21a6.16 6.16 0 00.53-.84 6.745 6.745 0 00.75-2.605C14.705 13.994 13 12.038 13 9.667 13 7.089 15.015 5 17.5 5z"/></svg>
|
|
@@ -879,6 +887,11 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
879
887
|
return new ActionTextAttachmentUploadNode({ ...serializedNode })
|
|
880
888
|
}
|
|
881
889
|
|
|
890
|
+
// Should never run since this is a transient node. Defined to remove console warning.
|
|
891
|
+
static importDOM() {
|
|
892
|
+
return null
|
|
893
|
+
}
|
|
894
|
+
|
|
882
895
|
constructor({ file, uploadUrl, blobUrlTemplate, editor, progress }, key) {
|
|
883
896
|
super({ contentType: file.type }, key);
|
|
884
897
|
this.file = file;
|
|
@@ -1136,6 +1149,7 @@ class CommandDispatcher {
|
|
|
1136
1149
|
this.highlighter = editorElement.highlighter;
|
|
1137
1150
|
|
|
1138
1151
|
this.#registerCommands();
|
|
1152
|
+
this.#registerKeyboardCommands();
|
|
1139
1153
|
this.#registerDragAndDropHandlers();
|
|
1140
1154
|
}
|
|
1141
1155
|
|
|
@@ -1300,15 +1314,8 @@ class CommandDispatcher {
|
|
|
1300
1314
|
this.editor.registerCommand(command, handler, priority);
|
|
1301
1315
|
}
|
|
1302
1316
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
this.editor.update(() => {
|
|
1306
|
-
if (url === null) {
|
|
1307
|
-
$toggleLink(null);
|
|
1308
|
-
} else {
|
|
1309
|
-
$toggleLink(url);
|
|
1310
|
-
}
|
|
1311
|
-
});
|
|
1317
|
+
#registerKeyboardCommands() {
|
|
1318
|
+
this.editor.registerCommand(KEY_TAB_COMMAND, this.#handleListIndentation.bind(this), COMMAND_PRIORITY_NORMAL);
|
|
1312
1319
|
}
|
|
1313
1320
|
|
|
1314
1321
|
#registerDragAndDropHandlers() {
|
|
@@ -1357,6 +1364,29 @@ class CommandDispatcher {
|
|
|
1357
1364
|
|
|
1358
1365
|
this.editor.focus();
|
|
1359
1366
|
}
|
|
1367
|
+
|
|
1368
|
+
#handleListIndentation(event) {
|
|
1369
|
+
if (this.selection.isInsideList) {
|
|
1370
|
+
event.preventDefault();
|
|
1371
|
+
if (event.shiftKey) {
|
|
1372
|
+
return this.editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined)
|
|
1373
|
+
} else {
|
|
1374
|
+
return this.editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined)
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return false
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// Not using TOGGLE_LINK_COMMAND because it's not handled unless you use React/LinkPlugin
|
|
1381
|
+
#toggleLink(url) {
|
|
1382
|
+
this.editor.update(() => {
|
|
1383
|
+
if (url === null) {
|
|
1384
|
+
$toggleLink(null);
|
|
1385
|
+
} else {
|
|
1386
|
+
$toggleLink(url);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1360
1390
|
}
|
|
1361
1391
|
|
|
1362
1392
|
function capitalize(str) {
|
|
@@ -3163,7 +3193,7 @@ class Clipboard {
|
|
|
3163
3193
|
|
|
3164
3194
|
if (!clipboardData) return false
|
|
3165
3195
|
|
|
3166
|
-
if (this.#
|
|
3196
|
+
if (this.#isPlainTextOrURLPasted(clipboardData) && !this.#isPastingIntoCodeBlock()) {
|
|
3167
3197
|
this.#pastePlainText(clipboardData);
|
|
3168
3198
|
event.preventDefault();
|
|
3169
3199
|
return true
|
|
@@ -3172,11 +3202,21 @@ class Clipboard {
|
|
|
3172
3202
|
this.#handlePastedFiles(clipboardData);
|
|
3173
3203
|
}
|
|
3174
3204
|
|
|
3205
|
+
#isPlainTextOrURLPasted(clipboardData) {
|
|
3206
|
+
return this.#isOnlyPlainTextPasted(clipboardData) || this.#isOnlyURLPasted(clipboardData)
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3175
3209
|
#isOnlyPlainTextPasted(clipboardData) {
|
|
3176
3210
|
const types = Array.from(clipboardData.types);
|
|
3177
3211
|
return types.length === 1 && types[0] === "text/plain"
|
|
3178
3212
|
}
|
|
3179
3213
|
|
|
3214
|
+
#isOnlyURLPasted(clipboardData) {
|
|
3215
|
+
// Safari URLs are copied as a text/plain + text/uri-list object
|
|
3216
|
+
const types = Array.from(clipboardData.types);
|
|
3217
|
+
return types.length === 2 && types.includes("text/uri-list") && types.includes("text/plain")
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3180
3220
|
#isPastingIntoCodeBlock() {
|
|
3181
3221
|
let result = false;
|
|
3182
3222
|
|
|
@@ -3475,6 +3515,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
3475
3515
|
return this.getAttribute("attachments") !== "false"
|
|
3476
3516
|
}
|
|
3477
3517
|
|
|
3518
|
+
get contentTabIndex() {
|
|
3519
|
+
return parseInt(this.editorContentElement?.getAttribute("tabindex") ?? "0")
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3478
3522
|
focus() {
|
|
3479
3523
|
this.editor.focus();
|
|
3480
3524
|
}
|
|
@@ -3788,39 +3832,26 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
3788
3832
|
|
|
3789
3833
|
#reconnect() {
|
|
3790
3834
|
this.disconnectedCallback();
|
|
3835
|
+
this.valueBeforeDisconnect = null;
|
|
3791
3836
|
this.connectedCallback();
|
|
3792
3837
|
}
|
|
3793
3838
|
}
|
|
3794
3839
|
|
|
3795
3840
|
customElements.define("lexxy-editor", LexicalEditorElement);
|
|
3796
3841
|
|
|
3797
|
-
class
|
|
3842
|
+
class ToolbarDropdown extends HTMLElement {
|
|
3798
3843
|
connectedCallback() {
|
|
3799
|
-
this.
|
|
3800
|
-
if ("closedBy" in this.dialog.constructor.prototype) {
|
|
3801
|
-
this.dialog.closedBy = "any";
|
|
3802
|
-
}
|
|
3803
|
-
this.#registerHandlers();
|
|
3804
|
-
}
|
|
3844
|
+
this.container = this.closest("details");
|
|
3805
3845
|
|
|
3806
|
-
|
|
3807
|
-
this.#
|
|
3808
|
-
}
|
|
3809
|
-
|
|
3810
|
-
updateStateCallback() { }
|
|
3811
|
-
|
|
3812
|
-
show(triggerButton) {
|
|
3813
|
-
if (this.preventImmediateReopen) { return }
|
|
3814
|
-
|
|
3815
|
-
this.triggerButton = triggerButton;
|
|
3816
|
-
this.#positionDialog();
|
|
3817
|
-
this.dialog.show();
|
|
3846
|
+
this.container.addEventListener("toggle", this.#handleToggle.bind(this));
|
|
3847
|
+
this.container.addEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
3818
3848
|
|
|
3819
|
-
this.#
|
|
3849
|
+
this.#setTabIndexValues();
|
|
3820
3850
|
}
|
|
3821
3851
|
|
|
3822
|
-
|
|
3823
|
-
this
|
|
3852
|
+
disconnectedCallback() {
|
|
3853
|
+
this.#removeClickOutsideHandler();
|
|
3854
|
+
this.container.removeEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
3824
3855
|
}
|
|
3825
3856
|
|
|
3826
3857
|
get toolbar() {
|
|
@@ -3831,32 +3862,32 @@ class ToolbarDialog extends HTMLElement {
|
|
|
3831
3862
|
return this.toolbar.editor
|
|
3832
3863
|
}
|
|
3833
3864
|
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
#registerHandlers() {
|
|
3837
|
-
this.#setupKeydownHandler();
|
|
3838
|
-
this.dialog.addEventListener("cancel", this.#handleCancel.bind(this));
|
|
3839
|
-
this.dialog.addEventListener("close", this.#handleClose.bind(this));
|
|
3865
|
+
close() {
|
|
3866
|
+
this.container.removeAttribute("open");
|
|
3840
3867
|
}
|
|
3841
3868
|
|
|
3842
|
-
#
|
|
3843
|
-
this
|
|
3844
|
-
|
|
3845
|
-
|
|
3869
|
+
#handleToggle(event) {
|
|
3870
|
+
if (this.container.open) {
|
|
3871
|
+
this.#handleOpen(event.target);
|
|
3872
|
+
} else {
|
|
3873
|
+
this.#handleClose();
|
|
3874
|
+
}
|
|
3846
3875
|
}
|
|
3847
3876
|
|
|
3848
|
-
#
|
|
3849
|
-
this.
|
|
3850
|
-
|
|
3877
|
+
#handleOpen(trigger) {
|
|
3878
|
+
this.trigger = trigger;
|
|
3879
|
+
this.#interactiveElements[0].focus();
|
|
3880
|
+
this.#setupClickOutsideHandler();
|
|
3851
3881
|
}
|
|
3852
3882
|
|
|
3853
|
-
#
|
|
3854
|
-
|
|
3855
|
-
this
|
|
3883
|
+
#handleClose() {
|
|
3884
|
+
this.trigger = null;
|
|
3885
|
+
this.#removeClickOutsideHandler();
|
|
3886
|
+
this.editor.focus();
|
|
3856
3887
|
}
|
|
3857
3888
|
|
|
3858
3889
|
#setupClickOutsideHandler() {
|
|
3859
|
-
if (this
|
|
3890
|
+
if (this.clickOutsideHandler) return
|
|
3860
3891
|
|
|
3861
3892
|
this.clickOutsideHandler = this.#handleClickOutside.bind(this);
|
|
3862
3893
|
document.addEventListener("click", this.clickOutsideHandler, true);
|
|
@@ -3870,22 +3901,7 @@ class ToolbarDialog extends HTMLElement {
|
|
|
3870
3901
|
}
|
|
3871
3902
|
|
|
3872
3903
|
#handleClickOutside({ target }) {
|
|
3873
|
-
if (!this.
|
|
3874
|
-
|
|
3875
|
-
const isClickInsideDialog = this.dialog.contains(target);
|
|
3876
|
-
const isClickOnTrigger = this.triggerButton.contains(target);
|
|
3877
|
-
|
|
3878
|
-
if (!isClickInsideDialog && !isClickOnTrigger) {
|
|
3879
|
-
this.close();
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
|
-
|
|
3883
|
-
#setupKeydownHandler() {
|
|
3884
|
-
if (!this.#browserHandlesClose) { this.addEventListener("keydown", this.#handleKeyDown.bind(this)); }
|
|
3885
|
-
}
|
|
3886
|
-
|
|
3887
|
-
get #browserHandlesClose() {
|
|
3888
|
-
return this.dialog.closedBy === "any"
|
|
3904
|
+
if (this.container.open && !this.container.contains(target)) this.close();
|
|
3889
3905
|
}
|
|
3890
3906
|
|
|
3891
3907
|
#handleKeyDown(event) {
|
|
@@ -3894,9 +3910,20 @@ class ToolbarDialog extends HTMLElement {
|
|
|
3894
3910
|
this.close();
|
|
3895
3911
|
}
|
|
3896
3912
|
}
|
|
3913
|
+
|
|
3914
|
+
async #setTabIndexValues() {
|
|
3915
|
+
await nextFrame();
|
|
3916
|
+
this.#interactiveElements.forEach((element) => {
|
|
3917
|
+
element.setAttribute("tabindex", 0);
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
get #interactiveElements() {
|
|
3922
|
+
return Array.from(this.querySelectorAll("button, input"))
|
|
3923
|
+
}
|
|
3897
3924
|
}
|
|
3898
3925
|
|
|
3899
|
-
class
|
|
3926
|
+
class LinkDropdown extends ToolbarDropdown {
|
|
3900
3927
|
connectedCallback() {
|
|
3901
3928
|
super.connectedCallback();
|
|
3902
3929
|
this.input = this.querySelector("input");
|
|
@@ -3904,23 +3931,21 @@ class LinkDialog extends ToolbarDialog {
|
|
|
3904
3931
|
this.#registerHandlers();
|
|
3905
3932
|
}
|
|
3906
3933
|
|
|
3907
|
-
updateStateCallback() {
|
|
3908
|
-
this.input.value = this.#selectedLinkUrl;
|
|
3909
|
-
}
|
|
3910
|
-
|
|
3911
3934
|
#registerHandlers() {
|
|
3912
|
-
this.
|
|
3913
|
-
this.
|
|
3935
|
+
this.container.addEventListener("toggle", this.#handleToggle.bind(this));
|
|
3936
|
+
this.addEventListener("submit", this.#handleSubmit.bind(this));
|
|
3914
3937
|
this.querySelector("[value='unlink']").addEventListener("click", this.#handleUnlink.bind(this));
|
|
3915
3938
|
}
|
|
3916
3939
|
|
|
3917
|
-
#
|
|
3940
|
+
#handleToggle({ newState }) {
|
|
3941
|
+
this.input.value = this.#selectedLinkUrl;
|
|
3918
3942
|
this.input.required = newState === "open";
|
|
3919
3943
|
}
|
|
3920
3944
|
|
|
3921
3945
|
#handleSubmit(event) {
|
|
3922
3946
|
const command = event.submitter?.value;
|
|
3923
3947
|
this.editor.dispatchCommand(command, this.input.value);
|
|
3948
|
+
this.close();
|
|
3924
3949
|
}
|
|
3925
3950
|
|
|
3926
3951
|
#handleUnlink() {
|
|
@@ -3949,9 +3974,7 @@ class LinkDialog extends ToolbarDialog {
|
|
|
3949
3974
|
}
|
|
3950
3975
|
}
|
|
3951
3976
|
|
|
3952
|
-
|
|
3953
|
-
// supported by Safari yet: customElements.define("lexxy-link-dialog", LinkDialog, { extends: "dialog" })
|
|
3954
|
-
customElements.define("lexxy-link-dialog", LinkDialog);
|
|
3977
|
+
customElements.define("lexxy-link-dropdown", LinkDropdown);
|
|
3955
3978
|
|
|
3956
3979
|
const APPLY_HIGHLIGHT_SELECTOR = "button.lexxy-highlight-button";
|
|
3957
3980
|
const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
|
|
@@ -3961,7 +3984,7 @@ const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
|
|
|
3961
3984
|
// see https://github.com/facebook/lexical/issues/8013
|
|
3962
3985
|
const NO_STYLE = Symbol("no_style");
|
|
3963
3986
|
|
|
3964
|
-
class
|
|
3987
|
+
class HighlightDropdown extends ToolbarDropdown {
|
|
3965
3988
|
connectedCallback() {
|
|
3966
3989
|
super.connectedCallback();
|
|
3967
3990
|
|
|
@@ -3969,13 +3992,10 @@ class HighlightDialog extends ToolbarDialog {
|
|
|
3969
3992
|
this.#registerHandlers();
|
|
3970
3993
|
}
|
|
3971
3994
|
|
|
3972
|
-
updateStateCallback() {
|
|
3973
|
-
this.#updateColorButtonStates($getSelection());
|
|
3974
|
-
}
|
|
3975
|
-
|
|
3976
3995
|
#registerHandlers() {
|
|
3977
|
-
this.
|
|
3996
|
+
this.container.addEventListener("toggle", this.#handleToggle.bind(this));
|
|
3978
3997
|
this.#colorButtons.forEach(button => button.addEventListener("click", this.#handleColorButtonClick.bind(this)));
|
|
3998
|
+
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick.bind(this));
|
|
3979
3999
|
}
|
|
3980
4000
|
|
|
3981
4001
|
#setUpButtons() {
|
|
@@ -4002,6 +4022,14 @@ class HighlightDialog extends ToolbarDialog {
|
|
|
4002
4022
|
return button
|
|
4003
4023
|
}
|
|
4004
4024
|
|
|
4025
|
+
#handleToggle({ newState }) {
|
|
4026
|
+
if (newState === "open") {
|
|
4027
|
+
this.editor.getEditorState().read(() => {
|
|
4028
|
+
this.#updateColorButtonStates($getSelection());
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4005
4033
|
#handleColorButtonClick(event) {
|
|
4006
4034
|
event.preventDefault();
|
|
4007
4035
|
|
|
@@ -4047,9 +4075,7 @@ class HighlightDialog extends ToolbarDialog {
|
|
|
4047
4075
|
}
|
|
4048
4076
|
}
|
|
4049
4077
|
|
|
4050
|
-
|
|
4051
|
-
// supported by Safari yet: customElements.define("lexxy-hightlight-dialog", HighlightDialog, { extends: "dialog" })
|
|
4052
|
-
customElements.define("lexxy-highlight-dialog", HighlightDialog);
|
|
4078
|
+
customElements.define("lexxy-highlight-dropdown", HighlightDropdown);
|
|
4053
4079
|
|
|
4054
4080
|
class BaseSource {
|
|
4055
4081
|
// Template method to override
|
|
@@ -4194,10 +4220,13 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
4194
4220
|
this.keyListeners = [];
|
|
4195
4221
|
}
|
|
4196
4222
|
|
|
4223
|
+
static observedAttributes = [ "connected" ]
|
|
4224
|
+
|
|
4197
4225
|
connectedCallback() {
|
|
4198
4226
|
this.source = this.#createSource();
|
|
4199
4227
|
|
|
4200
4228
|
this.#addTriggerListener();
|
|
4229
|
+
this.toggleAttribute("connected", true);
|
|
4201
4230
|
}
|
|
4202
4231
|
|
|
4203
4232
|
disconnectedCallback() {
|
|
@@ -4205,6 +4234,13 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
4205
4234
|
this.popoverElement = null;
|
|
4206
4235
|
}
|
|
4207
4236
|
|
|
4237
|
+
|
|
4238
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
4239
|
+
if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) {
|
|
4240
|
+
requestAnimationFrame(() => this.#reconnect());
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4208
4244
|
get name() {
|
|
4209
4245
|
return this.getAttribute("name")
|
|
4210
4246
|
}
|
|
@@ -4574,6 +4610,11 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
4574
4610
|
this.#optionWasSelected();
|
|
4575
4611
|
}
|
|
4576
4612
|
}
|
|
4613
|
+
|
|
4614
|
+
#reconnect() {
|
|
4615
|
+
this.disconnectedCallback();
|
|
4616
|
+
this.connectedCallback();
|
|
4617
|
+
}
|
|
4577
4618
|
}
|
|
4578
4619
|
|
|
4579
4620
|
customElements.define("lexxy-prompt", LexicalPromptElement);
|
|
@@ -4721,24 +4762,17 @@ function highlightAll() {
|
|
|
4721
4762
|
|
|
4722
4763
|
function highlightElement(preElement) {
|
|
4723
4764
|
const language = preElement.getAttribute("data-language");
|
|
4724
|
-
|
|
4725
4765
|
let code = preElement.innerHTML.replace(/<br\s*\/?>/gi, "\n");
|
|
4726
4766
|
|
|
4727
|
-
const grammar = Prism.languages[language];
|
|
4767
|
+
const grammar = Prism.languages?.[language];
|
|
4728
4768
|
if (!grammar) return
|
|
4729
4769
|
|
|
4730
4770
|
// unescape HTML entities in the code block
|
|
4731
4771
|
code = new DOMParser().parseFromString(code, "text/html").body.textContent || "";
|
|
4732
4772
|
|
|
4733
4773
|
const highlightedHtml = Prism.highlight(code, grammar, language);
|
|
4734
|
-
|
|
4735
4774
|
const codeElement = createElement("code", { "data-language": language, innerHTML: highlightedHtml });
|
|
4736
4775
|
preElement.replaceWith(codeElement);
|
|
4737
4776
|
}
|
|
4738
4777
|
|
|
4739
|
-
// Manual highlighting mode to prevent invocation on every page. See https://prismjs.com/docs/prism
|
|
4740
|
-
// This must happen before importing any Prism components
|
|
4741
|
-
window.Prism = window.Prism || {};
|
|
4742
|
-
Prism.manual = true;
|
|
4743
|
-
|
|
4744
4778
|
export { highlightAll };
|