@37signals/lexxy 0.9.2-beta → 0.9.3-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/dist/lexxy.esm.js +389 -37
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -10,10 +10,11 @@ import 'prismjs/components/prism-json';
|
|
|
10
10
|
import 'prismjs/components/prism-diff';
|
|
11
11
|
import DOMPurify from 'dompurify';
|
|
12
12
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType } from '@lexical/selection';
|
|
13
|
-
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, $getNodeByKey, $isTextNode, $createRangeSelection, $setSelection, DecoratorNode, $createTextNode, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, $isElementNode,
|
|
13
|
+
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, $getNodeByKey, $isTextNode, $createRangeSelection, $setSelection, DecoratorNode, $createTextNode, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, $isElementNode, $createParagraphNode, TextNode, createCommand, createState, defineExtension, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $getEditor, $getNearestRootOrShadowRoot, $isNodeSelection, $getRoot, mergeRegister as mergeRegister$1, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $isRootOrShadowRoot, ElementNode, $splitNode, $isParagraphNode, $createLineBreakNode, $isRootNode, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, CLEAR_HISTORY_COMMAND, $addUpdateTag, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
14
14
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
15
15
|
import { ListNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, ListItemNode, $getListDepth, $isListItemNode, $isListNode, registerList } from '@lexical/list';
|
|
16
16
|
import { $createAutoLinkNode, $toggleLink, LinkNode, $createLinkNode, AutoLinkNode, $isLinkNode } from '@lexical/link';
|
|
17
|
+
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, mergeRegister, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $descendantsMatching } from '@lexical/utils';
|
|
17
18
|
import { registerPlainText } from '@lexical/plain-text';
|
|
18
19
|
import { RichTextExtension, $isQuoteNode, $isHeadingNode, $createHeadingNode, $createQuoteNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
19
20
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
@@ -23,7 +24,6 @@ import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
|
23
24
|
import { createElement, extractPlainTextFromHtml, createAttachmentFigure, isPreviewableImage, dispatch, parseHtml, addBlockSpacing, generateDomId } from './lexxy_helpers.esm.js';
|
|
24
25
|
export { highlightCode as highlightAll, highlightCode } from './lexxy_helpers.esm.js';
|
|
25
26
|
import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, setScrollableTablesActive, registerTablePlugin, registerTableSelectionObserver, TableCellHeaderStates, $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $findTableNode, $getTableRowIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode, $findCellNode, $getElementForTableNode } from '@lexical/table';
|
|
26
|
-
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, mergeRegister, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $descendantsMatching } from '@lexical/utils';
|
|
27
27
|
import { marked } from 'marked';
|
|
28
28
|
import { $insertDataTransferForRichText } from '@lexical/clipboard';
|
|
29
29
|
|
|
@@ -1409,6 +1409,24 @@ function isSelectionHighlighted(selection) {
|
|
|
1409
1409
|
}
|
|
1410
1410
|
}
|
|
1411
1411
|
|
|
1412
|
+
function getHighlightStyles(selection) {
|
|
1413
|
+
if (!$isRangeSelection(selection)) return null
|
|
1414
|
+
|
|
1415
|
+
let styles = getStyleObjectFromCSS(selection.style);
|
|
1416
|
+
if (!styles.color && !styles["background-color"]) {
|
|
1417
|
+
const anchorNode = selection.anchor.getNode();
|
|
1418
|
+
if ($isTextNode(anchorNode)) {
|
|
1419
|
+
styles = getStyleObjectFromCSS(anchorNode.getStyle());
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const color = styles.color || null;
|
|
1424
|
+
const backgroundColor = styles["background-color"] || null;
|
|
1425
|
+
if (!color && !backgroundColor) return null
|
|
1426
|
+
|
|
1427
|
+
return { color, backgroundColor }
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1412
1430
|
function hasHighlightStyles(cssOrStyles) {
|
|
1413
1431
|
const styles = typeof cssOrStyles === "string" ? getStyleObjectFromCSS(cssOrStyles) : cssOrStyles;
|
|
1414
1432
|
return !!(styles.color || styles["background-color"])
|
|
@@ -1971,6 +1989,7 @@ const COMMANDS = [
|
|
|
1971
1989
|
"insertOrderedList",
|
|
1972
1990
|
"insertQuoteBlock",
|
|
1973
1991
|
"insertCodeBlock",
|
|
1992
|
+
"setCodeLanguage",
|
|
1974
1993
|
"insertHorizontalDivider",
|
|
1975
1994
|
"uploadImage",
|
|
1976
1995
|
"uploadFile",
|
|
@@ -2046,7 +2065,14 @@ class CommandDispatcher {
|
|
|
2046
2065
|
}
|
|
2047
2066
|
|
|
2048
2067
|
dispatchUnlink() {
|
|
2049
|
-
this
|
|
2068
|
+
this.editor.update(() => {
|
|
2069
|
+
// Let adapters signal whether unlink should target a frozen link key.
|
|
2070
|
+
if (this.editorElement.adapter.unlinkFrozenNode?.()) {
|
|
2071
|
+
return
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
$toggleLink(null);
|
|
2075
|
+
});
|
|
2050
2076
|
}
|
|
2051
2077
|
|
|
2052
2078
|
dispatchInsertUnorderedList() {
|
|
@@ -2137,6 +2163,17 @@ class CommandDispatcher {
|
|
|
2137
2163
|
}
|
|
2138
2164
|
}
|
|
2139
2165
|
|
|
2166
|
+
dispatchSetCodeLanguage(language) {
|
|
2167
|
+
this.editor.update(() => {
|
|
2168
|
+
if (!this.selection.isInsideCodeBlock) return
|
|
2169
|
+
|
|
2170
|
+
const codeNode = this.selection.nearestNodeOfType(CodeNode);
|
|
2171
|
+
if (!codeNode) return
|
|
2172
|
+
|
|
2173
|
+
codeNode.setLanguage(language);
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2140
2177
|
dispatchInsertHorizontalDivider() {
|
|
2141
2178
|
this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
|
|
2142
2179
|
this.editor.focus();
|
|
@@ -2335,16 +2372,6 @@ class CommandDispatcher {
|
|
|
2335
2372
|
return $isRangeSelection(selection) && selection.isCollapsed()
|
|
2336
2373
|
}
|
|
2337
2374
|
|
|
2338
|
-
// Not using TOGGLE_LINK_COMMAND because it's not handled unless you use React/LinkPlugin
|
|
2339
|
-
#toggleLink(url) {
|
|
2340
|
-
this.editor.update(() => {
|
|
2341
|
-
if (url === null) {
|
|
2342
|
-
$toggleLink(null);
|
|
2343
|
-
} else {
|
|
2344
|
-
$toggleLink(url);
|
|
2345
|
-
}
|
|
2346
|
-
});
|
|
2347
|
-
}
|
|
2348
2375
|
}
|
|
2349
2376
|
|
|
2350
2377
|
function capitalize(str) {
|
|
@@ -2571,8 +2598,8 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2571
2598
|
return null
|
|
2572
2599
|
}
|
|
2573
2600
|
|
|
2574
|
-
createAttachmentFigure() {
|
|
2575
|
-
const figure = createAttachmentFigure(this.contentType,
|
|
2601
|
+
createAttachmentFigure(previewable = this.isPreviewableAttachment) {
|
|
2602
|
+
const figure = createAttachmentFigure(this.contentType, previewable, this.fileName);
|
|
2576
2603
|
figure.draggable = true;
|
|
2577
2604
|
figure.dataset.lexicalNodeKey = this.__key;
|
|
2578
2605
|
|
|
@@ -3657,9 +3684,12 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3657
3684
|
// node is reloaded from saved state such as from history.
|
|
3658
3685
|
this.#startUploadIfNeeded();
|
|
3659
3686
|
|
|
3660
|
-
|
|
3687
|
+
// Bridge-managed uploads (uploadUrl is null) don't have file data to show
|
|
3688
|
+
// an image preview, so always show the file icon during upload.
|
|
3689
|
+
const canPreviewFile = this.isPreviewableAttachment && this.uploadUrl != null;
|
|
3690
|
+
const figure = this.createAttachmentFigure(canPreviewFile);
|
|
3661
3691
|
|
|
3662
|
-
if (
|
|
3692
|
+
if (canPreviewFile) {
|
|
3663
3693
|
const img = figure.appendChild(this.#createDOMForImage());
|
|
3664
3694
|
|
|
3665
3695
|
// load file locally to set dimensions and prevent vertical shifting
|
|
@@ -3759,6 +3789,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3759
3789
|
|
|
3760
3790
|
async #startUploadIfNeeded() {
|
|
3761
3791
|
if (this.#uploadStarted) return
|
|
3792
|
+
if (!this.uploadUrl) return // Bridge-managed upload — skip DirectUpload
|
|
3762
3793
|
|
|
3763
3794
|
this.#setUploadStarted();
|
|
3764
3795
|
|
|
@@ -3775,7 +3806,9 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3775
3806
|
this.#handleUploadError(error);
|
|
3776
3807
|
} else {
|
|
3777
3808
|
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null });
|
|
3778
|
-
this
|
|
3809
|
+
this.editor.update(() => {
|
|
3810
|
+
this.showUploadedAttachment(blob);
|
|
3811
|
+
}, { tag: this.#backgroundUpdateTags });
|
|
3779
3812
|
}
|
|
3780
3813
|
});
|
|
3781
3814
|
}
|
|
@@ -3819,17 +3852,15 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3819
3852
|
}, { tag: this.#backgroundUpdateTags });
|
|
3820
3853
|
}
|
|
3821
3854
|
|
|
3822
|
-
|
|
3823
|
-
const
|
|
3855
|
+
showUploadedAttachment(blob) {
|
|
3856
|
+
const replacementNode = this.#toActionTextAttachmentNodeWith(blob);
|
|
3857
|
+
this.replace(replacementNode);
|
|
3824
3858
|
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3859
|
+
if ($isRootOrShadowRoot(replacementNode.getParent())) {
|
|
3860
|
+
replacementNode.selectNext();
|
|
3861
|
+
}
|
|
3828
3862
|
|
|
3829
|
-
|
|
3830
|
-
replacementNode.selectNext();
|
|
3831
|
-
}
|
|
3832
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
3863
|
+
return replacementNode.getKey()
|
|
3833
3864
|
}
|
|
3834
3865
|
|
|
3835
3866
|
// Upload lifecycle methods (progress, completion, errors) run asynchronously and may
|
|
@@ -4431,6 +4462,53 @@ class Contents {
|
|
|
4431
4462
|
});
|
|
4432
4463
|
}
|
|
4433
4464
|
|
|
4465
|
+
insertPendingAttachment(file) {
|
|
4466
|
+
if (!this.editorElement.supportsAttachments) return null
|
|
4467
|
+
|
|
4468
|
+
let nodeKey = null;
|
|
4469
|
+
this.editor.update(() => {
|
|
4470
|
+
const uploadNode = new ActionTextAttachmentUploadNode({
|
|
4471
|
+
file,
|
|
4472
|
+
uploadUrl: null,
|
|
4473
|
+
blobUrlTemplate: this.editorElement.blobUrlTemplate,
|
|
4474
|
+
editor: this.editor
|
|
4475
|
+
});
|
|
4476
|
+
this.insertAtCursor(uploadNode);
|
|
4477
|
+
nodeKey = uploadNode.getKey();
|
|
4478
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4479
|
+
|
|
4480
|
+
if (!nodeKey) return null
|
|
4481
|
+
|
|
4482
|
+
const editor = this.editor;
|
|
4483
|
+
return {
|
|
4484
|
+
setAttributes(blob) {
|
|
4485
|
+
editor.update(() => {
|
|
4486
|
+
const node = $getNodeByKey(nodeKey);
|
|
4487
|
+
if (!(node instanceof ActionTextAttachmentUploadNode)) return
|
|
4488
|
+
|
|
4489
|
+
const replacementNodeKey = node.showUploadedAttachment(blob);
|
|
4490
|
+
if (replacementNodeKey) {
|
|
4491
|
+
nodeKey = replacementNodeKey;
|
|
4492
|
+
}
|
|
4493
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4494
|
+
},
|
|
4495
|
+
setUploadProgress(progress) {
|
|
4496
|
+
editor.update(() => {
|
|
4497
|
+
const node = $getNodeByKey(nodeKey);
|
|
4498
|
+
if (!(node instanceof ActionTextAttachmentUploadNode)) return
|
|
4499
|
+
|
|
4500
|
+
node.getWritable().progress = progress;
|
|
4501
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4502
|
+
},
|
|
4503
|
+
remove() {
|
|
4504
|
+
editor.update(() => {
|
|
4505
|
+
const node = $getNodeByKey(nodeKey);
|
|
4506
|
+
if (node) node.remove();
|
|
4507
|
+
});
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4434
4512
|
replaceNodeWithHTML(nodeKey, html, options = {}) {
|
|
4435
4513
|
this.editor.update(() => {
|
|
4436
4514
|
const node = $getNodeByKey(nodeKey);
|
|
@@ -4915,6 +4993,18 @@ class Extensions {
|
|
|
4915
4993
|
}
|
|
4916
4994
|
}
|
|
4917
4995
|
|
|
4996
|
+
class BrowserAdapter {
|
|
4997
|
+
frozenLinkKey = null
|
|
4998
|
+
|
|
4999
|
+
dispatchAttributesChange(attributes, linkHref, highlight, headingTag) {}
|
|
5000
|
+
dispatchEditorInitialized(detail) {}
|
|
5001
|
+
freeze() {}
|
|
5002
|
+
thaw() {}
|
|
5003
|
+
unlinkFrozenNode() {
|
|
5004
|
+
return false
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
|
|
4918
5008
|
// Custom TextNode exportDOM that avoids redundant bold/italic wrapping.
|
|
4919
5009
|
//
|
|
4920
5010
|
// Lexical's built-in TextNode.exportDOM() calls createDOM() which produces semantic tags
|
|
@@ -5966,6 +6056,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5966
6056
|
|
|
5967
6057
|
#initialValue = ""
|
|
5968
6058
|
#validationTextArea = document.createElement("textarea")
|
|
6059
|
+
#editorInitializedRafId = null
|
|
5969
6060
|
#disposables = []
|
|
5970
6061
|
|
|
5971
6062
|
constructor() {
|
|
@@ -5975,7 +6066,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5975
6066
|
}
|
|
5976
6067
|
|
|
5977
6068
|
connectedCallback() {
|
|
5978
|
-
this.id
|
|
6069
|
+
this.id ||= generateDomId("lexxy-editor");
|
|
5979
6070
|
this.config = new EditorConfiguration(this);
|
|
5980
6071
|
this.extensions = new Extensions(this);
|
|
5981
6072
|
|
|
@@ -5989,13 +6080,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5989
6080
|
this.#disposables.push(this.selection);
|
|
5990
6081
|
|
|
5991
6082
|
this.clipboard = new Clipboard(this);
|
|
6083
|
+
this.adapter = new BrowserAdapter();
|
|
5992
6084
|
|
|
5993
6085
|
const commandDispatcher = CommandDispatcher.configureFor(this);
|
|
5994
6086
|
this.#disposables.push(commandDispatcher);
|
|
5995
6087
|
|
|
5996
6088
|
this.#initialize();
|
|
5997
6089
|
|
|
5998
|
-
|
|
6090
|
+
this.#scheduleEditorInitializedDispatch();
|
|
5999
6091
|
this.toggleAttribute("connected", true);
|
|
6000
6092
|
|
|
6001
6093
|
this.#handleAutofocus();
|
|
@@ -6004,6 +6096,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6004
6096
|
}
|
|
6005
6097
|
|
|
6006
6098
|
disconnectedCallback() {
|
|
6099
|
+
this.#cancelEditorInitializedDispatch();
|
|
6007
6100
|
this.valueBeforeDisconnect = this.value;
|
|
6008
6101
|
this.#reset(); // Prevent hangs with Safari when morphing
|
|
6009
6102
|
}
|
|
@@ -6100,6 +6193,32 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6100
6193
|
return this.config.get("richText")
|
|
6101
6194
|
}
|
|
6102
6195
|
|
|
6196
|
+
registerAdapter(adapter) {
|
|
6197
|
+
this.adapter = adapter;
|
|
6198
|
+
|
|
6199
|
+
if (!this.editor) return
|
|
6200
|
+
|
|
6201
|
+
this.#cancelEditorInitializedDispatch();
|
|
6202
|
+
this.#dispatchEditorInitialized();
|
|
6203
|
+
this.#dispatchAttributesChange();
|
|
6204
|
+
}
|
|
6205
|
+
|
|
6206
|
+
freezeSelection() {
|
|
6207
|
+
this.adapter.freeze();
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
thawSelection() {
|
|
6211
|
+
this.adapter.thaw();
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6214
|
+
dispatchAttributesChange() {
|
|
6215
|
+
this.#dispatchAttributesChange();
|
|
6216
|
+
}
|
|
6217
|
+
|
|
6218
|
+
dispatchEditorInitialized() {
|
|
6219
|
+
this.#dispatchEditorInitialized();
|
|
6220
|
+
}
|
|
6221
|
+
|
|
6103
6222
|
// TODO: Deprecate `single-line` attribute
|
|
6104
6223
|
get isSingleLineMode() {
|
|
6105
6224
|
return this.hasAttribute("single-line")
|
|
@@ -6224,6 +6343,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6224
6343
|
const editorContentElement = createElement("div", {
|
|
6225
6344
|
classList: "lexxy-editor__content",
|
|
6226
6345
|
contenteditable: true,
|
|
6346
|
+
autocapitalize: "none",
|
|
6227
6347
|
role: "textbox",
|
|
6228
6348
|
"aria-multiline": true,
|
|
6229
6349
|
"aria-label": this.#labelText,
|
|
@@ -6285,6 +6405,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6285
6405
|
this.#internalFormValue = this.value;
|
|
6286
6406
|
this.#toggleEmptyStatus();
|
|
6287
6407
|
this.#setValidity();
|
|
6408
|
+
this.#dispatchAttributesChange();
|
|
6288
6409
|
}));
|
|
6289
6410
|
}
|
|
6290
6411
|
|
|
@@ -6380,6 +6501,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6380
6501
|
|
|
6381
6502
|
#handleFocusIn(event) {
|
|
6382
6503
|
if (this.#elementInEditorOrToolbar(event.target) && !this.currentlyFocused) {
|
|
6504
|
+
this.#dispatchAttributesChange();
|
|
6383
6505
|
dispatch(this, "lexxy:focus");
|
|
6384
6506
|
this.currentlyFocused = true;
|
|
6385
6507
|
}
|
|
@@ -6460,7 +6582,112 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6460
6582
|
}
|
|
6461
6583
|
}
|
|
6462
6584
|
|
|
6585
|
+
#dispatchAttributesChange() {
|
|
6586
|
+
let attributes = null;
|
|
6587
|
+
let linkHref = null;
|
|
6588
|
+
let highlight = null;
|
|
6589
|
+
let headingTag = null;
|
|
6590
|
+
|
|
6591
|
+
this.editor.getEditorState().read(() => {
|
|
6592
|
+
const selection = $getSelection();
|
|
6593
|
+
if (!$isRangeSelection(selection)) return
|
|
6594
|
+
|
|
6595
|
+
const format = this.selection.getFormat();
|
|
6596
|
+
if (Object.keys(format).length === 0) return
|
|
6597
|
+
|
|
6598
|
+
const anchorNode = selection.anchor.getNode();
|
|
6599
|
+
const linkNode = $getNearestNodeOfType(anchorNode, LinkNode);
|
|
6600
|
+
|
|
6601
|
+
attributes = {
|
|
6602
|
+
bold: { active: format.isBold, enabled: true },
|
|
6603
|
+
italic: { active: format.isItalic, enabled: true },
|
|
6604
|
+
strikethrough: { active: format.isStrikethrough, enabled: true },
|
|
6605
|
+
code: { active: format.isInCode, enabled: true },
|
|
6606
|
+
highlight: { active: format.isHighlight, enabled: true },
|
|
6607
|
+
link: { active: format.isInLink, enabled: true },
|
|
6608
|
+
quote: { active: format.isInQuote, enabled: true },
|
|
6609
|
+
heading: { active: format.isInHeading, enabled: true },
|
|
6610
|
+
"unordered-list": { active: format.isInList && format.listType === "bullet", enabled: true },
|
|
6611
|
+
"ordered-list": { active: format.isInList && format.listType === "number", enabled: true },
|
|
6612
|
+
undo: { active: false, enabled: this.historyState?.undoStack.length > 0 },
|
|
6613
|
+
redo: { active: false, enabled: this.historyState?.redoStack.length > 0 }
|
|
6614
|
+
};
|
|
6615
|
+
|
|
6616
|
+
linkHref = linkNode ? linkNode.getURL() : null;
|
|
6617
|
+
highlight = format.isHighlight ? getHighlightStyles(selection) : null;
|
|
6618
|
+
headingTag = format.headingTag ?? null;
|
|
6619
|
+
});
|
|
6620
|
+
|
|
6621
|
+
if (attributes) {
|
|
6622
|
+
this.adapter.dispatchAttributesChange(attributes, linkHref, highlight, headingTag);
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
#dispatchEditorInitialized() {
|
|
6627
|
+
if (!this.adapter) return
|
|
6628
|
+
|
|
6629
|
+
this.adapter.dispatchEditorInitialized({
|
|
6630
|
+
highlightColors: this.#resolvedHighlightColors,
|
|
6631
|
+
headingFormats: this.#supportedHeadingFormats
|
|
6632
|
+
});
|
|
6633
|
+
}
|
|
6634
|
+
|
|
6635
|
+
#scheduleEditorInitializedDispatch() {
|
|
6636
|
+
this.#cancelEditorInitializedDispatch();
|
|
6637
|
+
this.#editorInitializedRafId = requestAnimationFrame(() => {
|
|
6638
|
+
this.#editorInitializedRafId = null;
|
|
6639
|
+
if (!this.isConnected || !this.adapter) return
|
|
6640
|
+
|
|
6641
|
+
dispatch(this, "lexxy:initialize");
|
|
6642
|
+
this.#dispatchEditorInitialized();
|
|
6643
|
+
});
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
#cancelEditorInitializedDispatch() {
|
|
6647
|
+
if (this.#editorInitializedRafId == null) return
|
|
6648
|
+
|
|
6649
|
+
cancelAnimationFrame(this.#editorInitializedRafId);
|
|
6650
|
+
this.#editorInitializedRafId = null;
|
|
6651
|
+
}
|
|
6652
|
+
|
|
6653
|
+
get #resolvedHighlightColors() {
|
|
6654
|
+
const buttons = this.config.get("highlight.buttons");
|
|
6655
|
+
if (!buttons) return null
|
|
6656
|
+
|
|
6657
|
+
const colors = this.#resolveColors("color", buttons.color || []);
|
|
6658
|
+
const backgroundColors = this.#resolveColors("background-color", buttons["background-color"] || []);
|
|
6659
|
+
return { colors, backgroundColors }
|
|
6660
|
+
}
|
|
6661
|
+
|
|
6662
|
+
get #supportedHeadingFormats() {
|
|
6663
|
+
if (!this.supportsRichText) return []
|
|
6664
|
+
|
|
6665
|
+
return [
|
|
6666
|
+
{ label: "Normal", command: "setFormatParagraph", tag: null },
|
|
6667
|
+
{ label: "Large heading", command: "setFormatHeadingLarge", tag: "h2" },
|
|
6668
|
+
{ label: "Medium heading", command: "setFormatHeadingMedium", tag: "h3" },
|
|
6669
|
+
{ label: "Small heading", command: "setFormatHeadingSmall", tag: "h4" },
|
|
6670
|
+
]
|
|
6671
|
+
}
|
|
6672
|
+
|
|
6673
|
+
#resolveColors(property, cssValues) {
|
|
6674
|
+
const resolver = document.createElement("span");
|
|
6675
|
+
resolver.style.display = "none";
|
|
6676
|
+
this.appendChild(resolver);
|
|
6677
|
+
|
|
6678
|
+
const resolved = cssValues.map(cssValue => {
|
|
6679
|
+
resolver.style.setProperty(property, cssValue);
|
|
6680
|
+
const value = window.getComputedStyle(resolver).getPropertyValue(property);
|
|
6681
|
+
resolver.style.removeProperty(property);
|
|
6682
|
+
return { name: cssValue, value }
|
|
6683
|
+
});
|
|
6684
|
+
|
|
6685
|
+
resolver.remove();
|
|
6686
|
+
return resolved
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6463
6689
|
#reset() {
|
|
6690
|
+
this.#cancelEditorInitializedDispatch();
|
|
6464
6691
|
this.#dispose();
|
|
6465
6692
|
this.editorContentElement?.remove();
|
|
6466
6693
|
this.editorContentElement = null;
|
|
@@ -6472,6 +6699,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6472
6699
|
|
|
6473
6700
|
#dispose() {
|
|
6474
6701
|
this.#unregisterHandlers();
|
|
6702
|
+
this.adapter = null;
|
|
6475
6703
|
document.removeEventListener("turbo:before-cache", this.#handleTurboBeforeCache);
|
|
6476
6704
|
|
|
6477
6705
|
while (this.#disposables.length) {
|
|
@@ -7377,10 +7605,13 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
7377
7605
|
}
|
|
7378
7606
|
|
|
7379
7607
|
class CodeLanguagePicker extends HTMLElement {
|
|
7608
|
+
#abortController = null
|
|
7609
|
+
|
|
7380
7610
|
connectedCallback() {
|
|
7381
7611
|
this.editorElement = this.closest("lexxy-editor");
|
|
7382
7612
|
this.editor = this.editorElement.editor;
|
|
7383
7613
|
this.classList.add("lexxy-floating-controls");
|
|
7614
|
+
this.#abortController = new AbortController();
|
|
7384
7615
|
|
|
7385
7616
|
this.#attachLanguagePicker();
|
|
7386
7617
|
this.#hide();
|
|
@@ -7392,13 +7623,27 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
7392
7623
|
}
|
|
7393
7624
|
|
|
7394
7625
|
dispose() {
|
|
7626
|
+
this.#abortController?.abort();
|
|
7627
|
+
this.#abortController = null;
|
|
7395
7628
|
this.unregisterUpdateListener?.();
|
|
7396
7629
|
this.unregisterUpdateListener = null;
|
|
7397
7630
|
}
|
|
7398
7631
|
|
|
7399
7632
|
#attachLanguagePicker() {
|
|
7400
7633
|
this.languagePickerElement = this.#findLanguagePicker() ?? this.#createLanguagePicker();
|
|
7401
|
-
|
|
7634
|
+
|
|
7635
|
+
const signal = this.#abortController.signal;
|
|
7636
|
+
|
|
7637
|
+
this.languagePickerElement.addEventListener("change", () => {
|
|
7638
|
+
this.#updateCodeBlockLanguage(this.languagePickerElement.value);
|
|
7639
|
+
}, { signal });
|
|
7640
|
+
|
|
7641
|
+
this.languagePickerElement.addEventListener("mousedown", (event) => {
|
|
7642
|
+
this.#dispatchOpenEvent(event);
|
|
7643
|
+
}, { signal });
|
|
7644
|
+
|
|
7645
|
+
this.languagePickerElement.setAttribute("nonce", getNonce());
|
|
7646
|
+
this.appendChild(this.languagePickerElement);
|
|
7402
7647
|
}
|
|
7403
7648
|
|
|
7404
7649
|
#findLanguagePicker() {
|
|
@@ -7415,12 +7660,6 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
7415
7660
|
selectElement.appendChild(option);
|
|
7416
7661
|
}
|
|
7417
7662
|
|
|
7418
|
-
selectElement.addEventListener("change", () => {
|
|
7419
|
-
this.#updateCodeBlockLanguage(this.languagePickerElement.value);
|
|
7420
|
-
});
|
|
7421
|
-
|
|
7422
|
-
selectElement.setAttribute("nonce", getNonce());
|
|
7423
|
-
|
|
7424
7663
|
return selectElement
|
|
7425
7664
|
}
|
|
7426
7665
|
|
|
@@ -7443,6 +7682,21 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
7443
7682
|
return Object.fromEntries([ plainEntry, ...sortedEntries ])
|
|
7444
7683
|
}
|
|
7445
7684
|
|
|
7685
|
+
#dispatchOpenEvent(event) {
|
|
7686
|
+
const handled = !dispatch(this.editorElement, "lexxy:code-language-picker-open", {
|
|
7687
|
+
languages: this.#bridgeLanguages,
|
|
7688
|
+
currentLanguage: this.languagePickerElement.value
|
|
7689
|
+
}, true);
|
|
7690
|
+
|
|
7691
|
+
if (handled) {
|
|
7692
|
+
event.preventDefault();
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7696
|
+
get #bridgeLanguages() {
|
|
7697
|
+
return Object.entries(this.#languages).map(([ key, name ]) => ({ key, name }))
|
|
7698
|
+
}
|
|
7699
|
+
|
|
7446
7700
|
#updateCodeBlockLanguage(language) {
|
|
7447
7701
|
this.editor.update(() => {
|
|
7448
7702
|
const codeNode = this.#getCurrentCodeNode();
|
|
@@ -8304,9 +8558,107 @@ function defineElements() {
|
|
|
8304
8558
|
});
|
|
8305
8559
|
}
|
|
8306
8560
|
|
|
8561
|
+
class NativeAdapter {
|
|
8562
|
+
frozenLinkKey = null
|
|
8563
|
+
|
|
8564
|
+
constructor(editorElement) {
|
|
8565
|
+
this.editorElement = editorElement;
|
|
8566
|
+
this.editorContentElement = editorElement.editorContentElement;
|
|
8567
|
+
}
|
|
8568
|
+
|
|
8569
|
+
dispatchAttributesChange(attributes, linkHref, highlight, headingTag) {
|
|
8570
|
+
dispatch(this.editorElement, "lexxy:attributes-change", {
|
|
8571
|
+
attributes,
|
|
8572
|
+
link: linkHref ? { href: linkHref } : null,
|
|
8573
|
+
highlight,
|
|
8574
|
+
headingTag
|
|
8575
|
+
});
|
|
8576
|
+
}
|
|
8577
|
+
|
|
8578
|
+
dispatchEditorInitialized(detail) {
|
|
8579
|
+
dispatch(this.editorElement, "lexxy:editor-initialized", detail);
|
|
8580
|
+
}
|
|
8581
|
+
|
|
8582
|
+
freeze() {
|
|
8583
|
+
let frozenLinkKey = null;
|
|
8584
|
+
this.editorElement.editor?.getEditorState().read(() => {
|
|
8585
|
+
const selection = $getSelection();
|
|
8586
|
+
if (!$isRangeSelection(selection)) return
|
|
8587
|
+
|
|
8588
|
+
const linkNode = $getNearestNodeOfType(selection.anchor.getNode(), LinkNode);
|
|
8589
|
+
if (linkNode) {
|
|
8590
|
+
frozenLinkKey = linkNode.getKey();
|
|
8591
|
+
}
|
|
8592
|
+
});
|
|
8593
|
+
|
|
8594
|
+
this.frozenLinkKey = frozenLinkKey;
|
|
8595
|
+
this.editorContentElement.contentEditable = "false";
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8598
|
+
thaw() {
|
|
8599
|
+
this.editorContentElement.contentEditable = "true";
|
|
8600
|
+
}
|
|
8601
|
+
|
|
8602
|
+
unlinkFrozenNode() {
|
|
8603
|
+
const key = this.frozenLinkKey;
|
|
8604
|
+
if (!key) return false
|
|
8605
|
+
|
|
8606
|
+
const linkNode = $getNodeByKey(key);
|
|
8607
|
+
if (!$isLinkNode(linkNode)) {
|
|
8608
|
+
this.frozenLinkKey = null;
|
|
8609
|
+
return false
|
|
8610
|
+
}
|
|
8611
|
+
|
|
8612
|
+
const children = linkNode.getChildren();
|
|
8613
|
+
for (const child of children) {
|
|
8614
|
+
linkNode.insertBefore(child);
|
|
8615
|
+
}
|
|
8616
|
+
linkNode.remove();
|
|
8617
|
+
|
|
8618
|
+
// Select the former link text so a follow-up createLink can re-wrap it.
|
|
8619
|
+
const firstText = this.#findFirstTextDescendant(children);
|
|
8620
|
+
const lastText = this.#findLastTextDescendant(children);
|
|
8621
|
+
if (firstText && lastText) {
|
|
8622
|
+
const selection = $getSelection();
|
|
8623
|
+
if ($isRangeSelection(selection)) {
|
|
8624
|
+
selection.anchor.set(firstText.getKey(), 0, "text");
|
|
8625
|
+
selection.focus.set(lastText.getKey(), lastText.getTextContent().length, "text");
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8629
|
+
this.frozenLinkKey = null;
|
|
8630
|
+
return true
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8633
|
+
#findFirstTextDescendant(nodes) {
|
|
8634
|
+
for (const node of nodes) {
|
|
8635
|
+
if ($isTextNode(node)) return node
|
|
8636
|
+
if ($isElementNode(node)) {
|
|
8637
|
+
const nestedTextNode = this.#findFirstTextDescendant(node.getChildren());
|
|
8638
|
+
if (nestedTextNode) return nestedTextNode
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8642
|
+
return null
|
|
8643
|
+
}
|
|
8644
|
+
|
|
8645
|
+
#findLastTextDescendant(nodes) {
|
|
8646
|
+
for (let index = nodes.length - 1; index >= 0; index--) {
|
|
8647
|
+
const node = nodes[index];
|
|
8648
|
+
if ($isTextNode(node)) return node
|
|
8649
|
+
if ($isElementNode(node)) {
|
|
8650
|
+
const nestedTextNode = this.#findLastTextDescendant(node.getChildren());
|
|
8651
|
+
if (nestedTextNode) return nestedTextNode
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8654
|
+
|
|
8655
|
+
return null
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
|
|
8307
8659
|
const configure = Lexxy.configure;
|
|
8308
8660
|
|
|
8309
8661
|
// Pushing elements definition to after the current call stack to allow global configuration to take place first
|
|
8310
8662
|
setTimeout(defineElements, 0);
|
|
8311
8663
|
|
|
8312
|
-
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, configure };
|
|
8664
|
+
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, configure };
|