mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Added view/edit source code button/form/action
This commit is contained in:
parent
5c343638b6
commit
e889bc680b
26
resources/js/wysiwyg/actions.ts
Normal file
26
resources/js/wysiwyg/actions.ts
Normal file
@ -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 {createEmptyHistoryState, registerHistory} from '@lexical/history';
|
||||||
import {registerRichText} from '@lexical/rich-text';
|
import {registerRichText} from '@lexical/rich-text';
|
||||||
import {mergeRegister} from '@lexical/utils';
|
import {mergeRegister} from '@lexical/utils';
|
||||||
import {$generateNodesFromDOM} from '@lexical/html';
|
|
||||||
import {getNodesForPageEditor} from './nodes';
|
import {getNodesForPageEditor} from './nodes';
|
||||||
import {buildEditorUI} from "./ui";
|
import {buildEditorUI} from "./ui";
|
||||||
|
import {setEditorContentFromHtml} from "./actions";
|
||||||
|
|
||||||
export function createPageEditorInstance(editArea: HTMLElement) {
|
export function createPageEditorInstance(editArea: HTMLElement) {
|
||||||
const config: CreateEditorArgs = {
|
const config: CreateEditorArgs = {
|
||||||
@ -14,8 +14,6 @@ export function createPageEditorInstance(editArea: HTMLElement) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const startingHtml = editArea.innerHTML;
|
const startingHtml = editArea.innerHTML;
|
||||||
const parser = new DOMParser();
|
|
||||||
const dom = parser.parseFromString(startingHtml, 'text/html');
|
|
||||||
|
|
||||||
const editor = createEditor(config);
|
const editor = createEditor(config);
|
||||||
editor.setRootElement(editArea);
|
editor.setRootElement(editArea);
|
||||||
@ -25,11 +23,7 @@ export function createPageEditorInstance(editArea: HTMLElement) {
|
|||||||
registerHistory(editor, createEmptyHistoryState(), 300),
|
registerHistory(editor, createEmptyHistoryState(), 300),
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.update(() => {
|
setEditorContentFromHtml(editor, startingHtml);
|
||||||
const startingNodes = $generateNodesFromDOM(editor, dom);
|
|
||||||
const root = $getRoot();
|
|
||||||
root.append(...startingNodes);
|
|
||||||
});
|
|
||||||
|
|
||||||
const debugView = document.getElementById('lexical-debug');
|
const debugView = document.getElementById('lexical-debug');
|
||||||
editor.registerUpdateListener(({editorState}) => {
|
editor.registerUpdateListener(({editorState}) => {
|
||||||
|
@ -28,6 +28,7 @@ import {EditorUiContext} from "../framework/core";
|
|||||||
import {$isImageNode, ImageNode} from "../../nodes/image";
|
import {$isImageNode, ImageNode} from "../../nodes/image";
|
||||||
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
|
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
|
||||||
import {$insertNodeToNearestRoot} from "@lexical/utils";
|
import {$insertNodeToNearestRoot} from "@lexical/utils";
|
||||||
|
import {getEditorContentAsHtml} from "../../actions";
|
||||||
|
|
||||||
export const undo: EditorButtonDefinition = {
|
export const undo: EditorButtonDefinition = {
|
||||||
label: 'Undo',
|
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 {$createLinkNode} from "@lexical/link";
|
||||||
import {$createTextNode, $getSelection} from "lexical";
|
import {$createTextNode, $getSelection} from "lexical";
|
||||||
import {$createImageNode} from "../../nodes/image";
|
import {$createImageNode} from "../../nodes/image";
|
||||||
|
import {setEditorContentFromHtml} from "../../actions";
|
||||||
|
|
||||||
|
|
||||||
export const link: EditorFormDefinition = {
|
export const link: EditorFormDefinition = {
|
||||||
@ -86,4 +87,19 @@ export const image: EditorFormDefinition = {
|
|||||||
type: 'text',
|
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 {
|
export interface EditorFormFieldDefinition {
|
||||||
label: string;
|
label: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: 'text' | 'select';
|
type: 'text' | 'select' | 'textarea';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
|
export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
|
||||||
@ -28,7 +28,7 @@ export class EditorFormField extends EditorUiElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setValue(value: string) {
|
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;
|
input.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +45,8 @@ export class EditorFormField extends EditorUiElement {
|
|||||||
const labels = Object.keys(options);
|
const labels = Object.keys(options);
|
||||||
const optionElems = labels.map(label => el('option', {value: options[label]}, [label]));
|
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);
|
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 {
|
} else {
|
||||||
input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
|
input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {getMainEditorFullToolbar} from "./toolbars";
|
import {getMainEditorFullToolbar} from "./toolbars";
|
||||||
import {EditorUIManager} from "./framework/manager";
|
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 {DecoratorListener} from "lexical/LexicalEditor";
|
||||||
import type {NodeKey} from "lexical/LexicalNode";
|
import type {NodeKey} from "lexical/LexicalNode";
|
||||||
import {EditorDecoratorAdapter} from "./framework/decorator";
|
import {EditorDecoratorAdapter} from "./framework/decorator";
|
||||||
@ -36,7 +36,11 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
|
|||||||
manager.registerModal('image', {
|
manager.registerModal('image', {
|
||||||
title: 'Insert/Edit Image',
|
title: 'Insert/Edit Image',
|
||||||
form: imageFormDefinition
|
form: imageFormDefinition
|
||||||
})
|
});
|
||||||
|
manager.registerModal('source', {
|
||||||
|
title: 'Source code',
|
||||||
|
form: sourceFormDefinition,
|
||||||
|
});
|
||||||
|
|
||||||
// Register decorator listener
|
// Register decorator listener
|
||||||
// Maybe move to manager?
|
// Maybe move to manager?
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
dangerCallout, details,
|
dangerCallout, details,
|
||||||
h2, h3, h4, h5, image,
|
h2, h3, h4, h5, image,
|
||||||
infoCallout, italic, link, paragraph,
|
infoCallout, italic, link, paragraph,
|
||||||
redo, strikethrough, subscript,
|
redo, source, strikethrough, subscript,
|
||||||
successCallout, superscript, underline,
|
successCallout, superscript, underline,
|
||||||
undo,
|
undo,
|
||||||
warningCallout
|
warningCallout
|
||||||
@ -42,5 +42,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
|||||||
new EditorButton(link),
|
new EditorButton(link),
|
||||||
new EditorButton(image),
|
new EditorButton(image),
|
||||||
new EditorButton(details),
|
new EditorButton(details),
|
||||||
|
|
||||||
|
new EditorButton(source),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user