Lexical: Added view/edit source code button/form/action
This commit is contained in:
		
							parent
							
								
									5c343638b6
								
							
						
					
					
						commit
						e889bc680b
					
				| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
import {$getRoot, LexicalEditor} from "lexical";
 | 
			
		||||
import {$generateHtmlFromNodes, $generateNodesFromDOM} from "@lexical/html";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function setEditorContentFromHtml(editor: LexicalEditor, html: string) {
 | 
			
		||||
    const parser = new DOMParser();
 | 
			
		||||
    const dom = parser.parseFromString(html, 'text/html');
 | 
			
		||||
 | 
			
		||||
    editor.update(() => {
 | 
			
		||||
        const nodes = $generateNodesFromDOM(editor, dom);
 | 
			
		||||
        const root = $getRoot();
 | 
			
		||||
        for (const child of root.getChildren()) {
 | 
			
		||||
            child.remove(true);
 | 
			
		||||
        }
 | 
			
		||||
        root.append(...nodes);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getEditorContentAsHtml(editor: LexicalEditor): Promise<string> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        editor.getEditorState().read(() => {
 | 
			
		||||
            const html = $generateHtmlFromNodes(editor);
 | 
			
		||||
            resolve(html);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
import {$getRoot, createEditor, CreateEditorArgs} from 'lexical';
 | 
			
		||||
import {createEditor, CreateEditorArgs} from 'lexical';
 | 
			
		||||
import {createEmptyHistoryState, registerHistory} from '@lexical/history';
 | 
			
		||||
import {registerRichText} from '@lexical/rich-text';
 | 
			
		||||
import {mergeRegister} from '@lexical/utils';
 | 
			
		||||
import {$generateNodesFromDOM} from '@lexical/html';
 | 
			
		||||
import {getNodesForPageEditor} from './nodes';
 | 
			
		||||
import {buildEditorUI} from "./ui";
 | 
			
		||||
import {setEditorContentFromHtml} from "./actions";
 | 
			
		||||
 | 
			
		||||
export function createPageEditorInstance(editArea: HTMLElement) {
 | 
			
		||||
    const config: CreateEditorArgs = {
 | 
			
		||||
| 
						 | 
				
			
			@ -14,8 +14,6 @@ export function createPageEditorInstance(editArea: HTMLElement) {
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    const startingHtml = editArea.innerHTML;
 | 
			
		||||
    const parser = new DOMParser();
 | 
			
		||||
    const dom = parser.parseFromString(startingHtml, 'text/html');
 | 
			
		||||
 | 
			
		||||
    const editor = createEditor(config);
 | 
			
		||||
    editor.setRootElement(editArea);
 | 
			
		||||
| 
						 | 
				
			
			@ -25,11 +23,7 @@ export function createPageEditorInstance(editArea: HTMLElement) {
 | 
			
		|||
        registerHistory(editor, createEmptyHistoryState(), 300),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    editor.update(() => {
 | 
			
		||||
        const startingNodes = $generateNodesFromDOM(editor, dom);
 | 
			
		||||
        const root = $getRoot();
 | 
			
		||||
        root.append(...startingNodes);
 | 
			
		||||
    });
 | 
			
		||||
    setEditorContentFromHtml(editor, startingHtml);
 | 
			
		||||
 | 
			
		||||
    const debugView = document.getElementById('lexical-debug');
 | 
			
		||||
    editor.registerUpdateListener(({editorState}) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import {EditorUiContext} from "../framework/core";
 | 
			
		|||
import {$isImageNode, ImageNode} from "../../nodes/image";
 | 
			
		||||
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
 | 
			
		||||
import {$insertNodeToNearestRoot} from "@lexical/utils";
 | 
			
		||||
import {getEditorContentAsHtml} from "../../actions";
 | 
			
		||||
 | 
			
		||||
export const undo: EditorButtonDefinition = {
 | 
			
		||||
    label: 'Undo',
 | 
			
		||||
| 
						 | 
				
			
			@ -230,3 +231,14 @@ export const details: EditorButtonDefinition = {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const source: EditorButtonDefinition = {
 | 
			
		||||
    label: 'Source code',
 | 
			
		||||
    async action(context: EditorUiContext) {
 | 
			
		||||
        const modal = context.manager.createModal('source');
 | 
			
		||||
        const source = await getEditorContentAsHtml(context.editor);
 | 
			
		||||
        modal.show({source});
 | 
			
		||||
    },
 | 
			
		||||
    isActive() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import {EditorUiContext} from "../framework/core";
 | 
			
		|||
import {$createLinkNode} from "@lexical/link";
 | 
			
		||||
import {$createTextNode, $getSelection} from "lexical";
 | 
			
		||||
import {$createImageNode} from "../../nodes/image";
 | 
			
		||||
import {setEditorContentFromHtml} from "../../actions";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const link: EditorFormDefinition = {
 | 
			
		||||
| 
						 | 
				
			
			@ -86,4 +87,19 @@ export const image: EditorFormDefinition = {
 | 
			
		|||
            type: 'text',
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const source: EditorFormDefinition = {
 | 
			
		||||
    submitText: 'Save',
 | 
			
		||||
    action(formData, context: EditorUiContext) {
 | 
			
		||||
        setEditorContentFromHtml(context.editor, formData.get('source')?.toString() || '');
 | 
			
		||||
        return true;
 | 
			
		||||
    },
 | 
			
		||||
    fields: [
 | 
			
		||||
        {
 | 
			
		||||
            label: 'Source',
 | 
			
		||||
            name: 'source',
 | 
			
		||||
            type: 'textarea',
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import {el} from "../../helpers";
 | 
			
		|||
export interface EditorFormFieldDefinition {
 | 
			
		||||
    label: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    type: 'text' | 'select';
 | 
			
		||||
    type: 'text' | 'select' | 'textarea';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ export class EditorFormField extends EditorUiElement {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    setValue(value: string) {
 | 
			
		||||
        const input = this.getDOMElement().querySelector('input,select') as HTMLInputElement;
 | 
			
		||||
        const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
 | 
			
		||||
        input.value = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +45,8 @@ export class EditorFormField extends EditorUiElement {
 | 
			
		|||
            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 if (this.definition.type === 'textarea') {
 | 
			
		||||
            input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'});
 | 
			
		||||
        } else {
 | 
			
		||||
            input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ import {
 | 
			
		|||
} from "lexical";
 | 
			
		||||
import {getMainEditorFullToolbar} from "./toolbars";
 | 
			
		||||
import {EditorUIManager} from "./framework/manager";
 | 
			
		||||
import {image as imageFormDefinition, link as linkFormDefinition} from "./defaults/form-definitions";
 | 
			
		||||
import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions";
 | 
			
		||||
import {DecoratorListener} from "lexical/LexicalEditor";
 | 
			
		||||
import type {NodeKey} from "lexical/LexicalNode";
 | 
			
		||||
import {EditorDecoratorAdapter} from "./framework/decorator";
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,11 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
 | 
			
		|||
    manager.registerModal('image', {
 | 
			
		||||
        title: 'Insert/Edit Image',
 | 
			
		||||
        form: imageFormDefinition
 | 
			
		||||
    })
 | 
			
		||||
    });
 | 
			
		||||
    manager.registerModal('source', {
 | 
			
		||||
        title: 'Source code',
 | 
			
		||||
        form: sourceFormDefinition,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Register decorator listener
 | 
			
		||||
    // Maybe move to manager?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import {
 | 
			
		|||
    dangerCallout, details,
 | 
			
		||||
    h2, h3, h4, h5, image,
 | 
			
		||||
    infoCallout, italic, link, paragraph,
 | 
			
		||||
    redo, strikethrough, subscript,
 | 
			
		||||
    redo, source, strikethrough, subscript,
 | 
			
		||||
    successCallout, superscript, underline,
 | 
			
		||||
    undo,
 | 
			
		||||
    warningCallout
 | 
			
		||||
| 
						 | 
				
			
			@ -42,5 +42,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
 | 
			
		|||
        new EditorButton(link),
 | 
			
		||||
        new EditorButton(image),
 | 
			
		||||
        new EditorButton(details),
 | 
			
		||||
 | 
			
		||||
        new EditorButton(source),
 | 
			
		||||
    ]);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue