mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Started on form UI
This commit is contained in:
parent
57259aee00
commit
ae98745439
@ -3,7 +3,6 @@ import {
|
|||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$isParagraphNode,
|
$isParagraphNode,
|
||||||
BaseSelection, FORMAT_TEXT_COMMAND,
|
BaseSelection, FORMAT_TEXT_COMMAND,
|
||||||
LexicalEditor,
|
|
||||||
LexicalNode,
|
LexicalNode,
|
||||||
REDO_COMMAND, TextFormatType,
|
REDO_COMMAND, TextFormatType,
|
||||||
UNDO_COMMAND
|
UNDO_COMMAND
|
||||||
@ -19,11 +18,12 @@ import {
|
|||||||
HeadingTagType
|
HeadingTagType
|
||||||
} from "@lexical/rich-text";
|
} from "@lexical/rich-text";
|
||||||
import {$isLinkNode, $toggleLink} from "@lexical/link";
|
import {$isLinkNode, $toggleLink} from "@lexical/link";
|
||||||
|
import {EditorUiContext} from "../framework/core";
|
||||||
|
|
||||||
export const undo: EditorButtonDefinition = {
|
export const undo: EditorButtonDefinition = {
|
||||||
label: 'Undo',
|
label: 'Undo',
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
editor.dispatchCommand(UNDO_COMMAND, undefined);
|
context.editor.dispatchCommand(UNDO_COMMAND, undefined);
|
||||||
},
|
},
|
||||||
isActive(selection: BaseSelection|null): boolean {
|
isActive(selection: BaseSelection|null): boolean {
|
||||||
return false;
|
return false;
|
||||||
@ -32,8 +32,8 @@ export const undo: EditorButtonDefinition = {
|
|||||||
|
|
||||||
export const redo: EditorButtonDefinition = {
|
export const redo: EditorButtonDefinition = {
|
||||||
label: 'Redo',
|
label: 'Redo',
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
editor.dispatchCommand(REDO_COMMAND, undefined);
|
context.editor.dispatchCommand(REDO_COMMAND, undefined);
|
||||||
},
|
},
|
||||||
isActive(selection: BaseSelection|null): boolean {
|
isActive(selection: BaseSelection|null): boolean {
|
||||||
return false;
|
return false;
|
||||||
@ -43,9 +43,9 @@ export const redo: EditorButtonDefinition = {
|
|||||||
function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
|
function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
|
||||||
return {
|
return {
|
||||||
label: `${name} Callout`,
|
label: `${name} Callout`,
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
toggleSelectionBlockNodeType(
|
toggleSelectionBlockNodeType(
|
||||||
editor,
|
context.editor,
|
||||||
(node) => $isCalloutNodeOfCategory(node, category),
|
(node) => $isCalloutNodeOfCategory(node, category),
|
||||||
() => $createCalloutNode(category),
|
() => $createCalloutNode(category),
|
||||||
)
|
)
|
||||||
@ -68,9 +68,9 @@ const isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTag
|
|||||||
function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
|
function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
|
||||||
return {
|
return {
|
||||||
label: name,
|
label: name,
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
toggleSelectionBlockNodeType(
|
toggleSelectionBlockNodeType(
|
||||||
editor,
|
context.editor,
|
||||||
(node) => isHeaderNodeOfTag(node, tag),
|
(node) => isHeaderNodeOfTag(node, tag),
|
||||||
() => $createHeadingNode(tag),
|
() => $createHeadingNode(tag),
|
||||||
)
|
)
|
||||||
@ -88,8 +88,8 @@ export const h5: EditorButtonDefinition = buildHeaderButton('h5', 'Tiny Header')
|
|||||||
|
|
||||||
export const blockquote: EditorButtonDefinition = {
|
export const blockquote: EditorButtonDefinition = {
|
||||||
label: 'Blockquote',
|
label: 'Blockquote',
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
toggleSelectionBlockNodeType(editor, $isQuoteNode, $createQuoteNode);
|
toggleSelectionBlockNodeType(context.editor, $isQuoteNode, $createQuoteNode);
|
||||||
},
|
},
|
||||||
isActive(selection: BaseSelection|null): boolean {
|
isActive(selection: BaseSelection|null): boolean {
|
||||||
return selectionContainsNodeType(selection, $isQuoteNode);
|
return selectionContainsNodeType(selection, $isQuoteNode);
|
||||||
@ -98,8 +98,8 @@ export const blockquote: EditorButtonDefinition = {
|
|||||||
|
|
||||||
export const paragraph: EditorButtonDefinition = {
|
export const paragraph: EditorButtonDefinition = {
|
||||||
label: 'Paragraph',
|
label: 'Paragraph',
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
toggleSelectionBlockNodeType(editor, $isParagraphNode, $createParagraphNode);
|
toggleSelectionBlockNodeType(context.editor, $isParagraphNode, $createParagraphNode);
|
||||||
},
|
},
|
||||||
isActive(selection: BaseSelection|null): boolean {
|
isActive(selection: BaseSelection|null): boolean {
|
||||||
return selectionContainsNodeType(selection, $isParagraphNode);
|
return selectionContainsNodeType(selection, $isParagraphNode);
|
||||||
@ -109,8 +109,8 @@ export const paragraph: EditorButtonDefinition = {
|
|||||||
function buildFormatButton(label: string, format: TextFormatType): EditorButtonDefinition {
|
function buildFormatButton(label: string, format: TextFormatType): EditorButtonDefinition {
|
||||||
return {
|
return {
|
||||||
label: label,
|
label: label,
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
|
context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
|
||||||
},
|
},
|
||||||
isActive(selection: BaseSelection|null): boolean {
|
isActive(selection: BaseSelection|null): boolean {
|
||||||
return selectionContainsTextFormat(selection, format);
|
return selectionContainsTextFormat(selection, format);
|
||||||
@ -132,8 +132,8 @@ export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'co
|
|||||||
|
|
||||||
export const link: EditorButtonDefinition = {
|
export const link: EditorButtonDefinition = {
|
||||||
label: 'Insert/edit link',
|
label: 'Insert/edit link',
|
||||||
action(editor: LexicalEditor) {
|
action(context: EditorUiContext) {
|
||||||
editor.update(() => {
|
context.editor.update(() => {
|
||||||
$toggleLink('http://example.com');
|
$toggleLink('http://example.com');
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
43
resources/js/wysiwyg/ui/defaults/form-definitions.ts
Normal file
43
resources/js/wysiwyg/ui/defaults/form-definitions.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import {EditorFormDefinition, EditorFormFieldDefinition, EditorSelectFormFieldDefinition} from "../framework/forms";
|
||||||
|
import {EditorUiContext} from "../framework/core";
|
||||||
|
|
||||||
|
|
||||||
|
export const link: EditorFormDefinition = {
|
||||||
|
submitText: 'Apply',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
action(formData, context: EditorUiContext) {
|
||||||
|
// Todo
|
||||||
|
console.log('link-form-action', formData);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
// Todo
|
||||||
|
console.log('link-form-cancel');
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Text to display',
|
||||||
|
name: 'text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open link in...',
|
||||||
|
name: 'target',
|
||||||
|
type: 'select',
|
||||||
|
valuesByLabel: {
|
||||||
|
'Current window': '',
|
||||||
|
'New window': '_blank',
|
||||||
|
}
|
||||||
|
} as EditorSelectFormFieldDefinition,
|
||||||
|
],
|
||||||
|
};
|
@ -1,15 +1,16 @@
|
|||||||
import {BaseSelection, LexicalEditor} from "lexical";
|
import {BaseSelection} from "lexical";
|
||||||
import {EditorUiElement, EditorUiStateUpdate} from "./base-elements";
|
import {EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core";
|
||||||
import {el} from "../../helpers";
|
import {el} from "../../helpers";
|
||||||
|
|
||||||
export interface EditorButtonDefinition {
|
export interface EditorButtonDefinition {
|
||||||
label: string;
|
label: string;
|
||||||
action: (editor: LexicalEditor) => void;
|
action: (context: EditorUiContext) => void;
|
||||||
isActive: (selection: BaseSelection|null) => boolean;
|
isActive: (selection: BaseSelection|null) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditorButton extends EditorUiElement {
|
export class EditorButton extends EditorUiElement {
|
||||||
protected definition: EditorButtonDefinition;
|
protected definition: EditorButtonDefinition;
|
||||||
|
protected active: boolean = false;
|
||||||
|
|
||||||
constructor(definition: EditorButtonDefinition) {
|
constructor(definition: EditorButtonDefinition) {
|
||||||
super();
|
super();
|
||||||
@ -20,7 +21,7 @@ export class EditorButton extends EditorUiElement {
|
|||||||
const button = el('button', {
|
const button = el('button', {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: 'editor-button',
|
class: 'editor-button',
|
||||||
}, [this.definition.label]) as HTMLButtonElement;
|
}, [this.getLabel()]) as HTMLButtonElement;
|
||||||
|
|
||||||
button.addEventListener('click', this.onClick.bind(this));
|
button.addEventListener('click', this.onClick.bind(this));
|
||||||
|
|
||||||
@ -28,17 +29,25 @@ export class EditorButton extends EditorUiElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected onClick() {
|
protected onClick() {
|
||||||
this.definition.action(this.getContext().editor);
|
this.definition.action(this.getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateActiveState(selection: BaseSelection|null) {
|
updateActiveState(selection: BaseSelection|null) {
|
||||||
const isActive = this.definition.isActive(selection);
|
this.active = this.definition.isActive(selection);
|
||||||
this.dom?.classList.toggle('editor-button-active', isActive);
|
this.dom?.classList.toggle('editor-button-active', this.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState(state: EditorUiStateUpdate): void {
|
updateState(state: EditorUiStateUpdate): void {
|
||||||
this.updateActiveState(state.selection);
|
this.updateActiveState(state.selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isActive(): boolean {
|
||||||
|
return this.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLabel(): string {
|
||||||
|
return this.trans(this.definition.label);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormatPreviewButton extends EditorButton {
|
export class FormatPreviewButton extends EditorButton {
|
||||||
@ -55,7 +64,7 @@ export class FormatPreviewButton extends EditorButton {
|
|||||||
|
|
||||||
const preview = el('span', {
|
const preview = el('span', {
|
||||||
class: 'editor-button-format-preview'
|
class: 'editor-button-format-preview'
|
||||||
}, [this.definition.label]);
|
}, [this.getLabel()]);
|
||||||
|
|
||||||
const stylesToApply = this.getStylesFromPreview();
|
const stylesToApply = this.getStylesFromPreview();
|
||||||
console.log(stylesToApply);
|
console.log(stylesToApply);
|
||||||
@ -70,7 +79,7 @@ export class FormatPreviewButton extends EditorButton {
|
|||||||
protected getStylesFromPreview(): Record<string, string> {
|
protected getStylesFromPreview(): Record<string, string> {
|
||||||
const wrap = el('div', {style: 'display: none', hidden: 'true', class: 'page-content'});
|
const wrap = el('div', {style: 'display: none', hidden: 'true', class: 'page-content'});
|
||||||
const sampleClone = this.previewSampleElement.cloneNode() as HTMLElement;
|
const sampleClone = this.previewSampleElement.cloneNode() as HTMLElement;
|
||||||
sampleClone.textContent = this.definition.label;
|
sampleClone.textContent = this.getLabel();
|
||||||
wrap.append(sampleClone);
|
wrap.append(sampleClone);
|
||||||
document.body.append(wrap);
|
document.body.append(wrap);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./base-elements";
|
import {EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core";
|
||||||
import {el} from "../../helpers";
|
import {el} from "../../helpers";
|
||||||
|
import {EditorButton} from "./buttons";
|
||||||
|
|
||||||
export class EditorContainerUiElement extends EditorUiElement {
|
export class EditorContainerUiElement extends EditorUiElement {
|
||||||
protected children : EditorUiElement[];
|
protected children : EditorUiElement[];
|
||||||
@ -24,6 +25,7 @@ export class EditorContainerUiElement extends EditorUiElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContext(context: EditorUiContext) {
|
setContext(context: EditorUiContext) {
|
||||||
|
super.setContext(context);
|
||||||
for (const child of this.getChildren()) {
|
for (const child of this.getChildren()) {
|
||||||
child.setContext(context);
|
child.setContext(context);
|
||||||
}
|
}
|
||||||
@ -54,9 +56,9 @@ export class EditorFormatMenu extends EditorContainerUiElement {
|
|||||||
}, childElements);
|
}, childElements);
|
||||||
|
|
||||||
const toggle = el('button', {
|
const toggle = el('button', {
|
||||||
class: 'editor-format-menu-toggle',
|
class: 'editor-format-menu-toggle editor-button',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
}, ['Formats']);
|
}, [this.trans('Formats')]);
|
||||||
|
|
||||||
const wrapper = el('div', {
|
const wrapper = el('div', {
|
||||||
class: 'editor-format-menu editor-dropdown-menu-container',
|
class: 'editor-format-menu editor-dropdown-menu-container',
|
||||||
@ -88,4 +90,24 @@ export class EditorFormatMenu extends EditorContainerUiElement {
|
|||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateState(state: EditorUiStateUpdate) {
|
||||||
|
super.updateState(state);
|
||||||
|
|
||||||
|
for (const child of this.children) {
|
||||||
|
if (child instanceof EditorButton && child.isActive()) {
|
||||||
|
this.updateToggleLabel(child.getLabel());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateToggleLabel(this.trans('Formats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateToggleLabel(text: string): void {
|
||||||
|
const button = this.getDOMElement().querySelector('button');
|
||||||
|
if (button) {
|
||||||
|
button.innerText = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import {BaseSelection, LexicalEditor} from "lexical";
|
import {BaseSelection, LexicalEditor} from "lexical";
|
||||||
|
import {EditorUIManager} from "./manager";
|
||||||
|
|
||||||
export type EditorUiStateUpdate = {
|
export type EditorUiStateUpdate = {
|
||||||
editor: LexicalEditor,
|
editor: LexicalEditor,
|
||||||
@ -7,6 +8,8 @@ export type EditorUiStateUpdate = {
|
|||||||
|
|
||||||
export type EditorUiContext = {
|
export type EditorUiContext = {
|
||||||
editor: LexicalEditor,
|
editor: LexicalEditor,
|
||||||
|
translate: (text: string) => string,
|
||||||
|
manager: EditorUIManager,
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class EditorUiElement {
|
export abstract class EditorUiElement {
|
||||||
@ -35,5 +38,11 @@ export abstract class EditorUiElement {
|
|||||||
return this.dom;
|
return this.dom;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract updateState(state: EditorUiStateUpdate): void;
|
trans(text: string) {
|
||||||
|
return this.getContext().translate(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState(state: EditorUiStateUpdate): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
82
resources/js/wysiwyg/ui/framework/forms.ts
Normal file
82
resources/js/wysiwyg/ui/framework/forms.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import {EditorUiContext, EditorUiElement} from "./core";
|
||||||
|
import {EditorContainerUiElement} from "./containers";
|
||||||
|
import {el} from "../../helpers";
|
||||||
|
|
||||||
|
export interface EditorFormFieldDefinition {
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
type: 'text' | 'select';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
|
||||||
|
type: 'select',
|
||||||
|
valuesByLabel: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditorFormDefinition {
|
||||||
|
submitText: string;
|
||||||
|
cancelText: string;
|
||||||
|
action: (formData: FormData, context: EditorUiContext) => boolean;
|
||||||
|
cancel: () => void;
|
||||||
|
fields: EditorFormFieldDefinition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EditorFormField extends EditorUiElement {
|
||||||
|
protected definition: EditorFormFieldDefinition;
|
||||||
|
|
||||||
|
constructor(definition: EditorFormFieldDefinition) {
|
||||||
|
super();
|
||||||
|
this.definition = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected buildDOM(): HTMLElement {
|
||||||
|
const id = `editor-form-field-${this.definition.name}-${Date.now()}`;
|
||||||
|
let input: HTMLElement;
|
||||||
|
|
||||||
|
if (this.definition.type === 'select') {
|
||||||
|
const options = (this.definition as EditorSelectFormFieldDefinition).valuesByLabel
|
||||||
|
const labels = Object.keys(options);
|
||||||
|
const optionElems = labels.map(label => el('option', {value: options[label]}, [label]));
|
||||||
|
input = el('select', {id, name: this.definition.name, class: 'editor-form-field-input'}, optionElems);
|
||||||
|
} else {
|
||||||
|
input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
|
||||||
|
}
|
||||||
|
|
||||||
|
return el('div', {class: 'editor-form-field-wrapper'}, [
|
||||||
|
el('label', {class: 'editor-form-field-label', for: id}, [this.trans(this.definition.label)]),
|
||||||
|
input,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EditorForm extends EditorContainerUiElement {
|
||||||
|
protected definition: EditorFormDefinition;
|
||||||
|
|
||||||
|
constructor(definition: EditorFormDefinition) {
|
||||||
|
super(definition.fields.map(fieldDefinition => new EditorFormField(fieldDefinition)));
|
||||||
|
this.definition = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected buildDOM(): HTMLElement {
|
||||||
|
const cancelButton = el('button', {type: 'button', class: 'editor-form-action-secondary'}, [this.trans(this.definition.cancelText)]);
|
||||||
|
const form = el('form', {}, [
|
||||||
|
...this.children.map(child => child.getDOMElement()),
|
||||||
|
el('div', {class: 'editor-form-actions'}, [
|
||||||
|
cancelButton,
|
||||||
|
el('button', {type: 'submit', class: 'editor-form-action-primary'}, [this.trans(this.definition.submitText)]),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
form.addEventListener('submit', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = new FormData(form as HTMLFormElement);
|
||||||
|
this.definition.action(formData, this.getContext());
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelButton.addEventListener('click', (event) => {
|
||||||
|
this.definition.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
}
|
11
resources/js/wysiwyg/ui/framework/manager.ts
Normal file
11
resources/js/wysiwyg/ui/framework/manager.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class EditorUIManager {
|
||||||
|
|
||||||
|
// Todo - Register and show modal via this
|
||||||
|
// (Part of UI context)
|
||||||
|
|
||||||
|
}
|
@ -5,12 +5,28 @@ import {
|
|||||||
SELECTION_CHANGE_COMMAND
|
SELECTION_CHANGE_COMMAND
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {getMainEditorFullToolbar} from "./toolbars";
|
import {getMainEditorFullToolbar} from "./toolbars";
|
||||||
|
import {EditorUIManager} from "./framework/manager";
|
||||||
|
import {EditorForm} from "./framework/forms";
|
||||||
|
import {link} from "./defaults/form-definitions";
|
||||||
|
|
||||||
export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
|
export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
|
||||||
|
const manager = new EditorUIManager();
|
||||||
|
const context = {
|
||||||
|
editor,
|
||||||
|
manager,
|
||||||
|
translate: (text: string): string => text,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create primary toolbar
|
||||||
const toolbar = getMainEditorFullToolbar();
|
const toolbar = getMainEditorFullToolbar();
|
||||||
toolbar.setContext({editor});
|
toolbar.setContext(context);
|
||||||
element.before(toolbar.getDOMElement());
|
element.before(toolbar.getDOMElement());
|
||||||
|
|
||||||
|
// Form test
|
||||||
|
const linkForm = new EditorForm(link);
|
||||||
|
linkForm.setContext(context);
|
||||||
|
element.before(linkForm.getDOMElement());
|
||||||
|
|
||||||
// Update button states on editor selection change
|
// Update button states on editor selection change
|
||||||
editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
|
Loading…
Reference in New Issue
Block a user