kempo-ui 0.3.7 → 0.3.8
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/components/Accordion.js +5 -5
- package/dist/components/Aside.js +16 -16
- package/dist/components/Card.js +3 -3
- package/dist/components/ColorPicker.js +4 -4
- package/dist/components/ContentSlider.js +3 -3
- package/dist/components/Context.js +1 -1
- package/dist/components/Dialog.js +7 -7
- package/dist/components/Dropdown.js +7 -4
- package/dist/components/FilterItem.js +2 -2
- package/dist/components/FilterList.js +2 -2
- package/dist/components/FocusCapture.js +2 -2
- package/dist/components/HtmlEditor.js +62 -39
- package/dist/components/HybridComponent.js +1 -1
- package/dist/components/Icon.js +2 -2
- package/dist/components/Import.js +1 -1
- package/dist/components/LightComponent.js +1 -1
- package/dist/components/Main.js +3 -3
- package/dist/components/Nav.js +2 -2
- package/dist/components/NavSpacer.js +6 -0
- package/dist/components/PhotoViewer.js +3 -3
- package/dist/components/Resize.js +3 -3
- package/dist/components/ShadowComponent.js +1 -1
- package/dist/components/ShowMore.js +3 -3
- package/dist/components/Sortable.js +3 -3
- package/dist/components/Spinner.js +2 -2
- package/dist/components/Split.js +3 -3
- package/dist/components/Table.js +27 -27
- package/dist/components/Tabs.js +7 -7
- package/dist/components/Tags.js +4 -4
- package/dist/components/ThemeSelect.js +4 -4
- package/dist/components/ThemeSwitcher.js +5 -5
- package/dist/components/Timestamp.js +1 -1
- package/dist/components/Toast.js +3 -3
- package/dist/components/Toggle.js +3 -3
- package/dist/components/Tree.js +10 -10
- package/dist/components/htmlEditorControls/AlignCenter.js +4 -4
- package/dist/components/htmlEditorControls/AlignJustify.js +4 -4
- package/dist/components/htmlEditorControls/AlignLeft.js +4 -4
- package/dist/components/htmlEditorControls/AlignRight.js +4 -4
- package/dist/components/htmlEditorControls/Bold.js +4 -4
- package/dist/components/htmlEditorControls/BulletList.js +4 -4
- package/dist/components/htmlEditorControls/CharacterCount.js +3 -3
- package/dist/components/htmlEditorControls/ClearFormatting.js +4 -4
- package/dist/components/htmlEditorControls/CodeBlock.js +3 -3
- package/dist/components/htmlEditorControls/ControlGroup.js +2 -2
- package/dist/components/htmlEditorControls/ControlSpacer.js +2 -2
- package/dist/components/htmlEditorControls/CreateLink.js +3 -3
- package/dist/components/htmlEditorControls/DropdownControl.js +3 -3
- package/dist/components/htmlEditorControls/FormatBlock.js +4 -4
- package/dist/components/htmlEditorControls/HtmlEditorControl.js +2 -2
- package/dist/components/htmlEditorControls/InlineCode.js +3 -3
- package/dist/components/htmlEditorControls/InsertTable.js +3 -3
- package/dist/components/htmlEditorControls/Italic.js +4 -4
- package/dist/components/htmlEditorControls/Mode.js +3 -3
- package/dist/components/htmlEditorControls/NumberList.js +4 -4
- package/dist/components/htmlEditorControls/Strikethrough.js +4 -4
- package/dist/components/htmlEditorControls/TextBackgroundColor.js +6 -6
- package/dist/components/htmlEditorControls/TextColor.js +7 -7
- package/dist/components/htmlEditorControls/Underline.js +4 -4
- package/dist/components/htmlEditorControls/WordCount.js +3 -3
- package/dist/components/htmlEditorNodes/HtmlComment.js +24 -0
- package/dist/components/tableControls/DeleteRecord.js +2 -2
- package/dist/components/tableControls/DeleteSelected.js +2 -2
- package/dist/components/tableControls/Edit.js +4 -4
- package/dist/components/tableControls/ExportCSV.js +2 -2
- package/dist/components/tableControls/ExportJson.js +2 -2
- package/dist/components/tableControls/FieldSortHide.js +4 -4
- package/dist/components/tableControls/Filters.js +17 -17
- package/dist/components/tableControls/FirstPage.js +2 -2
- package/dist/components/tableControls/HiddenCount.js +2 -2
- package/dist/components/tableControls/Hide.js +2 -2
- package/dist/components/tableControls/LastPage.js +2 -2
- package/dist/components/tableControls/NextPage.js +2 -2
- package/dist/components/tableControls/PageSelect.js +4 -4
- package/dist/components/tableControls/PageSize.js +3 -3
- package/dist/components/tableControls/PrevPage.js +2 -2
- package/dist/components/tableControls/Search.js +2 -2
- package/dist/components/tableControls/ShowAll.js +2 -2
- package/dist/components/tableControls/TableControl.js +2 -2
- package/dist/utils/context.js +1 -1
- package/dist/utils/debounce.js +1 -1
- package/dist/utils/object.js +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/wait.js +0 -1
- package/docs/components/accordion.html +2 -0
- package/docs/components/aside.html +9 -8
- package/docs/components/card.html +2 -0
- package/docs/components/color-picker.html +2 -0
- package/docs/components/content-slider.html +2 -0
- package/docs/components/context.html +2 -0
- package/docs/components/dialog.html +2 -0
- package/docs/components/dropdown.html +2 -0
- package/docs/components/filter-list.html +2 -0
- package/docs/components/focus-capture.html +2 -0
- package/docs/components/html-editor.html +54 -6
- package/docs/components/hybrid-component.html +2 -0
- package/docs/components/icon.html +2 -0
- package/docs/components/import.html +2 -0
- package/docs/components/light-component.html +2 -0
- package/docs/components/nav-spacer.html +315 -0
- package/docs/components/nav.html +2 -0
- package/docs/components/photo-viewer.html +2 -0
- package/docs/components/resize.html +2 -0
- package/docs/components/shadow-component.html +2 -0
- package/docs/components/show-more.html +2 -0
- package/docs/components/sortable.html +2 -0
- package/docs/components/spinner.html +2 -0
- package/docs/components/split.html +2 -0
- package/docs/components/table.html +2 -0
- package/docs/components/tableControls.html +2 -0
- package/docs/components/tableCustomFields.html +2 -0
- package/docs/components/tableFetchRecords.html +2 -0
- package/docs/components/tableFieldSortHide.html +2 -0
- package/docs/components/tablePagination.html +2 -0
- package/docs/components/tablePlaceholder.html +2 -0
- package/docs/components/tableRecordEditing.html +2 -0
- package/docs/components/tableRecordFiltering.html +2 -0
- package/docs/components/tableRecordHiding.html +2 -0
- package/docs/components/tableRecordSearching.html +2 -0
- package/docs/components/tableRecordSelection.html +2 -0
- package/docs/components/tableRowControls.html +2 -0
- package/docs/components/tableServerSync.html +2 -0
- package/docs/components/tableSorting.html +2 -0
- package/docs/components/tabs.html +2 -0
- package/docs/components/tags.html +2 -0
- package/docs/components/theme-select.html +2 -0
- package/docs/components/theme-switcher.html +2 -0
- package/docs/components/timestamp.html +2 -0
- package/docs/components/toast.html +2 -0
- package/docs/components/toggle.html +2 -0
- package/docs/components/tree.html +2 -0
- package/docs/icons/comment.svg +1 -0
- package/docs/index.html +8 -0
- package/docs/src/components/Accordion.js +5 -5
- package/docs/src/components/Aside.js +16 -16
- package/docs/src/components/Card.js +3 -3
- package/docs/src/components/ColorPicker.js +4 -4
- package/docs/src/components/ContentSlider.js +3 -3
- package/docs/src/components/Context.js +1 -1
- package/docs/src/components/Dialog.js +7 -7
- package/docs/src/components/Dropdown.js +7 -4
- package/docs/src/components/FilterItem.js +2 -2
- package/docs/src/components/FilterList.js +2 -2
- package/docs/src/components/FocusCapture.js +2 -2
- package/docs/src/components/HtmlEditor.js +62 -39
- package/docs/src/components/HybridComponent.js +1 -1
- package/docs/src/components/Icon.js +2 -2
- package/docs/src/components/Import.js +1 -1
- package/docs/src/components/LightComponent.js +1 -1
- package/docs/src/components/Main.js +3 -3
- package/docs/src/components/Nav.js +2 -2
- package/docs/src/components/NavSpacer.js +6 -0
- package/docs/src/components/PhotoViewer.js +3 -3
- package/docs/src/components/Resize.js +3 -3
- package/docs/src/components/ShadowComponent.js +1 -1
- package/docs/src/components/ShowMore.js +3 -3
- package/docs/src/components/Sortable.js +3 -3
- package/docs/src/components/Spinner.js +2 -2
- package/docs/src/components/Split.js +3 -3
- package/docs/src/components/Table.js +27 -27
- package/docs/src/components/Tabs.js +7 -7
- package/docs/src/components/Tags.js +4 -4
- package/docs/src/components/ThemeSelect.js +4 -4
- package/docs/src/components/ThemeSwitcher.js +5 -5
- package/docs/src/components/Timestamp.js +1 -1
- package/docs/src/components/Toast.js +3 -3
- package/docs/src/components/Toggle.js +3 -3
- package/docs/src/components/Tree.js +10 -10
- package/docs/src/components/htmlEditorControls/AlignCenter.js +4 -4
- package/docs/src/components/htmlEditorControls/AlignJustify.js +4 -4
- package/docs/src/components/htmlEditorControls/AlignLeft.js +4 -4
- package/docs/src/components/htmlEditorControls/AlignRight.js +4 -4
- package/docs/src/components/htmlEditorControls/Bold.js +4 -4
- package/docs/src/components/htmlEditorControls/BulletList.js +4 -4
- package/docs/src/components/htmlEditorControls/CharacterCount.js +3 -3
- package/docs/src/components/htmlEditorControls/ClearFormatting.js +4 -4
- package/docs/src/components/htmlEditorControls/CodeBlock.js +3 -3
- package/docs/src/components/htmlEditorControls/ControlGroup.js +2 -2
- package/docs/src/components/htmlEditorControls/ControlSpacer.js +2 -2
- package/docs/src/components/htmlEditorControls/CreateLink.js +3 -3
- package/docs/src/components/htmlEditorControls/DropdownControl.js +3 -3
- package/docs/src/components/htmlEditorControls/FormatBlock.js +4 -4
- package/docs/src/components/htmlEditorControls/HtmlEditorControl.js +2 -2
- package/docs/src/components/htmlEditorControls/InlineCode.js +3 -3
- package/docs/src/components/htmlEditorControls/InsertTable.js +3 -3
- package/docs/src/components/htmlEditorControls/Italic.js +4 -4
- package/docs/src/components/htmlEditorControls/Mode.js +3 -3
- package/docs/src/components/htmlEditorControls/NumberList.js +4 -4
- package/docs/src/components/htmlEditorControls/Strikethrough.js +4 -4
- package/docs/src/components/htmlEditorControls/TextBackgroundColor.js +6 -6
- package/docs/src/components/htmlEditorControls/TextColor.js +7 -7
- package/docs/src/components/htmlEditorControls/Underline.js +4 -4
- package/docs/src/components/htmlEditorControls/WordCount.js +3 -3
- package/docs/src/components/htmlEditorNodes/HtmlComment.js +24 -0
- package/docs/src/components/tableControls/DeleteRecord.js +2 -2
- package/docs/src/components/tableControls/DeleteSelected.js +2 -2
- package/docs/src/components/tableControls/Edit.js +4 -4
- package/docs/src/components/tableControls/ExportCSV.js +2 -2
- package/docs/src/components/tableControls/ExportJson.js +2 -2
- package/docs/src/components/tableControls/FieldSortHide.js +4 -4
- package/docs/src/components/tableControls/Filters.js +17 -17
- package/docs/src/components/tableControls/FirstPage.js +2 -2
- package/docs/src/components/tableControls/HiddenCount.js +2 -2
- package/docs/src/components/tableControls/Hide.js +2 -2
- package/docs/src/components/tableControls/LastPage.js +2 -2
- package/docs/src/components/tableControls/NextPage.js +2 -2
- package/docs/src/components/tableControls/PageSelect.js +4 -4
- package/docs/src/components/tableControls/PageSize.js +3 -3
- package/docs/src/components/tableControls/PrevPage.js +2 -2
- package/docs/src/components/tableControls/Search.js +2 -2
- package/docs/src/components/tableControls/ShowAll.js +2 -2
- package/docs/src/components/tableControls/TableControl.js +2 -2
- package/docs/src/utils/context.js +1 -1
- package/docs/src/utils/debounce.js +1 -1
- package/docs/src/utils/object.js +1 -1
- package/docs/src/utils/theme.js +1 -1
- package/docs/src/utils/wait.js +0 -1
- package/docs/utils/context.html +2 -0
- package/docs/utils/cookie.html +2 -0
- package/docs/utils/debounce.html +2 -0
- package/docs/utils/drag.html +2 -0
- package/docs/utils/elevation.html +2 -0
- package/docs/utils/formatTimestamp.html +2 -0
- package/docs/utils/object.html +2 -0
- package/docs/utils/propConverters.html +2 -0
- package/docs/utils/string.html +2 -0
- package/docs/utils/theme.html +2 -0
- package/docs/utils/toTitleCase.html +2 -0
- package/docs/utils/type.html +2 -0
- package/docs/utils/wait.html +2 -0
- package/docs-src/components/aside.page.html +7 -8
- package/docs-src/components/html-editor.page.html +52 -6
- package/docs-src/components/nav-spacer.page.html +65 -0
- package/docs-src/index.page.html +6 -0
- package/docs-src/nav.fragment.html +2 -0
- package/icons/comment.svg +1 -0
- package/llm.txt +1 -0
- package/package.json +1 -1
- package/scripts/build.js +1 -1
- package/src/components/Accordion.js +1 -1
- package/src/components/Dropdown.js +7 -3
- package/src/components/HtmlEditor.js +699 -1206
- package/src/components/NavSpacer.js +55 -0
- package/src/components/htmlEditorControls/AlignCenter.js +5 -5
- package/src/components/htmlEditorControls/AlignJustify.js +5 -5
- package/src/components/htmlEditorControls/AlignLeft.js +5 -5
- package/src/components/htmlEditorControls/AlignRight.js +5 -5
- package/src/components/htmlEditorControls/Bold.js +5 -5
- package/src/components/htmlEditorControls/BulletList.js +5 -5
- package/src/components/htmlEditorControls/ClearFormatting.js +5 -5
- package/src/components/htmlEditorControls/CodeBlock.js +16 -8
- package/src/components/htmlEditorControls/CreateLink.js +3 -7
- package/src/components/htmlEditorControls/FormatBlock.js +5 -5
- package/src/components/htmlEditorControls/InlineCode.js +1 -13
- package/src/components/htmlEditorControls/InsertTable.js +43 -44
- package/src/components/htmlEditorControls/Italic.js +5 -5
- package/src/components/htmlEditorControls/Mode.js +1 -1
- package/src/components/htmlEditorControls/NumberList.js +5 -5
- package/src/components/htmlEditorControls/Strikethrough.js +5 -5
- package/src/components/htmlEditorControls/Underline.js +5 -5
- package/src/components/htmlEditorNodes/HtmlComment.js +118 -0
- package/tests/components/Accordion.browser-test.js +154 -0
- package/tests/components/Dropdown.browser-test.js +85 -0
- package/tests/components/HtmlComment.browser-test.js +115 -0
- package/tests/components/HtmlEditor.browser-test.js +379 -0
- package/tests/components/NavSpacer.browser-test.js +81 -0
|
@@ -2,35 +2,32 @@ import { html, css } from '../lit-all.min.js';
|
|
|
2
2
|
import ShadowComponent from './ShadowComponent.js';
|
|
3
3
|
import formatCode from '../utils/formatCode.js';
|
|
4
4
|
import debounce from '../utils/debounce.js';
|
|
5
|
+
import { getCalculatedTheme, subscribeToTheme } from '../utils/theme.js';
|
|
6
|
+
import Dialog from './Dialog.js';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
Default CDN URLs
|
|
10
|
+
*/
|
|
11
|
+
const LEXICAL_VERSION = '0.43.0';
|
|
12
|
+
const MONACO_VERSION = '0.52.2';
|
|
13
|
+
const DEFAULT_LEXICAL_BASE = 'https://esm.sh';
|
|
14
|
+
const DEFAULT_MONACO_SRC = `https://cdn.jsdelivr.net/npm/monaco-editor@${MONACO_VERSION}/min`;
|
|
15
|
+
|
|
16
|
+
const lexicalUrl = (base, pkg) => `${base}/${pkg}@${LEXICAL_VERSION}`;
|
|
5
17
|
|
|
6
18
|
export default class HtmlEditor extends ShadowComponent {
|
|
7
19
|
static formAssociated = true;
|
|
8
20
|
|
|
9
21
|
static properties = {
|
|
10
|
-
name: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
},
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
type: Object,
|
|
20
|
-
state: true
|
|
21
|
-
},
|
|
22
|
-
mode: {
|
|
23
|
-
type: String,
|
|
24
|
-
reflect: true
|
|
25
|
-
},
|
|
26
|
-
hasTopToolbar: {
|
|
27
|
-
type: Boolean,
|
|
28
|
-
state: true
|
|
29
|
-
},
|
|
30
|
-
hasBottomToolbar: {
|
|
31
|
-
type: Boolean,
|
|
32
|
-
state: true
|
|
33
|
-
}
|
|
22
|
+
name: { type: String, reflect: true },
|
|
23
|
+
value: { type: String, reflect: true },
|
|
24
|
+
selection: { type: Object, state: true },
|
|
25
|
+
mode: { type: String, reflect: true },
|
|
26
|
+
lexicalSrc: { type: String, attribute: 'lexical-src' },
|
|
27
|
+
monacoSrc: { type: String, attribute: 'monaco-src' },
|
|
28
|
+
nodes: { type: String },
|
|
29
|
+
hasTopToolbar: { type: Boolean, state: true },
|
|
30
|
+
hasBottomToolbar: { type: Boolean, state: true }
|
|
34
31
|
};
|
|
35
32
|
|
|
36
33
|
constructor() {
|
|
@@ -41,14 +38,18 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
41
38
|
this.selection = null;
|
|
42
39
|
this.cursor = null;
|
|
43
40
|
this.mode = 'visual';
|
|
41
|
+
this.lexicalSrc = '';
|
|
42
|
+
this.monacoSrc = '';
|
|
43
|
+
this.nodes = '';
|
|
44
44
|
this.hasTopToolbar = false;
|
|
45
45
|
this.hasBottomToolbar = false;
|
|
46
|
-
this.
|
|
46
|
+
this.skipValueSync = false;
|
|
47
|
+
this.lexicalValueSync = false;
|
|
47
48
|
this.savedSelection = null;
|
|
48
|
-
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
this.lexicalEditor = null;
|
|
50
|
+
this.monacoEditor = null;
|
|
51
|
+
this.lx = {};
|
|
52
|
+
this.debouncedSyncValue = debounce(() => this.syncValueFromLexical(), 300);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/*
|
|
@@ -56,64 +57,49 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
56
57
|
*/
|
|
57
58
|
connectedCallback() {
|
|
58
59
|
super.connectedCallback();
|
|
59
|
-
|
|
60
60
|
if(this.hasAttribute('value')){
|
|
61
61
|
this.value = this.getAttribute('value');
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
this.slotObserver
|
|
65
|
-
this.updateToolbarVisibility();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
this.slotObserver.observe(this, {
|
|
69
|
-
childList: true,
|
|
70
|
-
subtree: true
|
|
71
|
-
});
|
|
72
|
-
|
|
63
|
+
this.slotObserver = new MutationObserver(() => this.updateToolbarVisibility());
|
|
64
|
+
this.slotObserver.observe(this, { childList: true, subtree: true });
|
|
73
65
|
this.updateToolbarVisibility();
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
disconnectedCallback() {
|
|
77
69
|
super.disconnectedCallback();
|
|
78
70
|
this.slotObserver?.disconnect();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if(this.editorEl){
|
|
84
|
-
this.editorEl.removeEventListener('mouseup', this.boundUpdateSelection);
|
|
85
|
-
this.editorEl.removeEventListener('keyup', this.boundUpdateSelection);
|
|
86
|
-
this.editorEl.removeEventListener('focus', this.boundUpdateSelection);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
71
|
+
this.cleanupFns?.forEach(fn => fn?.());
|
|
72
|
+
this.monacoEditor?.dispose();
|
|
73
|
+
this.unsubscribeTheme?.();
|
|
74
|
+
if(this.syncShadowSelection) document.removeEventListener('selectionchange', this.syncShadowSelection);
|
|
89
75
|
}
|
|
90
76
|
|
|
91
77
|
updateToolbarVisibility() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
const bottomSlotContent = Array.from(this.children).filter(child =>
|
|
96
|
-
child.getAttribute('slot') === 'toolbar-bottom'
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
this.hasTopToolbar = topSlotContent.length > 0;
|
|
100
|
-
this.hasBottomToolbar = bottomSlotContent.length > 0;
|
|
78
|
+
this.hasTopToolbar = Array.from(this.children).some(c => c.getAttribute('slot') === 'toolbar-top');
|
|
79
|
+
this.hasBottomToolbar = Array.from(this.children).some(c => c.getAttribute('slot') === 'toolbar-bottom');
|
|
101
80
|
}
|
|
102
81
|
|
|
103
82
|
updated(changedProperties) {
|
|
104
83
|
super.updated(changedProperties);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
84
|
+
if(changedProperties.has('value') && !this.skipValueSync){
|
|
85
|
+
if(this.lexicalValueSync){
|
|
86
|
+
this.lexicalValueSync = false;
|
|
87
|
+
} else {
|
|
88
|
+
if(this.lexicalEditor && this.mode === 'visual' && !this.isVisualCompatible(this.value)){
|
|
89
|
+
this.skipLexicalExport = true;
|
|
90
|
+
this.mode = 'code';
|
|
91
|
+
} else {
|
|
92
|
+
this.syncContentToEditors();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
108
95
|
this.updateFormValue();
|
|
109
96
|
this.dispatchEvent(new CustomEvent('change', {
|
|
110
97
|
detail: { value: this.value },
|
|
111
98
|
bubbles: true
|
|
112
99
|
}));
|
|
113
100
|
}
|
|
114
|
-
|
|
115
101
|
if(changedProperties.has('mode')){
|
|
116
|
-
this.
|
|
102
|
+
this.handleModeSwitch(changedProperties.get('mode'));
|
|
117
103
|
this.dispatchEvent(new CustomEvent('mode-changed', {
|
|
118
104
|
detail: { mode: this.mode },
|
|
119
105
|
bubbles: true
|
|
@@ -121,22 +107,10 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
121
107
|
}
|
|
122
108
|
}
|
|
123
109
|
|
|
124
|
-
firstUpdated() {
|
|
125
|
-
this.
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
if(this.value){
|
|
129
|
-
this.syncContent();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
this.boundUpdateSelection = this.updateSelection.bind(this);
|
|
133
|
-
|
|
134
|
-
this.shadowRoot.addEventListener('selectionchange', this.boundUpdateSelection);
|
|
135
|
-
this.editorEl.addEventListener('mouseup', this.boundUpdateSelection);
|
|
136
|
-
this.editorEl.addEventListener('keyup', this.boundUpdateSelection);
|
|
137
|
-
this.editorEl.addEventListener('focus', this.boundUpdateSelection);
|
|
138
|
-
this.editorEl.addEventListener('keydown', this.handleTableNavigation);
|
|
139
|
-
|
|
110
|
+
async firstUpdated() {
|
|
111
|
+
this.lexicalContainer = this.shadowRoot.querySelector('.lexical-editor');
|
|
112
|
+
this.monacoContainer = this.shadowRoot.querySelector('.monaco-editor-container');
|
|
113
|
+
await this.initLexical();
|
|
140
114
|
this.dispatchEvent(new CustomEvent('ready', {
|
|
141
115
|
detail: { value: this.value },
|
|
142
116
|
bubbles: true
|
|
@@ -144,385 +118,406 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
144
118
|
}
|
|
145
119
|
|
|
146
120
|
/*
|
|
147
|
-
|
|
121
|
+
Module Loading
|
|
148
122
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
123
|
+
async loadNodeModules() {
|
|
124
|
+
if(!this.nodes?.trim()) return [];
|
|
125
|
+
const base = new URL('./htmlEditorNodes/', import.meta.url).href;
|
|
126
|
+
const modules = await Promise.all(
|
|
127
|
+
this.nodes.split(',').map(n => n.trim()).filter(Boolean).map(n => import(/* @vite-ignore */ `${base}${n}.js`))
|
|
128
|
+
);
|
|
129
|
+
return modules.map(m => m.default?.lexicalNode).filter(Boolean);
|
|
151
130
|
}
|
|
152
131
|
|
|
153
|
-
|
|
154
|
-
this.
|
|
132
|
+
async loadLexicalModules() {
|
|
133
|
+
const base = this.lexicalSrc || DEFAULT_LEXICAL_BASE;
|
|
134
|
+
const url = pkg => lexicalUrl(base, pkg);
|
|
135
|
+
const [lexical, richText, lexicalHtml, history, list, link, selection, table, code] = await Promise.all([
|
|
136
|
+
import(/* @vite-ignore */ url('lexical')),
|
|
137
|
+
import(/* @vite-ignore */ url('@lexical/rich-text')),
|
|
138
|
+
import(/* @vite-ignore */ url('@lexical/html')),
|
|
139
|
+
import(/* @vite-ignore */ url('@lexical/history')),
|
|
140
|
+
import(/* @vite-ignore */ url('@lexical/list')),
|
|
141
|
+
import(/* @vite-ignore */ url('@lexical/link')),
|
|
142
|
+
import(/* @vite-ignore */ url('@lexical/selection')),
|
|
143
|
+
import(/* @vite-ignore */ url('@lexical/table')),
|
|
144
|
+
import(/* @vite-ignore */ url('@lexical/code'))
|
|
145
|
+
]);
|
|
146
|
+
this.lx = { lexical, richText, lexicalHtml, history, list, link, selection, table, code };
|
|
147
|
+
this.StyledTextNode = class extends lexical.TextNode {
|
|
148
|
+
static getType() { return 'styled-text'; }
|
|
149
|
+
static clone(node) { return new this(node.__text, node.__key); }
|
|
150
|
+
static importDOM() {
|
|
151
|
+
return {
|
|
152
|
+
span: () => ({
|
|
153
|
+
conversion: domNode => {
|
|
154
|
+
const style = domNode.getAttribute('style');
|
|
155
|
+
if(!style) return null;
|
|
156
|
+
const node = lexical.$createTextNode(domNode.textContent);
|
|
157
|
+
node.setStyle(style);
|
|
158
|
+
return { node };
|
|
159
|
+
},
|
|
160
|
+
priority: 1
|
|
161
|
+
})
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
static importJSON(json) { return lexical.$createTextNode(json.text); }
|
|
165
|
+
exportJSON() { return { ...super.exportJSON(), type: 'styled-text' }; }
|
|
166
|
+
};
|
|
155
167
|
}
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
this.
|
|
159
|
-
|
|
169
|
+
async initLexical() {
|
|
170
|
+
await this.loadLexicalModules();
|
|
171
|
+
this.customNodes = await this.loadNodeModules();
|
|
172
|
+
this.nodeCompatCheckers = this.customNodes.filter(n => typeof n.isVisualCompatible === 'function').map(n => n.isVisualCompatible);
|
|
173
|
+
this.nodePreprocessors = this.customNodes.filter(n => typeof n.preprocessHtml === 'function').map(n => n.preprocessHtml);
|
|
174
|
+
const { lexical, richText, history, list, link, table, code } = this.lx;
|
|
175
|
+
|
|
176
|
+
const editorConfig = {
|
|
177
|
+
namespace: 'KempoHtmlEditor',
|
|
178
|
+
theme: {
|
|
179
|
+
paragraph: 'k-editor-p',
|
|
180
|
+
heading: { h1: 'k-editor-h1', h2: 'k-editor-h2', h3: 'k-editor-h3', h4: 'k-editor-h4', h5: 'k-editor-h5', h6: 'k-editor-h6' },
|
|
181
|
+
text: { underline: 'td-u', strikethrough: 'td-lt' },
|
|
182
|
+
list: { ul: 'k-editor-ul', ol: 'k-editor-ol', listitem: 'k-editor-li' },
|
|
183
|
+
link: 'k-editor-link',
|
|
184
|
+
quote: 'k-editor-quote',
|
|
185
|
+
code: 'k-editor-code-block',
|
|
186
|
+
codeHighlight: {},
|
|
187
|
+
table: 'k-editor-table',
|
|
188
|
+
tableCell: 'k-editor-table-cell',
|
|
189
|
+
tableCellHeader: 'k-editor-table-cell-header'
|
|
190
|
+
},
|
|
191
|
+
nodes: [richText.HeadingNode, richText.QuoteNode, list.ListNode, list.ListItemNode, link.LinkNode, table.TableNode, table.TableCellNode, table.TableRowNode, code.CodeNode, code.CodeHighlightNode, this.StyledTextNode, ...this.customNodes],
|
|
192
|
+
onError: console.error,
|
|
193
|
+
editorState: null
|
|
194
|
+
};
|
|
160
195
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if(!cell) return;
|
|
179
|
-
|
|
180
|
-
e.preventDefault();
|
|
181
|
-
|
|
182
|
-
if(e.key === 'Tab'){
|
|
183
|
-
const row = cell.parentElement;
|
|
184
|
-
const cells = Array.from(row.cells);
|
|
185
|
-
const currentIndex = cells.indexOf(cell);
|
|
186
|
-
|
|
187
|
-
if(e.shiftKey){
|
|
188
|
-
// Shift+Tab: Go to previous cell
|
|
189
|
-
let prevCell = cells[currentIndex - 1];
|
|
190
|
-
|
|
191
|
-
if(!prevCell){
|
|
192
|
-
const prevRow = row.previousElementSibling;
|
|
193
|
-
if(prevRow){
|
|
194
|
-
prevCell = prevRow.cells[prevRow.cells.length - 1];
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if(prevCell){
|
|
199
|
-
const range = document.createRange();
|
|
200
|
-
range.selectNodeContents(prevCell);
|
|
201
|
-
range.collapse(false);
|
|
202
|
-
sel.removeAllRanges();
|
|
203
|
-
sel.addRange(range);
|
|
204
|
-
prevCell.focus();
|
|
205
|
-
}
|
|
196
|
+
this.lexicalEditor = lexical.createEditor(editorConfig);
|
|
197
|
+
this.lexicalEditor.setRootElement(this.lexicalContainer);
|
|
198
|
+
|
|
199
|
+
this.cleanupFns = [
|
|
200
|
+
richText.registerRichText(this.lexicalEditor),
|
|
201
|
+
history.registerHistory(this.lexicalEditor, history.createEmptyHistoryState(), 300)
|
|
202
|
+
];
|
|
203
|
+
if(list.registerList) this.cleanupFns.push(list.registerList(this.lexicalEditor));
|
|
204
|
+
if(table.registerTable) this.cleanupFns.push(table.registerTable(this.lexicalEditor));
|
|
205
|
+
if(code.registerCodeHighlighting) this.cleanupFns.push(code.registerCodeHighlighting(this.lexicalEditor));
|
|
206
|
+
if(link.registerLink) this.cleanupFns.push(link.registerLink(this.lexicalEditor, {
|
|
207
|
+
validateUrl: url => { try { new URL(url); return true; } catch { return false; } }
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
if(this.value){
|
|
211
|
+
if(this.isVisualCompatible(this.value)){
|
|
212
|
+
this.importHtmlToLexical(this.value);
|
|
206
213
|
} else {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if(!nextCell){
|
|
211
|
-
const nextRow = row.nextElementSibling;
|
|
212
|
-
if(nextRow){
|
|
213
|
-
nextCell = nextRow.cells[0];
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if(nextCell){
|
|
218
|
-
const range = document.createRange();
|
|
219
|
-
range.selectNodeContents(nextCell);
|
|
220
|
-
range.collapse(false);
|
|
221
|
-
sel.removeAllRanges();
|
|
222
|
-
sel.addRange(range);
|
|
223
|
-
nextCell.focus();
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
} else if(e.key === 'Enter'){
|
|
227
|
-
const row = cell.parentElement;
|
|
228
|
-
const cells = Array.from(row.cells);
|
|
229
|
-
const currentIndex = cells.indexOf(cell);
|
|
230
|
-
|
|
231
|
-
if(e.shiftKey){
|
|
232
|
-
// Shift+Enter: Go to cell above
|
|
233
|
-
const prevRow = row.previousElementSibling;
|
|
234
|
-
if(prevRow && prevRow.cells[currentIndex]){
|
|
235
|
-
const prevCell = prevRow.cells[currentIndex];
|
|
236
|
-
const range = document.createRange();
|
|
237
|
-
range.selectNodeContents(prevCell);
|
|
238
|
-
range.collapse(false);
|
|
239
|
-
sel.removeAllRanges();
|
|
240
|
-
sel.addRange(range);
|
|
241
|
-
prevCell.focus();
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
// Enter: Go to cell below or create paragraph after table
|
|
245
|
-
const nextRow = row.nextElementSibling;
|
|
246
|
-
if(nextRow && nextRow.cells[currentIndex]){
|
|
247
|
-
const nextCell = nextRow.cells[currentIndex];
|
|
248
|
-
const range = document.createRange();
|
|
249
|
-
range.selectNodeContents(nextCell);
|
|
250
|
-
range.collapse(false);
|
|
251
|
-
sel.removeAllRanges();
|
|
252
|
-
sel.addRange(range);
|
|
253
|
-
nextCell.focus();
|
|
254
|
-
} else {
|
|
255
|
-
// Last row - create paragraph after table
|
|
256
|
-
const table = cell.closest('table');
|
|
257
|
-
if(table){
|
|
258
|
-
const p = document.createElement('p');
|
|
259
|
-
p.innerHTML = '<br>';
|
|
260
|
-
|
|
261
|
-
if(table.nextSibling){
|
|
262
|
-
table.parentNode.insertBefore(p, table.nextSibling);
|
|
263
|
-
} else {
|
|
264
|
-
table.parentNode.appendChild(p);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const range = document.createRange();
|
|
268
|
-
range.setStart(p, 0);
|
|
269
|
-
range.setEnd(p, 0);
|
|
270
|
-
sel.removeAllRanges();
|
|
271
|
-
sel.addRange(range);
|
|
272
|
-
p.focus();
|
|
273
|
-
}
|
|
274
|
-
}
|
|
214
|
+
this.skipLexicalExport = true;
|
|
215
|
+
this.mode = 'code';
|
|
275
216
|
}
|
|
276
217
|
}
|
|
277
|
-
};
|
|
278
218
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.
|
|
282
|
-
|
|
283
|
-
|
|
219
|
+
this.lexicalEditor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
|
|
220
|
+
if(dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
|
|
221
|
+
this.debouncedSyncValue();
|
|
222
|
+
this.dispatchEvent(new CustomEvent('input', {
|
|
223
|
+
detail: { value: this.exportHtmlFromLexical() },
|
|
224
|
+
bubbles: true
|
|
225
|
+
}));
|
|
226
|
+
});
|
|
284
227
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
228
|
+
this.lexicalEditor.registerCommand(
|
|
229
|
+
lexical.SELECTION_CHANGE_COMMAND,
|
|
230
|
+
() => { this.updateSelection(); return false; },
|
|
231
|
+
lexical.COMMAND_PRIORITY_LOW
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
this.syncShadowSelection = () => {
|
|
235
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return;
|
|
236
|
+
const shadowSel = this.shadowRoot.getSelection?.();
|
|
237
|
+
if(!shadowSel || shadowSel.rangeCount === 0) return;
|
|
238
|
+
const range = shadowSel.getRangeAt(0);
|
|
239
|
+
if(!this.lexicalContainer.contains(range.startContainer)) return;
|
|
240
|
+
this.lexicalEditor.update(() => {
|
|
241
|
+
const lexSel = lexical.$createRangeSelectionFromDom(shadowSel, this.lexicalEditor);
|
|
242
|
+
if(lexSel) lexical.$setSelection(lexSel);
|
|
243
|
+
}, { discrete: true });
|
|
244
|
+
this.updateSelection();
|
|
245
|
+
};
|
|
246
|
+
document.addEventListener('selectionchange', this.syncShadowSelection);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async initMonaco() {
|
|
250
|
+
if(this.monacoEditor) return;
|
|
251
|
+
if(this.monacoInitPromise) return this.monacoInitPromise;
|
|
252
|
+
this.monacoInitPromise = this._initMonaco();
|
|
253
|
+
await this.monacoInitPromise;
|
|
254
|
+
this.monacoInitPromise = null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async _initMonaco() {
|
|
258
|
+
const src = this.monacoSrc || DEFAULT_MONACO_SRC;
|
|
259
|
+
|
|
260
|
+
await new Promise((resolve, reject) => {
|
|
261
|
+
if(window.monaco){ resolve(); return; }
|
|
262
|
+
if(window.require?.defined?.('vs/editor/editor.main')){ resolve(); return; }
|
|
263
|
+
const existing = document.querySelector(`script[src="${src}/vs/loader.js"]`);
|
|
264
|
+
if(existing){
|
|
265
|
+
existing.addEventListener('load', () => {
|
|
266
|
+
window.require.config({ paths: { vs: `${src}/vs` } });
|
|
267
|
+
window.require(['vs/editor/editor.main'], () => resolve(), reject);
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const script = document.createElement('script');
|
|
272
|
+
script.src = `${src}/vs/loader.js`;
|
|
273
|
+
script.onload = () => {
|
|
274
|
+
window.require.config({ paths: { vs: `${src}/vs` } });
|
|
275
|
+
window.require(['vs/editor/editor.main'], () => resolve(), reject);
|
|
276
|
+
};
|
|
277
|
+
script.onerror = reject;
|
|
278
|
+
document.head.appendChild(script);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
this.monacoEditor = window.monaco.editor.create(this.monacoContainer, {
|
|
282
|
+
value: formatCode(this.value),
|
|
283
|
+
language: 'html',
|
|
284
|
+
theme: getCalculatedTheme() === 'dark' ? 'vs-dark' : 'vs',
|
|
285
|
+
minimap: { enabled: false },
|
|
286
|
+
wordWrap: 'on',
|
|
287
|
+
fontSize: 14,
|
|
288
|
+
scrollBeyondLastLine: false,
|
|
289
|
+
automaticLayout: true,
|
|
290
|
+
tabSize: 2
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const monacoCSS = document.querySelector('link[href*="monaco"][href*="editor.main.css"]');
|
|
294
|
+
if(monacoCSS){
|
|
295
|
+
const link = document.createElement('link');
|
|
296
|
+
link.rel = 'stylesheet';
|
|
297
|
+
link.href = monacoCSS.href;
|
|
298
|
+
this.shadowRoot.appendChild(link);
|
|
290
299
|
}
|
|
291
300
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.
|
|
299
|
-
|
|
301
|
+
this.unsubscribeTheme = subscribeToTheme(() => {
|
|
302
|
+
if(this.monacoEditor) window.monaco.editor.setTheme(getCalculatedTheme() === 'dark' ? 'vs-dark' : 'vs');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
this.monacoEditor.onDidChangeModelContent(() => {
|
|
306
|
+
this.skipValueSync = true;
|
|
307
|
+
this.value = this.monacoEditor.getValue();
|
|
308
|
+
this.skipValueSync = false;
|
|
309
|
+
this.updateFormValue();
|
|
310
|
+
this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value }, bubbles: true }));
|
|
311
|
+
this.dispatchEvent(new CustomEvent('input', { detail: { value: this.value }, bubbles: true }));
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/*
|
|
316
|
+
Content Sync
|
|
317
|
+
*/
|
|
318
|
+
importHtmlToLexical(htmlString) {
|
|
319
|
+
if(!this.lexicalEditor || !this.lx.lexicalHtml) return;
|
|
320
|
+
const { lexical, lexicalHtml } = this.lx;
|
|
321
|
+
const processed = (this.nodePreprocessors || []).reduce((str, fn) => fn(str), htmlString);
|
|
322
|
+
this.lexicalEditor.update(() => {
|
|
323
|
+
const root = lexical.$getRoot();
|
|
324
|
+
root.clear();
|
|
325
|
+
if(!processed?.trim()) return;
|
|
326
|
+
const dom = new DOMParser().parseFromString(processed, 'text/html');
|
|
327
|
+
const nodes = lexicalHtml.$generateNodesFromDOM(this.lexicalEditor, dom);
|
|
328
|
+
if(nodes.length > 0) lexical.$insertNodes(nodes);
|
|
329
|
+
}, { discrete: true });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
exportHtmlFromLexical() {
|
|
333
|
+
if(!this.lexicalEditor || !this.lx.lexicalHtml) return this.value;
|
|
334
|
+
let result = '';
|
|
335
|
+
this.lexicalEditor.getEditorState().read(() => {
|
|
336
|
+
result = this.lx.lexicalHtml.$generateHtmlFromNodes(this.lexicalEditor, null);
|
|
337
|
+
});
|
|
338
|
+
return this.cleanExportedHtml(result);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
isVisualCompatible(htmlStr) {
|
|
342
|
+
if(!htmlStr?.trim()) return true;
|
|
343
|
+
const incompatibleTags = new Set(['script', 'style', 'meta', 'link', 'head', 'iframe', 'object', 'embed', 'canvas', 'video', 'audio', 'form', 'input', 'button', 'select', 'textarea', 'fieldset', 'label', 'noscript', 'template', 'slot', 'svg', 'math']);
|
|
344
|
+
const checkers = this.nodeCompatCheckers || [];
|
|
345
|
+
const doc = new DOMParser().parseFromString(htmlStr, 'text/html');
|
|
346
|
+
const walker = document.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT);
|
|
347
|
+
let node;
|
|
348
|
+
while((node = walker.nextNode())){
|
|
349
|
+
if(checkers.some(check => check(node))) continue;
|
|
350
|
+
if(node.nodeType === Node.COMMENT_NODE) return false;
|
|
351
|
+
if(incompatibleTags.has(node.tagName?.toLowerCase())) return false;
|
|
300
352
|
}
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
301
355
|
|
|
302
|
-
|
|
356
|
+
cleanExportedHtml(html) {
|
|
357
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
358
|
+
doc.body.querySelectorAll('[class]').forEach(el => {
|
|
359
|
+
const classes = Array.from(el.classList).filter(c => !c.startsWith('k-editor-') && c !== 'td-u' && c !== 'td-lt');
|
|
360
|
+
if(classes.length === 0) el.removeAttribute('class');
|
|
361
|
+
else el.className = classes.join(' ');
|
|
362
|
+
});
|
|
363
|
+
doc.body.querySelectorAll('[style]').forEach(el => {
|
|
364
|
+
const style = el.style.cssText.replace(/white-space:\s*pre-wrap;?\s*/g, '').trim();
|
|
365
|
+
if(!style) el.removeAttribute('style');
|
|
366
|
+
else el.style.cssText = style;
|
|
367
|
+
});
|
|
368
|
+
doc.body.querySelectorAll('span:not([class]):not([style]):not([id])').forEach(span => {
|
|
369
|
+
if(!span.attributes.length) span.replaceWith(...span.childNodes);
|
|
370
|
+
});
|
|
371
|
+
doc.body.querySelectorAll('b > strong, i > em, b > b, strong > strong, i > i, em > em').forEach(inner => {
|
|
372
|
+
inner.replaceWith(...inner.childNodes);
|
|
373
|
+
});
|
|
374
|
+
doc.body.querySelectorAll('pre[data-highlight-language], code[data-highlight-language]').forEach(el => {
|
|
375
|
+
el.removeAttribute('data-highlight-language');
|
|
376
|
+
el.removeAttribute('data-language');
|
|
377
|
+
});
|
|
378
|
+
return doc.body.innerHTML;
|
|
379
|
+
}
|
|
303
380
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
endContainer: range.endContainer,
|
|
316
|
-
endOffset: range.endOffset,
|
|
317
|
-
text: selectedText,
|
|
318
|
-
collapsed: false
|
|
319
|
-
};
|
|
381
|
+
syncValueFromLexical() {
|
|
382
|
+
if(!this.lexicalEditor) return;
|
|
383
|
+
this.lexicalValueSync = true;
|
|
384
|
+
this.value = this.exportHtmlFromLexical();
|
|
385
|
+
this.updateFormValue();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
syncContentToEditors() {
|
|
389
|
+
if(this.mode === 'visual' && this.lexicalEditor){
|
|
390
|
+
this.importHtmlToLexical(this.value);
|
|
391
|
+
}
|
|
320
392
|
}
|
|
321
|
-
}; handleEditorInput = () => {
|
|
322
|
-
// Don't update this.value on every keystroke - it triggers Lit's reactive system
|
|
323
|
-
// which calls syncContent() and destroys the cursor position.
|
|
324
|
-
// Use debounce to only update after user stops typing for 300ms.
|
|
325
|
-
this.debouncedDispatchChange();
|
|
326
|
-
this.dispatchEvent(new CustomEvent('input', { detail: { value: this.editorEl.innerHTML } }));
|
|
327
|
-
};
|
|
328
393
|
|
|
329
|
-
|
|
394
|
+
async handleModeSwitch(previousMode) {
|
|
330
395
|
if(this.mode === 'code'){
|
|
331
|
-
this.value =
|
|
396
|
+
if(this.lexicalEditor && !this.skipLexicalExport) this.value = this.exportHtmlFromLexical();
|
|
397
|
+
this.skipLexicalExport = false;
|
|
398
|
+
await this.initMonaco();
|
|
399
|
+
if(this.monacoEditor){
|
|
400
|
+
this.monacoEditor.setValue(formatCode(this.value));
|
|
401
|
+
this.monacoEditor.layout();
|
|
402
|
+
}
|
|
403
|
+
} else if(this.mode === 'visual'){
|
|
404
|
+
if(this.monacoEditor) this.value = this.monacoEditor.getValue();
|
|
405
|
+
if(this.lexicalEditor) this.importHtmlToLexical(this.value);
|
|
332
406
|
}
|
|
333
|
-
|
|
407
|
+
this.requestUpdate();
|
|
408
|
+
}
|
|
334
409
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
410
|
+
/*
|
|
411
|
+
Form Integration
|
|
412
|
+
*/
|
|
413
|
+
updateFormValue() {
|
|
414
|
+
this.internals.setFormValue(this.getValue());
|
|
415
|
+
}
|
|
340
416
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const cleanedValue = this.cleanupHtml(this.editorEl.innerHTML);
|
|
359
|
-
if(cleanedValue !== this.value){
|
|
360
|
-
this.value = cleanedValue;
|
|
361
|
-
this.syncContent();
|
|
362
|
-
}
|
|
417
|
+
formResetCallback() {
|
|
418
|
+
this.value = '';
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
formStateRestoreCallback(state) {
|
|
422
|
+
this.value = state;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/*
|
|
426
|
+
Selection Management
|
|
427
|
+
*/
|
|
428
|
+
updateSelection = () => {
|
|
429
|
+
if(this.mode !== 'visual' || !this.lexicalEditor){
|
|
430
|
+
this.selection = null;
|
|
431
|
+
return;
|
|
363
432
|
}
|
|
433
|
+
const { lexical } = this.lx;
|
|
434
|
+
this.lexicalEditor.getEditorState().read(() => {
|
|
435
|
+
const sel = lexical.$getSelection();
|
|
436
|
+
if(lexical.$isRangeSelection(sel) && !sel.isCollapsed()){
|
|
437
|
+
this.selection = { text: sel.getTextContent(), collapsed: false };
|
|
438
|
+
} else {
|
|
439
|
+
this.selection = null;
|
|
440
|
+
this.cursor = sel ? { anchor: sel.anchor, focus: sel.focus } : null;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
364
443
|
};
|
|
365
444
|
|
|
366
445
|
/*
|
|
367
446
|
Public Methods - Mode Control
|
|
368
447
|
*/
|
|
369
448
|
setMode(mode) {
|
|
370
|
-
if(['visual', 'code'].includes(mode))
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
449
|
+
if(!['visual', 'code'].includes(mode)) return this;
|
|
450
|
+
if(mode === 'visual' && !this.isVisualCompatible(this.getValue())){
|
|
451
|
+
Dialog.confirm(
|
|
452
|
+
'This html contains code that is not compatible with the visual editor, the incompatible code will be lost',
|
|
453
|
+
response => { if(response) this.mode = mode; },
|
|
454
|
+
{ title: 'Warning', confirmText: 'Change Anyways' }
|
|
455
|
+
);
|
|
456
|
+
return this;
|
|
375
457
|
}
|
|
458
|
+
this.mode = mode;
|
|
376
459
|
return this;
|
|
377
460
|
}
|
|
378
461
|
|
|
379
462
|
toggleMode() {
|
|
380
|
-
|
|
381
|
-
this.setMode(newMode);
|
|
382
|
-
return this;
|
|
463
|
+
return this.setMode(this.mode === 'visual' ? 'code' : 'visual');
|
|
383
464
|
}
|
|
384
465
|
|
|
385
466
|
/*
|
|
386
467
|
Public Methods - Content Management
|
|
387
468
|
*/
|
|
388
469
|
getValue() {
|
|
389
|
-
if(this.mode === 'visual'){
|
|
390
|
-
|
|
391
|
-
|
|
470
|
+
if(this.mode === 'visual' && this.lexicalEditor){
|
|
471
|
+
this.skipValueSync = true;
|
|
472
|
+
this.value = this.exportHtmlFromLexical();
|
|
473
|
+
this.skipValueSync = false;
|
|
474
|
+
} else if(this.mode === 'code' && this.monacoEditor){
|
|
475
|
+
return this.monacoEditor.getValue();
|
|
392
476
|
}
|
|
393
477
|
return this.value;
|
|
394
478
|
}
|
|
395
479
|
|
|
396
|
-
setValue(
|
|
397
|
-
this.
|
|
398
|
-
|
|
480
|
+
setValue(htmlStr) {
|
|
481
|
+
if(this.lexicalEditor && this.mode === 'visual' && !this.isVisualCompatible(htmlStr)){
|
|
482
|
+
this.value = htmlStr;
|
|
483
|
+
this.skipLexicalExport = true;
|
|
484
|
+
this.mode = 'code';
|
|
485
|
+
this.updateFormValue();
|
|
486
|
+
return this;
|
|
487
|
+
}
|
|
488
|
+
this.skipValueSync = true;
|
|
489
|
+
this.value = htmlStr;
|
|
490
|
+
if(this.mode === 'visual') this.syncContentToEditors();
|
|
491
|
+
else if(this.mode === 'code' && this.monacoEditor) this.monacoEditor.setValue(formatCode(htmlStr));
|
|
492
|
+
this.updateFormValue();
|
|
493
|
+
this.skipValueSync = false;
|
|
399
494
|
return this;
|
|
400
495
|
}
|
|
401
496
|
|
|
402
497
|
clear() {
|
|
403
|
-
this.
|
|
404
|
-
this.syncContent();
|
|
405
|
-
return this;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
cleanupHtml(html){
|
|
409
|
-
const tempDiv = document.createElement('div');
|
|
410
|
-
tempDiv.innerHTML = html;
|
|
411
|
-
|
|
412
|
-
const inlineTags = ['code', 'a', 'span', 'strong', 'em', 'b', 'i', 'u', 's'];
|
|
413
|
-
const blockTags = ['pre', 'div', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'];
|
|
414
|
-
|
|
415
|
-
const unwrapNested = (parent, tagName) => {
|
|
416
|
-
const elements = parent.querySelectorAll(tagName);
|
|
417
|
-
elements.forEach(el => {
|
|
418
|
-
let ancestor = el.parentElement;
|
|
419
|
-
while(ancestor && ancestor !== parent){
|
|
420
|
-
if(ancestor.tagName.toLowerCase() === tagName){
|
|
421
|
-
while(el.firstChild){
|
|
422
|
-
el.parentNode.insertBefore(el.firstChild, el);
|
|
423
|
-
}
|
|
424
|
-
el.parentNode.removeChild(el);
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
ancestor = ancestor.parentElement;
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
const removeUselessStyles = (parent) => {
|
|
433
|
-
const allElements = parent.querySelectorAll('*');
|
|
434
|
-
allElements.forEach(el => {
|
|
435
|
-
if(!el.hasAttribute('style')) return;
|
|
436
|
-
|
|
437
|
-
const style = el.style;
|
|
438
|
-
const uselessProps = [
|
|
439
|
-
'font-size',
|
|
440
|
-
'font-family',
|
|
441
|
-
'background-color',
|
|
442
|
-
'line-height',
|
|
443
|
-
'font-weight',
|
|
444
|
-
'font-style'
|
|
445
|
-
];
|
|
446
|
-
|
|
447
|
-
const uselessValues = [
|
|
448
|
-
'inherit',
|
|
449
|
-
'rgba(0, 0, 0, 0)',
|
|
450
|
-
'transparent',
|
|
451
|
-
'initial',
|
|
452
|
-
'unset'
|
|
453
|
-
];
|
|
454
|
-
|
|
455
|
-
uselessProps.forEach(prop => {
|
|
456
|
-
const value = style.getPropertyValue(prop);
|
|
457
|
-
if(value && uselessValues.some(uv => value.includes(uv))){
|
|
458
|
-
style.removeProperty(prop);
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
if(style.length === 0){
|
|
463
|
-
el.removeAttribute('style');
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
const unwrapEmptySpans = (parent) => {
|
|
469
|
-
const spans = parent.querySelectorAll('span');
|
|
470
|
-
spans.forEach(span => {
|
|
471
|
-
if(!span.className && !span.id && !span.hasAttribute('style')){
|
|
472
|
-
while(span.firstChild){
|
|
473
|
-
span.parentNode.insertBefore(span.firstChild, span);
|
|
474
|
-
}
|
|
475
|
-
span.parentNode.removeChild(span);
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
const normalizeWhitespace = (parent) => {
|
|
481
|
-
const walker = document.createTreeWalker(
|
|
482
|
-
parent,
|
|
483
|
-
NodeFilter.SHOW_TEXT,
|
|
484
|
-
null
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
const textNodes = [];
|
|
488
|
-
while(walker.nextNode()){
|
|
489
|
-
textNodes.push(walker.currentNode);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
textNodes.forEach(node => {
|
|
493
|
-
node.textContent = node.textContent.replace(/\u00A0/g, ' ');
|
|
494
|
-
});
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
inlineTags.forEach(tag => unwrapNested(tempDiv, tag));
|
|
498
|
-
blockTags.forEach(tag => unwrapNested(tempDiv, tag));
|
|
499
|
-
removeUselessStyles(tempDiv);
|
|
500
|
-
unwrapEmptySpans(tempDiv);
|
|
501
|
-
normalizeWhitespace(tempDiv);
|
|
502
|
-
|
|
503
|
-
return tempDiv.innerHTML;
|
|
498
|
+
return this.setValue('');
|
|
504
499
|
}
|
|
505
500
|
|
|
506
501
|
/*
|
|
507
502
|
Public Methods - Text Formatting
|
|
508
503
|
*/
|
|
509
504
|
bold() {
|
|
510
|
-
this.
|
|
505
|
+
this.lexicalFormat('bold');
|
|
511
506
|
return this;
|
|
512
507
|
}
|
|
513
508
|
|
|
514
509
|
italic() {
|
|
515
|
-
this.
|
|
510
|
+
this.lexicalFormat('italic');
|
|
516
511
|
return this;
|
|
517
512
|
}
|
|
518
513
|
|
|
519
514
|
underline() {
|
|
520
|
-
this.
|
|
515
|
+
this.lexicalFormat('underline');
|
|
521
516
|
return this;
|
|
522
517
|
}
|
|
523
518
|
|
|
524
519
|
strikethrough() {
|
|
525
|
-
this.
|
|
520
|
+
this.lexicalFormat('strikethrough');
|
|
526
521
|
return this;
|
|
527
522
|
}
|
|
528
523
|
|
|
@@ -530,12 +525,18 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
530
525
|
Public Methods - Lists
|
|
531
526
|
*/
|
|
532
527
|
orderedList() {
|
|
533
|
-
this.
|
|
528
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
529
|
+
this.lexicalEditor.update(() => {
|
|
530
|
+
this.lx.list.$insertList('number');
|
|
531
|
+
}, { discrete: true });
|
|
534
532
|
return this;
|
|
535
533
|
}
|
|
536
534
|
|
|
537
535
|
unorderedList() {
|
|
538
|
-
this.
|
|
536
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
537
|
+
this.lexicalEditor.update(() => {
|
|
538
|
+
this.lx.list.$insertList('bullet');
|
|
539
|
+
}, { discrete: true });
|
|
539
540
|
return this;
|
|
540
541
|
}
|
|
541
542
|
|
|
@@ -543,22 +544,22 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
543
544
|
Public Methods - Text Alignment
|
|
544
545
|
*/
|
|
545
546
|
alignLeft() {
|
|
546
|
-
this.
|
|
547
|
+
this.lexicalFormatElement('left');
|
|
547
548
|
return this;
|
|
548
549
|
}
|
|
549
550
|
|
|
550
551
|
alignCenter() {
|
|
551
|
-
this.
|
|
552
|
+
this.lexicalFormatElement('center');
|
|
552
553
|
return this;
|
|
553
554
|
}
|
|
554
555
|
|
|
555
556
|
alignRight() {
|
|
556
|
-
this.
|
|
557
|
+
this.lexicalFormatElement('right');
|
|
557
558
|
return this;
|
|
558
559
|
}
|
|
559
560
|
|
|
560
561
|
alignJustify() {
|
|
561
|
-
this.
|
|
562
|
+
this.lexicalFormatElement('justify');
|
|
562
563
|
return this;
|
|
563
564
|
}
|
|
564
565
|
|
|
@@ -566,52 +567,46 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
566
567
|
Public Methods - Text Color
|
|
567
568
|
*/
|
|
568
569
|
setTextColor(color) {
|
|
569
|
-
this.
|
|
570
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
571
|
+
const { lexical, selection } = this.lx;
|
|
572
|
+
this.lexicalEditor.update(() => {
|
|
573
|
+
const sel = lexical.$getSelection();
|
|
574
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
575
|
+
selection.$patchStyleText(sel, { color });
|
|
576
|
+
}, { discrete: true });
|
|
570
577
|
return this;
|
|
571
578
|
}
|
|
572
579
|
|
|
573
580
|
removeTextColor() {
|
|
574
|
-
this.
|
|
581
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
582
|
+
const { lexical, selection } = this.lx;
|
|
583
|
+
this.lexicalEditor.update(() => {
|
|
584
|
+
const sel = lexical.$getSelection();
|
|
585
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
586
|
+
selection.$patchStyleText(sel, { color: null });
|
|
587
|
+
}, { discrete: true });
|
|
575
588
|
return this;
|
|
576
589
|
}
|
|
577
590
|
|
|
578
591
|
setTextBackgroundColor(color) {
|
|
579
|
-
this.
|
|
592
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
593
|
+
const { lexical, selection } = this.lx;
|
|
594
|
+
this.lexicalEditor.update(() => {
|
|
595
|
+
const sel = lexical.$getSelection();
|
|
596
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
597
|
+
selection.$patchStyleText(sel, { 'background-color': color });
|
|
598
|
+
}, { discrete: true });
|
|
580
599
|
return this;
|
|
581
600
|
}
|
|
582
601
|
|
|
583
602
|
removeTextBackgroundColor() {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const walker = document.createTreeWalker(
|
|
592
|
-
fragment,
|
|
593
|
-
NodeFilter.SHOW_ELEMENT,
|
|
594
|
-
null
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
const removeBackground = (el) => {
|
|
598
|
-
if(el.style){
|
|
599
|
-
el.style.backgroundColor = '';
|
|
600
|
-
if(!el.style.cssText){
|
|
601
|
-
el.removeAttribute('style');
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
let node;
|
|
607
|
-
while(node = walker.nextNode()){
|
|
608
|
-
removeBackground(node);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
removeBackground(fragment.firstChild);
|
|
612
|
-
range.insertNode(fragment);
|
|
613
|
-
|
|
614
|
-
this.editorEl.dispatchEvent(new Event('input', {bubbles: true}));
|
|
603
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
604
|
+
const { lexical, selection } = this.lx;
|
|
605
|
+
this.lexicalEditor.update(() => {
|
|
606
|
+
const sel = lexical.$getSelection();
|
|
607
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
608
|
+
selection.$patchStyleText(sel, { 'background-color': null });
|
|
609
|
+
}, { discrete: true });
|
|
615
610
|
return this;
|
|
616
611
|
}
|
|
617
612
|
|
|
@@ -619,830 +614,305 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
619
614
|
Public Methods - Formatting Control
|
|
620
615
|
*/
|
|
621
616
|
removeFormat() {
|
|
622
|
-
if(this.mode !== 'visual') return this;
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if(!sel.rangeCount) return this;
|
|
633
|
-
|
|
634
|
-
let node = sel.getRangeAt(0).startContainer;
|
|
635
|
-
if(node.nodeType === Node.TEXT_NODE){
|
|
636
|
-
node = node.parentElement;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Find the closest formatting element
|
|
640
|
-
const formattingTags = ['B', 'I', 'U', 'S', 'STRONG', 'EM', 'CODE', 'MARK', 'SUB', 'SUP', 'SPAN'];
|
|
641
|
-
const formattingElement = node.closest(formattingTags.map(tag => tag.toLowerCase()).join(','));
|
|
642
|
-
|
|
643
|
-
if(formattingElement && this.editorEl.contains(formattingElement)){
|
|
644
|
-
// Get the text content
|
|
645
|
-
const textContent = formattingElement.textContent;
|
|
646
|
-
|
|
647
|
-
// Create a text node to replace the formatting element
|
|
648
|
-
const textNode = document.createTextNode(textContent);
|
|
649
|
-
formattingElement.replaceWith(textNode);
|
|
650
|
-
|
|
651
|
-
// Place cursor at the end of the text
|
|
652
|
-
const range = document.createRange();
|
|
653
|
-
range.setStart(textNode, textContent.length);
|
|
654
|
-
range.setEnd(textNode, textContent.length);
|
|
655
|
-
sel.removeAllRanges();
|
|
656
|
-
sel.addRange(range);
|
|
657
|
-
|
|
658
|
-
// Update value
|
|
659
|
-
this.value = this.editorEl.innerHTML;
|
|
660
|
-
}
|
|
661
|
-
|
|
617
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
618
|
+
const { lexical, selection } = this.lx;
|
|
619
|
+
this.lexicalEditor.update(() => {
|
|
620
|
+
const sel = lexical.$getSelection();
|
|
621
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
622
|
+
sel.getNodes().forEach(node => {
|
|
623
|
+
if(lexical.$isTextNode(node)) node.setFormat(0);
|
|
624
|
+
});
|
|
625
|
+
selection.$patchStyleText(sel, { color: null, 'background-color': null });
|
|
626
|
+
}, { discrete: true });
|
|
662
627
|
return this;
|
|
663
628
|
}
|
|
664
629
|
|
|
665
630
|
formatBlock(tag) {
|
|
666
|
-
this.
|
|
631
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
632
|
+
const { lexical, richText, code } = this.lx;
|
|
633
|
+
this.lexicalEditor.update(() => {
|
|
634
|
+
const sel = lexical.$getSelection();
|
|
635
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
636
|
+
const anchor = sel.anchor.getNode();
|
|
637
|
+
const topLevel = anchor.getTopLevelElementOrThrow();
|
|
638
|
+
const isCodeNode = code.$isCodeNode(topLevel);
|
|
639
|
+
|
|
640
|
+
let newBlock;
|
|
641
|
+
if(tag === 'p'){
|
|
642
|
+
newBlock = lexical.$createParagraphNode();
|
|
643
|
+
} else if(tag.match(/^h[1-6]$/)){
|
|
644
|
+
newBlock = richText.$createHeadingNode(tag);
|
|
645
|
+
} else if(tag === 'blockquote'){
|
|
646
|
+
newBlock = richText.$createQuoteNode();
|
|
647
|
+
} else if(tag === 'pre'){
|
|
648
|
+
newBlock = code.$createCodeNode();
|
|
649
|
+
} else {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if(isCodeNode && tag !== 'pre'){
|
|
654
|
+
const text = topLevel.getTextContent();
|
|
655
|
+
topLevel.replace(newBlock);
|
|
656
|
+
newBlock.append(lexical.$createTextNode(text));
|
|
657
|
+
} else {
|
|
658
|
+
const children = topLevel.getChildren();
|
|
659
|
+
topLevel.replace(newBlock);
|
|
660
|
+
children.forEach(child => newBlock.append(child));
|
|
661
|
+
}
|
|
662
|
+
newBlock.selectEnd();
|
|
663
|
+
}, { discrete: true });
|
|
667
664
|
return this;
|
|
668
665
|
}
|
|
669
666
|
|
|
670
|
-
insertHTML(
|
|
671
|
-
this.
|
|
667
|
+
insertHTML(htmlStr) {
|
|
668
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
669
|
+
const { lexical, lexicalHtml } = this.lx;
|
|
670
|
+
this.lexicalEditor.update(() => {
|
|
671
|
+
const dom = new DOMParser().parseFromString(htmlStr, 'text/html');
|
|
672
|
+
const nodes = lexicalHtml.$generateNodesFromDOM(this.lexicalEditor, dom);
|
|
673
|
+
lexical.$insertNodes(nodes);
|
|
674
|
+
}, { discrete: true });
|
|
672
675
|
return this;
|
|
673
676
|
}
|
|
674
677
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
* @param {boolean} includeHeaders - Whether to include a header row
|
|
680
|
-
* @param {Array<Array<string>>|null} cellData - Optional 2D array of cell contents for editing existing tables
|
|
681
|
-
* @returns {HtmlEditor} Returns this for chaining
|
|
682
|
-
*/
|
|
678
|
+
insertAtCursor(htmlStr) {
|
|
679
|
+
return this.insertHTML(htmlStr);
|
|
680
|
+
}
|
|
681
|
+
|
|
683
682
|
insertTable(rows, columns, includeHeaders = false, cellData = null) {
|
|
684
|
-
if(this.mode !== 'visual') return this;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const th = document.createElement('th');
|
|
701
|
-
th.style.cssText = 'border: 1px solid var(--border-color, #ccc); padding: 0.5rem; text-align: left; background-color: var(--background-secondary, #f5f5f5);';
|
|
702
|
-
|
|
703
|
-
if(cellData && cellData[0] && cellData[0][c] !== undefined){
|
|
704
|
-
th.innerHTML = cellData[0][c];
|
|
705
|
-
} else {
|
|
706
|
-
th.innerHTML = `Header ${c + 1}`;
|
|
683
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
684
|
+
const { lexical, table } = this.lx;
|
|
685
|
+
this.lexicalEditor.update(() => {
|
|
686
|
+
const totalRows = rows + (includeHeaders ? 1 : 0);
|
|
687
|
+
const rowNodes = [];
|
|
688
|
+
for(let r = 0; r < totalRows; r++){
|
|
689
|
+
const cells = [];
|
|
690
|
+
for(let c = 0; c < columns; c++){
|
|
691
|
+
const isHeader = includeHeaders && r === 0;
|
|
692
|
+
const headerState = isHeader ? table.TableCellHeaderStates.ROW : table.TableCellHeaderStates.NO_STATUS;
|
|
693
|
+
const cell = table.$createTableCellNode(headerState);
|
|
694
|
+
const content = cellData?.[r]?.[c] ?? (isHeader ? `Header ${c + 1}` : '');
|
|
695
|
+
const p = lexical.$createParagraphNode();
|
|
696
|
+
p.append(lexical.$createTextNode(content || '\u00A0'));
|
|
697
|
+
cell.append(p);
|
|
698
|
+
cells.push(cell);
|
|
707
699
|
}
|
|
708
|
-
|
|
709
|
-
headerRow.appendChild(th);
|
|
700
|
+
rowNodes.push(table.$createTableRowNode().append(...cells));
|
|
710
701
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
for(let c = 0; c < columns; c++){
|
|
725
|
-
const td = document.createElement('td');
|
|
726
|
-
td.style.cssText = 'border: 1px solid var(--border-color, #ccc); padding: 0.5rem;';
|
|
727
|
-
|
|
728
|
-
if(cellData && cellData[r + dataStartRow] && cellData[r + dataStartRow][c] !== undefined){
|
|
729
|
-
td.innerHTML = cellData[r + dataStartRow][c];
|
|
730
|
-
} else {
|
|
731
|
-
td.innerHTML = '\u200B';
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
row.appendChild(td);
|
|
702
|
+
const tableNode = table.$createTableNode().append(...rowNodes);
|
|
703
|
+
const sel = lexical.$getSelection();
|
|
704
|
+
if(lexical.$isRangeSelection(sel)){
|
|
705
|
+
const anchor = sel.anchor.getNode();
|
|
706
|
+
const topLevel = anchor.getTopLevelElementOrThrow();
|
|
707
|
+
topLevel.insertAfter(tableNode);
|
|
708
|
+
const trailing = lexical.$createParagraphNode();
|
|
709
|
+
tableNode.insertAfter(trailing);
|
|
710
|
+
trailing.selectEnd();
|
|
711
|
+
} else {
|
|
712
|
+
const root = lexical.$getRoot();
|
|
713
|
+
root.append(tableNode);
|
|
714
|
+
root.append(lexical.$createParagraphNode());
|
|
735
715
|
}
|
|
736
|
-
|
|
737
|
-
tbody.appendChild(row);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
table.appendChild(tbody);
|
|
741
|
-
|
|
742
|
-
// Convert to HTML
|
|
743
|
-
const tempDiv = document.createElement('div');
|
|
744
|
-
tempDiv.appendChild(table);
|
|
745
|
-
const tableHTML = tempDiv.innerHTML;
|
|
746
|
-
|
|
747
|
-
const pAfter = document.createElement('p');
|
|
748
|
-
pAfter.innerHTML = '<br>';
|
|
749
|
-
const fullHTML = tableHTML + pAfter.outerHTML;
|
|
750
|
-
|
|
751
|
-
// Insert or append table
|
|
752
|
-
if(markerData.hasCursor){
|
|
753
|
-
html = html.replace(CURSOR_MARKER, fullHTML + CURSOR_MARKER);
|
|
754
|
-
} else if(markerData.hasSelection){
|
|
755
|
-
// Replace selection with table
|
|
756
|
-
html = html.replace(markerData.selectionStart, '').replace(markerData.selectionEnd, '');
|
|
757
|
-
html = html + fullHTML;
|
|
758
|
-
} else {
|
|
759
|
-
// No cursor or selection - append to end
|
|
760
|
-
html = html + fullHTML;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Restore cursor position after table
|
|
764
|
-
const markers = {
|
|
765
|
-
hasCursor: markerData.hasCursor,
|
|
766
|
-
hasSelection: false,
|
|
767
|
-
cursorMarker: CURSOR_MARKER
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
this.setValueFromSelectionMarkers(html, markers);
|
|
771
|
-
|
|
716
|
+
}, { discrete: true });
|
|
772
717
|
return this;
|
|
773
718
|
}
|
|
774
719
|
|
|
775
|
-
insertElementAtCursor(element
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const CURSOR_MARKER = '\uFFF0';
|
|
779
|
-
const SELECT_START = '\uFFF1';
|
|
780
|
-
const SELECT_END = '\uFFF2';
|
|
781
|
-
|
|
782
|
-
const markerData = this.getValueWithSelectionMarkers();
|
|
783
|
-
let html = markerData.html;
|
|
784
|
-
|
|
785
|
-
if(selectAfter){
|
|
786
|
-
element.textContent = SELECT_START + (element.textContent || '') + SELECT_END;
|
|
787
|
-
}
|
|
788
|
-
const elementHtml = element.outerHTML;
|
|
789
|
-
|
|
790
|
-
if(markerData.hasCursor){
|
|
791
|
-
html = html.replace(CURSOR_MARKER, elementHtml);
|
|
792
|
-
} else {
|
|
793
|
-
// No cursor - append at the very end of content
|
|
794
|
-
// Wrap inline elements in a paragraph
|
|
795
|
-
const inlineElements = ['code', 'a', 'span', 'strong', 'em', 'b', 'i', 'u', 's', 'mark', 'sub', 'sup'];
|
|
796
|
-
const tagName = element.tagName.toLowerCase();
|
|
797
|
-
|
|
798
|
-
if(inlineElements.includes(tagName)){
|
|
799
|
-
html = html + '<p>' + elementHtml + '</p>';
|
|
800
|
-
} else {
|
|
801
|
-
html = html + elementHtml;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
const markers = {
|
|
806
|
-
hasCursor: false,
|
|
807
|
-
hasSelection: selectAfter,
|
|
808
|
-
selectionStart: SELECT_START,
|
|
809
|
-
selectionEnd: SELECT_END,
|
|
810
|
-
selectedText: element.textContent || ''
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
this.setValueFromSelectionMarkers(html, markers);
|
|
814
|
-
|
|
815
|
-
return this;
|
|
720
|
+
insertElementAtCursor(element) {
|
|
721
|
+
return this.insertHTML(element.outerHTML);
|
|
816
722
|
}
|
|
817
723
|
|
|
818
|
-
replaceSelectionWithElement(element
|
|
819
|
-
if(this.mode !== 'visual') return this;
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if(selectAfter){
|
|
831
|
-
element.textContent = SELECT_START + element.textContent + SELECT_END;
|
|
832
|
-
}
|
|
833
|
-
const elementHtml = element.outerHTML;
|
|
834
|
-
|
|
835
|
-
const selectedPattern = SELECT_START + markerData.selectedText + SELECT_END;
|
|
836
|
-
html = html.replace(selectedPattern, elementHtml);
|
|
837
|
-
|
|
838
|
-
const markers = {
|
|
839
|
-
hasCursor: false,
|
|
840
|
-
hasSelection: selectAfter,
|
|
841
|
-
selectionStart: SELECT_START,
|
|
842
|
-
selectionEnd: SELECT_END,
|
|
843
|
-
selectedText: markerData.selectedText
|
|
844
|
-
};
|
|
845
|
-
|
|
846
|
-
this.setValueFromSelectionMarkers(html, markers);
|
|
847
|
-
|
|
724
|
+
replaceSelectionWithElement(element) {
|
|
725
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
726
|
+
const { lexical, lexicalHtml } = this.lx;
|
|
727
|
+
this.lexicalEditor.update(() => {
|
|
728
|
+
const sel = lexical.$getSelection();
|
|
729
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
730
|
+
sel.removeText();
|
|
731
|
+
const dom = new DOMParser().parseFromString(element.outerHTML, 'text/html');
|
|
732
|
+
const nodes = lexicalHtml.$generateNodesFromDOM(this.lexicalEditor, dom);
|
|
733
|
+
lexical.$insertNodes(nodes);
|
|
734
|
+
}, { discrete: true });
|
|
848
735
|
return this;
|
|
849
736
|
}
|
|
850
737
|
|
|
738
|
+
wrapSelection(before, after, savedSelection = null) {
|
|
739
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
740
|
+
const text = savedSelection || this.getSelectedText();
|
|
741
|
+
if(!text) return this;
|
|
742
|
+
return this.insertHTML(before + text + after);
|
|
743
|
+
}
|
|
744
|
+
|
|
851
745
|
/*
|
|
852
746
|
Public Methods - Selection Management
|
|
853
747
|
*/
|
|
854
748
|
getSelection() {
|
|
855
|
-
if(this.mode !== 'visual') return null;
|
|
856
|
-
|
|
857
|
-
this.
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if(!isInEditor) return null;
|
|
866
|
-
|
|
867
|
-
return {
|
|
868
|
-
text: selection.toString(),
|
|
869
|
-
html: range.cloneContents().textContent ? new XMLSerializer().serializeToString(range.cloneContents()) : '',
|
|
870
|
-
range: range,
|
|
871
|
-
selection: selection
|
|
872
|
-
};
|
|
749
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return null;
|
|
750
|
+
let result = null;
|
|
751
|
+
const { lexical } = this.lx;
|
|
752
|
+
this.lexicalEditor.getEditorState().read(() => {
|
|
753
|
+
const sel = lexical.$getSelection();
|
|
754
|
+
if(!lexical.$isRangeSelection(sel) || sel.isCollapsed()) return;
|
|
755
|
+
result = { text: sel.getTextContent(), html: sel.getTextContent(), selection: sel };
|
|
756
|
+
});
|
|
757
|
+
return result;
|
|
873
758
|
}
|
|
874
759
|
|
|
875
760
|
getSelectedText() {
|
|
876
|
-
|
|
877
|
-
|
|
761
|
+
let text = '';
|
|
762
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return text;
|
|
763
|
+
const { lexical } = this.lx;
|
|
764
|
+
this.lexicalEditor.getEditorState().read(() => {
|
|
765
|
+
const sel = lexical.$getSelection();
|
|
766
|
+
if(lexical.$isRangeSelection(sel)) text = sel.getTextContent();
|
|
767
|
+
});
|
|
768
|
+
return text;
|
|
878
769
|
}
|
|
879
770
|
|
|
880
771
|
getSelectedHTML() {
|
|
881
|
-
|
|
882
|
-
return sel ? sel.html : '';
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
setSelection(startNode, startOffset, endNode, endOffset) {
|
|
886
|
-
if(this.mode !== 'visual') return this;
|
|
887
|
-
|
|
888
|
-
this.editorEl.focus();
|
|
889
|
-
const selection = window.getSelection();
|
|
890
|
-
const range = document.createRange();
|
|
891
|
-
|
|
892
|
-
range.setStart(startNode, startOffset);
|
|
893
|
-
range.setEnd(endNode, endOffset);
|
|
894
|
-
|
|
895
|
-
selection.removeAllRanges();
|
|
896
|
-
selection.addRange(range);
|
|
897
|
-
|
|
898
|
-
return this;
|
|
772
|
+
return this.getSelectedText();
|
|
899
773
|
}
|
|
900
774
|
|
|
901
775
|
selectAll() {
|
|
902
|
-
if(this.mode === 'visual'){
|
|
903
|
-
this.
|
|
904
|
-
|
|
905
|
-
|
|
776
|
+
if(this.mode === 'visual' && this.lexicalEditor){
|
|
777
|
+
const { lexical } = this.lx;
|
|
778
|
+
this.lexicalEditor.update(() => {
|
|
779
|
+
lexical.$selectAll();
|
|
780
|
+
}, { discrete: true });
|
|
781
|
+
} else if(this.monacoEditor){
|
|
782
|
+
const model = this.monacoEditor.getModel();
|
|
783
|
+
if(model) this.monacoEditor.setSelection(model.getFullModelRange());
|
|
906
784
|
}
|
|
907
785
|
return this;
|
|
908
786
|
}
|
|
909
787
|
|
|
910
|
-
replaceSelection(
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
const sel = this.getSelection();
|
|
914
|
-
if(!sel) return this;
|
|
915
|
-
|
|
916
|
-
sel.range.deleteContents();
|
|
917
|
-
const fragment = sel.range.createContextualFragment(html);
|
|
918
|
-
sel.range.insertNode(fragment);
|
|
919
|
-
|
|
920
|
-
this.value = this.editorEl.innerHTML;
|
|
921
|
-
return this;
|
|
788
|
+
replaceSelection(htmlStr) {
|
|
789
|
+
return this.insertHTML(htmlStr);
|
|
922
790
|
}
|
|
923
791
|
|
|
924
792
|
deleteSelection() {
|
|
925
793
|
if(this.mode === 'visual'){
|
|
926
|
-
this.
|
|
927
|
-
} else {
|
|
928
|
-
|
|
929
|
-
const end = this.textareaEl.selectionEnd;
|
|
930
|
-
this.textareaEl.value = this.textareaEl.value.substring(0, start) + this.textareaEl.value.substring(end);
|
|
931
|
-
this.value = this.textareaEl.value;
|
|
794
|
+
this.lexicalCmd('DELETE_CHARACTER_COMMAND', false);
|
|
795
|
+
} else if(this.monacoEditor){
|
|
796
|
+
this.monacoEditor.trigger('keyboard', 'deleteAllLeft', null);
|
|
932
797
|
}
|
|
933
798
|
return this;
|
|
934
799
|
}
|
|
935
800
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
NodeFilter.SHOW_TEXT,
|
|
940
|
-
null
|
|
941
|
-
);
|
|
942
|
-
|
|
943
|
-
let position = 0;
|
|
944
|
-
|
|
945
|
-
while(walker.nextNode()){
|
|
946
|
-
const node = walker.currentNode;
|
|
947
|
-
|
|
948
|
-
if(targetNode.contains(node)){
|
|
949
|
-
return position;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
position += node.textContent.length;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
return position;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
wrapSelection(before, after, savedSelection = null){
|
|
959
|
-
if(this.mode !== 'visual') return this;
|
|
960
|
-
|
|
961
|
-
let selectedText;
|
|
962
|
-
|
|
963
|
-
if(savedSelection){
|
|
964
|
-
selectedText = savedSelection;
|
|
965
|
-
} else {
|
|
966
|
-
const selection = window.getSelection();
|
|
967
|
-
|
|
968
|
-
if(!selection.rangeCount || selection.isCollapsed){
|
|
969
|
-
return this;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const range = selection.getRangeAt(0);
|
|
973
|
-
const isInEditor = this.editorEl.contains(range.commonAncestorContainer);
|
|
974
|
-
|
|
975
|
-
if(!isInEditor){
|
|
976
|
-
return this;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
selectedText = selection.toString();
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
if(!selectedText) return this;
|
|
983
|
-
|
|
984
|
-
this.editorEl.focus();
|
|
985
|
-
|
|
986
|
-
const openingTagMatch = before.match(/<([a-zA-Z]+)/);
|
|
987
|
-
if(!openingTagMatch) return this;
|
|
988
|
-
const outerTagName = openingTagMatch[1];
|
|
989
|
-
|
|
990
|
-
const tempDiv = document.createElement('div');
|
|
991
|
-
tempDiv.innerHTML = this.getValue();
|
|
992
|
-
|
|
993
|
-
const textContent = tempDiv.textContent;
|
|
994
|
-
const textIndex = textContent.indexOf(selectedText);
|
|
995
|
-
|
|
996
|
-
if(textIndex === -1) return this;
|
|
997
|
-
|
|
998
|
-
const walker = document.createTreeWalker(
|
|
999
|
-
tempDiv,
|
|
1000
|
-
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
1001
|
-
null
|
|
1002
|
-
);
|
|
1003
|
-
|
|
1004
|
-
let currentPos = 0;
|
|
1005
|
-
let startNode = null;
|
|
1006
|
-
let startOffset = 0;
|
|
1007
|
-
let endNode = null;
|
|
1008
|
-
let endOffset = 0;
|
|
1009
|
-
const overlappingWrappers = [];
|
|
1010
|
-
|
|
1011
|
-
while(walker.nextNode()){
|
|
1012
|
-
const node = walker.currentNode;
|
|
1013
|
-
|
|
1014
|
-
if(node.nodeType === Node.ELEMENT_NODE){
|
|
1015
|
-
if(node.tagName.toLowerCase() === outerTagName){
|
|
1016
|
-
const nodeTextStart = this.getTextPosition(tempDiv, node);
|
|
1017
|
-
const nodeTextEnd = nodeTextStart + node.textContent.length;
|
|
1018
|
-
|
|
1019
|
-
const selectionEnd = textIndex + selectedText.length;
|
|
1020
|
-
const overlaps = !(nodeTextEnd <= textIndex || nodeTextStart >= selectionEnd);
|
|
1021
|
-
|
|
1022
|
-
if(overlaps){
|
|
1023
|
-
overlappingWrappers.push(node);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
continue;
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
const nodeLength = node.textContent.length;
|
|
1030
|
-
const nodeStart = currentPos;
|
|
1031
|
-
const nodeEnd = currentPos + nodeLength;
|
|
1032
|
-
|
|
1033
|
-
if(!startNode && nodeEnd > textIndex){
|
|
1034
|
-
startNode = node;
|
|
1035
|
-
startOffset = textIndex - nodeStart;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
if(nodeEnd >= textIndex + selectedText.length){
|
|
1039
|
-
endNode = node;
|
|
1040
|
-
endOffset = (textIndex + selectedText.length) - nodeStart;
|
|
1041
|
-
break;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
currentPos += nodeLength;
|
|
801
|
+
getValueWithSelectionMarkers() {
|
|
802
|
+
if(this.mode !== 'visual' || !this.lexicalEditor){
|
|
803
|
+
return { html: this.value, hasCursor: false, hasSelection: false, selectedText: '' };
|
|
1045
804
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
};
|
|
1057
|
-
collectTags(wrapper);
|
|
1058
|
-
|
|
1059
|
-
if(isPreTag){
|
|
1060
|
-
const p = document.createElement('p');
|
|
1061
|
-
while(wrapper.firstChild){
|
|
1062
|
-
p.appendChild(wrapper.firstChild);
|
|
1063
|
-
}
|
|
1064
|
-
parent.replaceChild(p, wrapper);
|
|
1065
|
-
} else {
|
|
1066
|
-
allTags.reverse().forEach(tag => {
|
|
1067
|
-
if(tag.parentNode){
|
|
1068
|
-
while(tag.firstChild){
|
|
1069
|
-
tag.parentNode.insertBefore(tag.firstChild, tag);
|
|
1070
|
-
}
|
|
1071
|
-
tag.parentNode.removeChild(tag);
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
});
|
|
1076
|
-
} else if(startNode && endNode){
|
|
1077
|
-
const range = document.createRange();
|
|
1078
|
-
range.setStart(startNode, startOffset);
|
|
1079
|
-
range.setEnd(endNode, endOffset);
|
|
1080
|
-
|
|
1081
|
-
const fragment = range.extractContents();
|
|
1082
|
-
|
|
1083
|
-
const wrapperDiv = document.createElement('div');
|
|
1084
|
-
wrapperDiv.innerHTML = before + after;
|
|
1085
|
-
const wrapper = wrapperDiv.firstChild;
|
|
1086
|
-
|
|
1087
|
-
let innermost = wrapper;
|
|
1088
|
-
while(innermost.firstChild && innermost.firstChild.nodeType === Node.ELEMENT_NODE){
|
|
1089
|
-
innermost = innermost.firstChild;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
innermost.appendChild(fragment);
|
|
1093
|
-
range.insertNode(wrapper);
|
|
1094
|
-
|
|
1095
|
-
if(outerTagName === 'pre'){
|
|
1096
|
-
let parent = wrapper.parentNode;
|
|
1097
|
-
while(parent && parent !== tempDiv){
|
|
1098
|
-
if(parent.tagName && parent.tagName.toLowerCase() === 'p'){
|
|
1099
|
-
const afterContent = [];
|
|
1100
|
-
let sibling = wrapper.nextSibling;
|
|
1101
|
-
while(sibling){
|
|
1102
|
-
const next = sibling.nextSibling;
|
|
1103
|
-
afterContent.push(sibling);
|
|
1104
|
-
sibling = next;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
parent.parentNode.insertBefore(wrapper, parent.nextSibling);
|
|
1108
|
-
|
|
1109
|
-
if(afterContent.length > 0 && afterContent.some(n => n.textContent.trim())){
|
|
1110
|
-
const newP = document.createElement('p');
|
|
1111
|
-
afterContent.forEach(node => newP.appendChild(node));
|
|
1112
|
-
wrapper.parentNode.insertBefore(newP, wrapper.nextSibling);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
break;
|
|
1116
|
-
}
|
|
1117
|
-
parent = parent.parentNode;
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
this.setValue(tempDiv.innerHTML);
|
|
1123
|
-
|
|
1124
|
-
this.editorEl.focus();
|
|
1125
|
-
|
|
1126
|
-
setTimeout(() => {
|
|
1127
|
-
const textContent = this.editorEl.textContent;
|
|
1128
|
-
const textIndex = textContent.indexOf(selectedText);
|
|
1129
|
-
|
|
1130
|
-
if(textIndex === -1) return;
|
|
1131
|
-
|
|
1132
|
-
const walker = document.createTreeWalker(
|
|
1133
|
-
this.editorEl,
|
|
1134
|
-
NodeFilter.SHOW_TEXT,
|
|
1135
|
-
null
|
|
1136
|
-
);
|
|
1137
|
-
|
|
1138
|
-
let currentPos = 0;
|
|
1139
|
-
let startNode = null;
|
|
1140
|
-
let startOffset = 0;
|
|
1141
|
-
let endNode = null;
|
|
1142
|
-
let endOffset = 0;
|
|
1143
|
-
|
|
1144
|
-
while(walker.nextNode()){
|
|
1145
|
-
const node = walker.currentNode;
|
|
1146
|
-
const nodeLength = node.textContent.length;
|
|
1147
|
-
const nodeStart = currentPos;
|
|
1148
|
-
const nodeEnd = currentPos + nodeLength;
|
|
1149
|
-
|
|
1150
|
-
if(startNode === null && nodeEnd > textIndex){
|
|
1151
|
-
startNode = node;
|
|
1152
|
-
startOffset = textIndex - nodeStart;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if(nodeEnd >= textIndex + selectedText.length){
|
|
1156
|
-
endNode = node;
|
|
1157
|
-
endOffset = (textIndex + selectedText.length) - nodeStart;
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
currentPos += nodeLength;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
if(startNode && endNode){
|
|
1165
|
-
const range = document.createRange();
|
|
1166
|
-
range.setStart(startNode, startOffset);
|
|
1167
|
-
range.setEnd(endNode, endOffset);
|
|
1168
|
-
|
|
1169
|
-
const selection = window.getSelection();
|
|
1170
|
-
selection.removeAllRanges();
|
|
1171
|
-
selection.addRange(range);
|
|
805
|
+
let result = { html: this.exportHtmlFromLexical(), hasCursor: false, hasSelection: false, selectedText: '' };
|
|
806
|
+
const { lexical } = this.lx;
|
|
807
|
+
this.lexicalEditor.getEditorState().read(() => {
|
|
808
|
+
const sel = lexical.$getSelection();
|
|
809
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
810
|
+
if(sel.isCollapsed()){
|
|
811
|
+
result.hasCursor = true;
|
|
812
|
+
} else {
|
|
813
|
+
result.hasSelection = true;
|
|
814
|
+
result.selectedText = sel.getTextContent();
|
|
1172
815
|
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
return this;
|
|
816
|
+
});
|
|
817
|
+
return result;
|
|
1176
818
|
}
|
|
1177
819
|
|
|
1178
|
-
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
820
|
+
setValueFromSelectionMarkers(htmlStr) {
|
|
821
|
+
return this.setValue(htmlStr);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
captureSelection() {
|
|
825
|
+
return this.selection;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
restoreSavedSelection() {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
clearSavedSelection() {
|
|
833
|
+
this.savedSelection = null;
|
|
1181
834
|
}
|
|
1182
835
|
|
|
1183
836
|
/*
|
|
1184
837
|
Public Methods - Links and Media
|
|
1185
838
|
*/
|
|
1186
839
|
createLink(url) {
|
|
1187
|
-
this.
|
|
840
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
841
|
+
const { link } = this.lx;
|
|
842
|
+
this.lexicalEditor.update(() => {
|
|
843
|
+
link.$toggleLink(url);
|
|
844
|
+
}, { discrete: true });
|
|
845
|
+
return this;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
createLinkWithText(url, text) {
|
|
849
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
850
|
+
const { lexical, link } = this.lx;
|
|
851
|
+
this.lexicalEditor.update(() => {
|
|
852
|
+
const sel = lexical.$getSelection();
|
|
853
|
+
if(lexical.$isRangeSelection(sel) && !sel.isCollapsed()) sel.removeText();
|
|
854
|
+
const linkNode = link.$createLinkNode(url);
|
|
855
|
+
linkNode.append(lexical.$createTextNode(text));
|
|
856
|
+
lexical.$insertNodes([linkNode]);
|
|
857
|
+
}, { discrete: true });
|
|
1188
858
|
return this;
|
|
1189
859
|
}
|
|
1190
860
|
|
|
1191
861
|
unlink() {
|
|
1192
|
-
this.
|
|
862
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return this;
|
|
863
|
+
const { lexical, link } = this.lx;
|
|
864
|
+
this.lexicalEditor.update(() => {
|
|
865
|
+
const sel = lexical.$getSelection();
|
|
866
|
+
if(!lexical.$isRangeSelection(sel)) return;
|
|
867
|
+
sel.getNodes().forEach(node => {
|
|
868
|
+
const parent = node.getParent();
|
|
869
|
+
if(parent && link.$isLinkNode?.(parent)){
|
|
870
|
+
parent.getChildren().forEach(child => parent.insertBefore(child));
|
|
871
|
+
parent.remove();
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
}, { discrete: true });
|
|
1193
875
|
return this;
|
|
1194
876
|
}
|
|
1195
877
|
|
|
1196
878
|
insertImage(url) {
|
|
1197
|
-
this.
|
|
1198
|
-
return this;
|
|
879
|
+
return this.insertHTML(`<img src="${encodeURI(url)}" />`);
|
|
1199
880
|
}
|
|
1200
881
|
|
|
1201
882
|
/*
|
|
1202
883
|
Public Methods - History
|
|
1203
884
|
*/
|
|
1204
885
|
undo() {
|
|
1205
|
-
this.
|
|
886
|
+
this.lexicalCmd('UNDO_COMMAND', undefined);
|
|
1206
887
|
return this;
|
|
1207
888
|
}
|
|
1208
889
|
|
|
1209
890
|
redo() {
|
|
1210
|
-
this.
|
|
891
|
+
this.lexicalCmd('REDO_COMMAND', undefined);
|
|
1211
892
|
return this;
|
|
1212
893
|
}
|
|
1213
894
|
|
|
1214
895
|
/*
|
|
1215
896
|
Utility Methods
|
|
1216
897
|
*/
|
|
1217
|
-
|
|
1218
|
-
if(this.mode !== 'visual') return;
|
|
1219
|
-
|
|
1220
|
-
this.
|
|
1221
|
-
document.execCommand(command, false, value);
|
|
1222
|
-
this.value = this.editorEl.innerHTML;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
captureSelection(){
|
|
1226
|
-
if(this.mode !== 'visual') return null;
|
|
1227
|
-
|
|
1228
|
-
const selection = window.getSelection();
|
|
1229
|
-
|
|
1230
|
-
if(!selection.rangeCount){
|
|
1231
|
-
return null;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const range = selection.getRangeAt(0);
|
|
1235
|
-
const isInEditor = this.editorEl.contains(range.commonAncestorContainer);
|
|
1236
|
-
|
|
1237
|
-
if(!isInEditor){
|
|
1238
|
-
return null;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
const selectedText = range.toString();
|
|
1242
|
-
|
|
1243
|
-
if(range.collapsed){
|
|
1244
|
-
return null;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
return {
|
|
1248
|
-
startContainer: range.startContainer,
|
|
1249
|
-
startOffset: range.startOffset,
|
|
1250
|
-
endContainer: range.endContainer,
|
|
1251
|
-
endOffset: range.endOffset,
|
|
1252
|
-
text: selectedText,
|
|
1253
|
-
collapsed: range.collapsed
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
restoreSavedSelection(){
|
|
1258
|
-
if(!this.savedSelection) return false;
|
|
1259
|
-
|
|
1260
|
-
const selection = this.shadowRoot.getSelection() || window.getSelection();
|
|
1261
|
-
const range = document.createRange();
|
|
1262
|
-
|
|
1263
|
-
try {
|
|
1264
|
-
range.setStart(this.savedSelection.startContainer, this.savedSelection.startOffset);
|
|
1265
|
-
range.setEnd(this.savedSelection.endContainer, this.savedSelection.endOffset);
|
|
1266
|
-
selection.removeAllRanges();
|
|
1267
|
-
selection.addRange(range);
|
|
1268
|
-
return true;
|
|
1269
|
-
} catch(e) {
|
|
1270
|
-
this.savedSelection = null;
|
|
1271
|
-
return false;
|
|
1272
|
-
}
|
|
898
|
+
lexicalCmd(commandName, payload) {
|
|
899
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return;
|
|
900
|
+
const cmd = this.lx.lexical?.[commandName];
|
|
901
|
+
if(cmd) this.lexicalEditor.dispatchCommand(cmd, payload);
|
|
1273
902
|
}
|
|
1274
903
|
|
|
1275
|
-
|
|
1276
|
-
this.
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const CURSOR_MARKER = '\uFFF0';
|
|
1283
|
-
const SELECTION_START = '\uFFF1';
|
|
1284
|
-
const SELECTION_END = '\uFFF2';
|
|
1285
|
-
|
|
1286
|
-
const selection = this.shadowRoot.getSelection() || window.getSelection();
|
|
1287
|
-
|
|
1288
|
-
// If no current selection, try to restore saved selection
|
|
1289
|
-
if(!selection.rangeCount){
|
|
1290
|
-
if(!this.restoreSavedSelection()){
|
|
1291
|
-
return { html: this.editorEl.innerHTML, hasCursor: false, hasSelection: false };
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
if(!selection.rangeCount){
|
|
1296
|
-
return { html: this.editorEl.innerHTML, hasCursor: false, hasSelection: false };
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
const range = selection.getRangeAt(0);
|
|
1300
|
-
const isInEditor = this.editorEl.contains(range.commonAncestorContainer);
|
|
1301
|
-
|
|
1302
|
-
if(!isInEditor){
|
|
1303
|
-
return { html: this.editorEl.innerHTML, hasCursor: false, hasSelection: false };
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
const selectedText = range.toString();
|
|
1307
|
-
|
|
1308
|
-
if(range.collapsed){
|
|
1309
|
-
const cursorMarker = document.createTextNode(CURSOR_MARKER);
|
|
1310
|
-
const tempRange = document.createRange();
|
|
1311
|
-
tempRange.setStart(range.startContainer, range.startOffset);
|
|
1312
|
-
tempRange.insertNode(cursorMarker);
|
|
1313
|
-
|
|
1314
|
-
const htmlWithMarker = this.editorEl.innerHTML;
|
|
1315
|
-
cursorMarker.remove();
|
|
1316
|
-
|
|
1317
|
-
return {
|
|
1318
|
-
html: htmlWithMarker,
|
|
1319
|
-
hasCursor: true,
|
|
1320
|
-
hasSelection: false,
|
|
1321
|
-
cursorMarker: CURSOR_MARKER,
|
|
1322
|
-
selectedText: ''
|
|
1323
|
-
};
|
|
1324
|
-
} else {
|
|
1325
|
-
const startMarker = document.createTextNode(SELECTION_START);
|
|
1326
|
-
const endMarker = document.createTextNode(SELECTION_END);
|
|
1327
|
-
|
|
1328
|
-
const tempRange = document.createRange();
|
|
1329
|
-
tempRange.setStart(range.endContainer, range.endOffset);
|
|
1330
|
-
tempRange.insertNode(endMarker);
|
|
1331
|
-
|
|
1332
|
-
tempRange.setStart(range.startContainer, range.startOffset);
|
|
1333
|
-
tempRange.insertNode(startMarker);
|
|
1334
|
-
|
|
1335
|
-
const htmlWithMarkers = this.editorEl.innerHTML;
|
|
1336
|
-
|
|
1337
|
-
startMarker.remove();
|
|
1338
|
-
endMarker.remove();
|
|
1339
|
-
|
|
1340
|
-
return {
|
|
1341
|
-
html: htmlWithMarkers,
|
|
1342
|
-
hasCursor: false,
|
|
1343
|
-
hasSelection: true,
|
|
1344
|
-
selectionStart: SELECTION_START,
|
|
1345
|
-
selectionEnd: SELECTION_END,
|
|
1346
|
-
selectedText: selectedText
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
setValueFromSelectionMarkers(html, markers){
|
|
1352
|
-
if(this.mode !== 'visual') return this;
|
|
1353
|
-
|
|
1354
|
-
const CURSOR_MARKER = markers.cursorMarker || '\uFFF0';
|
|
1355
|
-
const SELECTION_START = markers.selectionStart || '\uFFF1';
|
|
1356
|
-
const SELECTION_END = markers.selectionEnd || '\uFFF2';
|
|
1357
|
-
|
|
1358
|
-
this.editorEl.innerHTML = html;
|
|
1359
|
-
|
|
1360
|
-
if(markers.hasCursor){
|
|
1361
|
-
const walker = document.createTreeWalker(
|
|
1362
|
-
this.editorEl,
|
|
1363
|
-
NodeFilter.SHOW_TEXT,
|
|
1364
|
-
null
|
|
1365
|
-
);
|
|
1366
|
-
|
|
1367
|
-
while(walker.nextNode()){
|
|
1368
|
-
const node = walker.currentNode;
|
|
1369
|
-
const index = node.textContent.indexOf(CURSOR_MARKER);
|
|
1370
|
-
|
|
1371
|
-
if(index !== -1){
|
|
1372
|
-
node.textContent = node.textContent.replace(CURSOR_MARKER, '');
|
|
1373
|
-
|
|
1374
|
-
const range = document.createRange();
|
|
1375
|
-
range.setStart(node, index);
|
|
1376
|
-
range.setEnd(node, index);
|
|
1377
|
-
|
|
1378
|
-
const selection = this.shadowRoot.getSelection() || window.getSelection();
|
|
1379
|
-
selection.removeAllRanges();
|
|
1380
|
-
selection.addRange(range);
|
|
1381
|
-
|
|
1382
|
-
break;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
} else if(markers.hasSelection){
|
|
1386
|
-
const walker = document.createTreeWalker(
|
|
1387
|
-
this.editorEl,
|
|
1388
|
-
NodeFilter.SHOW_TEXT,
|
|
1389
|
-
null
|
|
1390
|
-
);
|
|
1391
|
-
|
|
1392
|
-
let startNode = null;
|
|
1393
|
-
let startOffset = 0;
|
|
1394
|
-
let endNode = null;
|
|
1395
|
-
let endOffset = 0;
|
|
1396
|
-
|
|
1397
|
-
while(walker.nextNode()){
|
|
1398
|
-
const node = walker.currentNode;
|
|
1399
|
-
|
|
1400
|
-
const startIndex = node.textContent.indexOf(SELECTION_START);
|
|
1401
|
-
if(startIndex !== -1){
|
|
1402
|
-
startNode = node;
|
|
1403
|
-
startOffset = startIndex;
|
|
1404
|
-
node.textContent = node.textContent.replace(SELECTION_START, '');
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
const endIndex = node.textContent.indexOf(SELECTION_END);
|
|
1408
|
-
if(endIndex !== -1){
|
|
1409
|
-
endNode = node;
|
|
1410
|
-
endOffset = endIndex;
|
|
1411
|
-
node.textContent = node.textContent.replace(SELECTION_END, '');
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
if(startNode && endNode) break;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
if(startNode && endNode){
|
|
1418
|
-
const range = document.createRange();
|
|
1419
|
-
range.setStart(startNode, startOffset);
|
|
1420
|
-
range.setEnd(endNode, endOffset);
|
|
1421
|
-
|
|
1422
|
-
const selection = this.shadowRoot.getSelection() || window.getSelection();
|
|
1423
|
-
selection.removeAllRanges();
|
|
1424
|
-
selection.addRange(range);
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
this.editorEl.focus();
|
|
1429
|
-
|
|
1430
|
-
return this;
|
|
904
|
+
lexicalFormat(format) {
|
|
905
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return;
|
|
906
|
+
const { lexical } = this.lx;
|
|
907
|
+
this.lexicalEditor.update(() => {
|
|
908
|
+
const sel = lexical.$getSelection();
|
|
909
|
+
if(lexical.$isRangeSelection(sel)) sel.formatText(format);
|
|
910
|
+
}, { discrete: true });
|
|
1431
911
|
}
|
|
1432
912
|
|
|
1433
|
-
|
|
1434
|
-
if(
|
|
1435
|
-
|
|
1436
|
-
if(this.mode === 'visual'){
|
|
1437
|
-
if(this.editorEl.innerHTML !== this.value){
|
|
1438
|
-
this.editorEl.innerHTML = this.value;
|
|
1439
|
-
}
|
|
1440
|
-
} else {
|
|
1441
|
-
const formattedValue = formatCode(this.value);
|
|
1442
|
-
if(this.textareaEl.value !== formattedValue){
|
|
1443
|
-
this.textareaEl.value = formattedValue;
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
913
|
+
lexicalFormatElement(alignment) {
|
|
914
|
+
if(this.mode !== 'visual' || !this.lexicalEditor) return;
|
|
915
|
+
this.lexicalEditor.dispatchCommand(this.lx.lexical.FORMAT_ELEMENT_COMMAND, alignment);
|
|
1446
916
|
}
|
|
1447
917
|
|
|
1448
918
|
/*
|
|
@@ -1455,7 +925,6 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
1455
925
|
gap: 0;
|
|
1456
926
|
height: 300px;
|
|
1457
927
|
}
|
|
1458
|
-
|
|
1459
928
|
.toolbar-top,
|
|
1460
929
|
.toolbar-bottom {
|
|
1461
930
|
display: flex;
|
|
@@ -1465,18 +934,20 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
1465
934
|
background: var(--bg-secondary);
|
|
1466
935
|
min-height: 40px;
|
|
1467
936
|
}
|
|
1468
|
-
|
|
1469
937
|
.editor-container {
|
|
1470
938
|
position: relative;
|
|
1471
939
|
flex: 1;
|
|
1472
940
|
min-height: 0;
|
|
1473
941
|
overflow: hidden;
|
|
1474
942
|
}
|
|
1475
|
-
|
|
1476
|
-
.editor
|
|
1477
|
-
textarea {
|
|
943
|
+
.lexical-editor,
|
|
944
|
+
.monaco-editor-container {
|
|
1478
945
|
width: 100%;
|
|
1479
946
|
height: 100%;
|
|
947
|
+
box-sizing: border-box;
|
|
948
|
+
overflow: auto;
|
|
949
|
+
}
|
|
950
|
+
.lexical-editor {
|
|
1480
951
|
padding: 1rem;
|
|
1481
952
|
border: 1px solid var(--border-color);
|
|
1482
953
|
background: var(--bg-primary);
|
|
@@ -1484,31 +955,59 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
1484
955
|
font-family: inherit;
|
|
1485
956
|
font-size: inherit;
|
|
1486
957
|
line-height: 1.5;
|
|
1487
|
-
overflow: auto;
|
|
1488
|
-
box-sizing: border-box;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
.editor {
|
|
1492
958
|
outline: none;
|
|
1493
959
|
}
|
|
1494
|
-
|
|
1495
|
-
.editor:focus {
|
|
960
|
+
.lexical-editor:focus {
|
|
1496
961
|
border-color: var(--primary-color);
|
|
1497
962
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
963
|
+
.lexical-editor p { margin: 0 0 0.5em 0; }
|
|
964
|
+
.lexical-editor h1,
|
|
965
|
+
.lexical-editor h2,
|
|
966
|
+
.lexical-editor h3,
|
|
967
|
+
.lexical-editor h4,
|
|
968
|
+
.lexical-editor h5,
|
|
969
|
+
.lexical-editor h6 { margin: 0.5em 0; }
|
|
970
|
+
.lexical-editor ul,
|
|
971
|
+
.lexical-editor ol { margin: 0.5em 0; padding-left: 1.5em; }
|
|
972
|
+
.lexical-editor blockquote {
|
|
973
|
+
margin: 0.5em 0;
|
|
974
|
+
padding-left: 1em;
|
|
975
|
+
border-left: 3px solid var(--border-color, #ccc);
|
|
976
|
+
color: var(--text-secondary, #666);
|
|
977
|
+
}
|
|
978
|
+
.lexical-editor a { color: var(--primary-color, #007acc); }
|
|
979
|
+
.lexical-editor code {
|
|
980
|
+
background: var(--bg-secondary, #f5f5f5);
|
|
981
|
+
padding: 0.125em 0.25em;
|
|
982
|
+
border-radius: 3px;
|
|
1501
983
|
font-family: monospace;
|
|
1502
984
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
border-
|
|
985
|
+
.lexical-editor pre {
|
|
986
|
+
background: var(--bg-secondary, #f5f5f5);
|
|
987
|
+
padding: 1em;
|
|
988
|
+
border-radius: 4px;
|
|
989
|
+
font-family: monospace;
|
|
990
|
+
overflow-x: auto;
|
|
991
|
+
white-space: pre-wrap;
|
|
1507
992
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
993
|
+
.lexical-editor table {
|
|
994
|
+
border-collapse: collapse;
|
|
995
|
+
width: 100%;
|
|
996
|
+
}
|
|
997
|
+
.lexical-editor th,
|
|
998
|
+
.lexical-editor td {
|
|
999
|
+
border: 1px solid var(--border-color, #ccc);
|
|
1000
|
+
padding: 0.5rem;
|
|
1001
|
+
}
|
|
1002
|
+
.lexical-editor th {
|
|
1003
|
+
background-color: var(--bg-secondary, #f5f5f5);
|
|
1004
|
+
text-align: left;
|
|
1005
|
+
}
|
|
1006
|
+
.monaco-editor-container {
|
|
1007
|
+
border: 1px solid var(--border-color);
|
|
1008
|
+
}
|
|
1009
|
+
[hidden] {
|
|
1010
|
+
display: none !important;
|
|
1512
1011
|
}
|
|
1513
1012
|
`;
|
|
1514
1013
|
|
|
@@ -1519,23 +1018,17 @@ export default class HtmlEditor extends ShadowComponent {
|
|
|
1519
1018
|
<slot name="toolbar-top"></slot>
|
|
1520
1019
|
</div>
|
|
1521
1020
|
` : ''}
|
|
1522
|
-
|
|
1523
1021
|
<div class="editor-container">
|
|
1524
|
-
<div
|
|
1525
|
-
class="editor"
|
|
1022
|
+
<div
|
|
1023
|
+
class="lexical-editor"
|
|
1526
1024
|
contenteditable="true"
|
|
1527
1025
|
?hidden=${this.mode !== 'visual'}
|
|
1528
|
-
@input=${this.handleEditorInput}
|
|
1529
|
-
@paste=${this.handleEditorPaste}
|
|
1530
|
-
@blur=${this.handleEditorBlur}
|
|
1531
1026
|
></div>
|
|
1532
|
-
|
|
1533
|
-
|
|
1027
|
+
<div
|
|
1028
|
+
class="monaco-editor-container"
|
|
1534
1029
|
?hidden=${this.mode !== 'code'}
|
|
1535
|
-
|
|
1536
|
-
></textarea>
|
|
1030
|
+
></div>
|
|
1537
1031
|
</div>
|
|
1538
|
-
|
|
1539
1032
|
${this.hasBottomToolbar ? html`
|
|
1540
1033
|
<div class="toolbar-bottom bt">
|
|
1541
1034
|
<slot name="toolbar-bottom"></slot>
|