mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-23 11:22:33 +01:00
Lexical: Added code block selection & edit features
Also added extra lifecycle handling for decorators to things can be properly cleaned up after node destruction.
This commit is contained in:
parent
51d8044a54
commit
ea4c50c2c2
@ -1,14 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
|
$createNodeSelection,
|
||||||
$createParagraphNode, $getRoot,
|
$createParagraphNode, $getRoot,
|
||||||
$getSelection,
|
$getSelection,
|
||||||
$isTextNode,
|
$isTextNode, $setSelection,
|
||||||
BaseSelection, ElementNode,
|
BaseSelection,
|
||||||
LexicalEditor, LexicalNode, TextFormatType
|
LexicalEditor, LexicalNode, TextFormatType
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
|
import {getNodesForPageEditor, LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
|
||||||
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
|
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
|
||||||
import {$setBlocksType} from "@lexical/selection";
|
import {$setBlocksType} from "@lexical/selection";
|
||||||
import {$createDetailsNode} from "./nodes/details";
|
|
||||||
|
|
||||||
export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
|
export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
|
||||||
const el = document.createElement(tag);
|
const el = document.createElement(tag);
|
||||||
@ -94,3 +94,24 @@ export function insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: bo
|
|||||||
$getRoot().append(node);
|
$getRoot().append(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectSingleNode(node: LexicalNode) {
|
||||||
|
const nodeSelection = $createNodeSelection();
|
||||||
|
nodeSelection.add(node.getKey());
|
||||||
|
$setSelection(nodeSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectionContainsNode(selection: BaseSelection|null, node: LexicalNode): boolean {
|
||||||
|
if (!selection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = node.getKey();
|
||||||
|
for (const node of selection.getNodes()) {
|
||||||
|
if (node.getKey() === key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -2,11 +2,12 @@ import {createEditor, CreateEditorArgs, LexicalEditor} 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 {getNodesForPageEditor} from './nodes';
|
import {getNodesForPageEditor, registerCommonNodeMutationListeners} from './nodes';
|
||||||
import {buildEditorUI} from "./ui";
|
import {buildEditorUI} from "./ui";
|
||||||
import {getEditorContentAsHtml, setEditorContentFromHtml} from "./actions";
|
import {getEditorContentAsHtml, setEditorContentFromHtml} from "./actions";
|
||||||
import {registerTableResizer} from "./ui/framework/helpers/table-resizer";
|
import {registerTableResizer} from "./ui/framework/helpers/table-resizer";
|
||||||
import {el} from "./helpers";
|
import {el} from "./helpers";
|
||||||
|
import {EditorUiContext} from "./ui/framework/core";
|
||||||
|
|
||||||
export function createPageEditorInstance(container: HTMLElement, htmlContent: string): SimpleWysiwygEditorInterface {
|
export function createPageEditorInstance(container: HTMLElement, htmlContent: string): SimpleWysiwygEditorInterface {
|
||||||
const config: CreateEditorArgs = {
|
const config: CreateEditorArgs = {
|
||||||
@ -59,7 +60,8 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
buildEditorUI(container, editArea, editor);
|
const context: EditorUiContext = buildEditorUI(container, editArea, editor);
|
||||||
|
registerCommonNodeMutationListeners(context);
|
||||||
|
|
||||||
return new SimpleWysiwygEditorInterface(editor);
|
return new SimpleWysiwygEditorInterface(editor);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
|
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
|
||||||
import {CalloutNode} from './callout';
|
import {CalloutNode} from './callout';
|
||||||
import {ElementNode, KlassConstructor, LexicalNode, LexicalNodeReplacement, ParagraphNode} from "lexical";
|
import {
|
||||||
|
$getNodeByKey,
|
||||||
|
ElementNode,
|
||||||
|
KlassConstructor,
|
||||||
|
LexicalEditor,
|
||||||
|
LexicalNode,
|
||||||
|
LexicalNodeReplacement, NodeMutation,
|
||||||
|
ParagraphNode
|
||||||
|
} from "lexical";
|
||||||
import {CustomParagraphNode} from "./custom-paragraph";
|
import {CustomParagraphNode} from "./custom-paragraph";
|
||||||
import {LinkNode} from "@lexical/link";
|
import {LinkNode} from "@lexical/link";
|
||||||
import {ImageNode} from "./image";
|
import {ImageNode} from "./image";
|
||||||
@ -11,6 +19,8 @@ import {CustomTableNode} from "./custom-table";
|
|||||||
import {HorizontalRuleNode} from "./horizontal-rule";
|
import {HorizontalRuleNode} from "./horizontal-rule";
|
||||||
import {CodeBlockNode} from "./code-block";
|
import {CodeBlockNode} from "./code-block";
|
||||||
import {DiagramNode} from "./diagram";
|
import {DiagramNode} from "./diagram";
|
||||||
|
import {EditorUIManager} from "../ui/framework/manager";
|
||||||
|
import {EditorUiContext} from "../ui/framework/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the nodes for lexical.
|
* Load the nodes for lexical.
|
||||||
@ -47,5 +57,25 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerCommonNodeMutationListeners(context: EditorUiContext): void {
|
||||||
|
const decorated = [ImageNode, CodeBlockNode, DiagramNode];
|
||||||
|
|
||||||
|
const decorationDestroyListener = (mutations: Map<string, NodeMutation>): void => {
|
||||||
|
for (let [nodeKey, mutation] of mutations) {
|
||||||
|
if (mutation === "destroyed") {
|
||||||
|
const decorator = context.manager.getDecoratorByNodeKey(nodeKey);
|
||||||
|
if (decorator) {
|
||||||
|
decorator.destroy(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let decoratedNode of decorated) {
|
||||||
|
// Have to pass a unique function here since they are stored by lexical keyed on listener function.
|
||||||
|
context.editor.registerMutationListener(decoratedNode, (mutations) => decorationDestroyListener(mutations));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type LexicalNodeMatcher = (node: LexicalNode|null|undefined) => boolean;
|
export type LexicalNodeMatcher = (node: LexicalNode|null|undefined) => boolean;
|
||||||
export type LexicalElementNodeCreator = () => ElementNode;
|
export type LexicalElementNodeCreator = () => ElementNode;
|
@ -1,7 +1,9 @@
|
|||||||
import {EditorDecorator} from "../framework/decorator";
|
import {EditorDecorator} from "../framework/decorator";
|
||||||
import {EditorUiContext} from "../framework/core";
|
import {EditorUiContext} from "../framework/core";
|
||||||
import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
||||||
import {ImageNode} from "../../nodes/image";
|
import {selectionContainsNode, selectSingleNode} from "../../helpers";
|
||||||
|
import {context} from "esbuild";
|
||||||
|
import {BaseSelection} from "lexical";
|
||||||
|
|
||||||
|
|
||||||
export class CodeBlockDecorator extends EditorDecorator {
|
export class CodeBlockDecorator extends EditorDecorator {
|
||||||
@ -32,12 +34,26 @@ export class CodeBlockDecorator extends EditorDecorator {
|
|||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
element.addEventListener('click', event => {
|
||||||
|
context.editor.update(() => {
|
||||||
|
selectSingleNode(this.getNode());
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
element.addEventListener('dblclick', event => {
|
element.addEventListener('dblclick', event => {
|
||||||
context.editor.getEditorState().read(() => {
|
context.editor.getEditorState().read(() => {
|
||||||
$openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
|
$openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectionChange = (selection: BaseSelection|null): void => {
|
||||||
|
element.classList.toggle('selected', selectionContainsNode(selection, codeNode));
|
||||||
|
};
|
||||||
|
context.manager.onSelectionChange(selectionChange);
|
||||||
|
this.onDestroy(() => {
|
||||||
|
context.manager.offSelectionChange(selectionChange);
|
||||||
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const renderEditor = (Code) => {
|
const renderEditor = (Code) => {
|
||||||
this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
|
this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {EditorDecorator} from "../framework/decorator";
|
import {EditorDecorator} from "../framework/decorator";
|
||||||
import {el} from "../../helpers";
|
import {el, selectSingleNode} from "../../helpers";
|
||||||
import {$createNodeSelection, $setSelection} from "lexical";
|
import {$createNodeSelection, $setSelection} from "lexical";
|
||||||
import {EditorUiContext} from "../framework/core";
|
import {EditorUiContext} from "../framework/core";
|
||||||
import {ImageNode} from "../../nodes/image";
|
import {ImageNode} from "../../nodes/image";
|
||||||
@ -41,9 +41,7 @@ export class ImageDecorator extends EditorDecorator {
|
|||||||
tracker = this.setupTracker(decorateEl, context);
|
tracker = this.setupTracker(decorateEl, context);
|
||||||
|
|
||||||
context.editor.update(() => {
|
context.editor.update(() => {
|
||||||
const nodeSelection = $createNodeSelection();
|
selectSingleNode(this.getNode());
|
||||||
nodeSelection.add(this.getNode().getKey());
|
|
||||||
$setSelection(nodeSelection);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ import codeBlockIcon from "@icons/editor/code-block.svg"
|
|||||||
import detailsIcon from "@icons/editor/details.svg"
|
import detailsIcon from "@icons/editor/details.svg"
|
||||||
import sourceIcon from "@icons/editor/source-view.svg"
|
import sourceIcon from "@icons/editor/source-view.svg"
|
||||||
import fullscreenIcon from "@icons/editor/fullscreen.svg"
|
import fullscreenIcon from "@icons/editor/fullscreen.svg"
|
||||||
|
import editIcon from "@icons/edit.svg"
|
||||||
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
|
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
|
||||||
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
||||||
|
|
||||||
@ -344,7 +345,7 @@ export const codeBlock: EditorButtonDefinition = {
|
|||||||
action(context: EditorUiContext) {
|
action(context: EditorUiContext) {
|
||||||
context.editor.getEditorState().read(() => {
|
context.editor.getEditorState().read(() => {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
const codeBlock = getNodeFromSelection(selection, $isCodeBlockNode) as (CodeBlockNode|null);
|
const codeBlock = getNodeFromSelection(context.lastSelection, $isCodeBlockNode) as (CodeBlockNode|null);
|
||||||
if (codeBlock === null) {
|
if (codeBlock === null) {
|
||||||
context.editor.update(() => {
|
context.editor.update(() => {
|
||||||
const codeBlock = $createCodeBlockNode();
|
const codeBlock = $createCodeBlockNode();
|
||||||
@ -363,6 +364,11 @@ export const codeBlock: EditorButtonDefinition = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
|
||||||
|
label: 'Edit code block',
|
||||||
|
icon: editIcon,
|
||||||
|
});
|
||||||
|
|
||||||
export const details: EditorButtonDefinition = {
|
export const details: EditorButtonDefinition = {
|
||||||
label: 'Insert collapsible block',
|
label: 'Insert collapsible block',
|
||||||
icon: detailsIcon,
|
icon: detailsIcon,
|
||||||
|
@ -11,6 +11,8 @@ export abstract class EditorDecorator {
|
|||||||
protected node: LexicalNode | null = null;
|
protected node: LexicalNode | null = null;
|
||||||
protected context: EditorUiContext;
|
protected context: EditorUiContext;
|
||||||
|
|
||||||
|
private onDestroyCallbacks: (() => void)[] = [];
|
||||||
|
|
||||||
constructor(context: EditorUiContext) {
|
constructor(context: EditorUiContext) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
@ -27,6 +29,13 @@ export abstract class EditorDecorator {
|
|||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to be ran on destroy of this decorator's node.
|
||||||
|
*/
|
||||||
|
protected onDestroy(callback: () => void) {
|
||||||
|
this.onDestroyCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the decorator.
|
* Render the decorator.
|
||||||
* Can run on both creation and update for a node decorator.
|
* Can run on both creation and update for a node decorator.
|
||||||
@ -35,4 +44,14 @@ export abstract class EditorDecorator {
|
|||||||
*/
|
*/
|
||||||
abstract render(context: EditorUiContext, decorated: HTMLElement): HTMLElement|void;
|
abstract render(context: EditorUiContext, decorated: HTMLElement): HTMLElement|void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy this decorator. Used for tear-down operations upon destruction
|
||||||
|
* of the underlying node this decorator is attached to.
|
||||||
|
*/
|
||||||
|
destroy(context: EditorUiContext): void {
|
||||||
|
for (const callback of this.onDestroyCallbacks) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,11 +1,13 @@
|
|||||||
import {EditorFormModal, EditorFormModalDefinition} from "./modals";
|
import {EditorFormModal, EditorFormModalDefinition} from "./modals";
|
||||||
import {EditorContainerUiElement, EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core";
|
import {EditorContainerUiElement, EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core";
|
||||||
import {EditorDecorator, EditorDecoratorAdapter} from "./decorator";
|
import {EditorDecorator, EditorDecoratorAdapter} from "./decorator";
|
||||||
import {$getSelection, COMMAND_PRIORITY_LOW, LexicalEditor, SELECTION_CHANGE_COMMAND} from "lexical";
|
import {$getSelection, BaseSelection, COMMAND_PRIORITY_LOW, LexicalEditor, SELECTION_CHANGE_COMMAND} from "lexical";
|
||||||
import {DecoratorListener} from "lexical/LexicalEditor";
|
import {DecoratorListener} from "lexical/LexicalEditor";
|
||||||
import type {NodeKey} from "lexical/LexicalNode";
|
import type {NodeKey} from "lexical/LexicalNode";
|
||||||
import {EditorContextToolbar, EditorContextToolbarDefinition} from "./toolbars";
|
import {EditorContextToolbar, EditorContextToolbarDefinition} from "./toolbars";
|
||||||
|
|
||||||
|
export type SelectionChangeHandler = (selection: BaseSelection|null) => void;
|
||||||
|
|
||||||
export class EditorUIManager {
|
export class EditorUIManager {
|
||||||
|
|
||||||
protected modalDefinitionsByKey: Record<string, EditorFormModalDefinition> = {};
|
protected modalDefinitionsByKey: Record<string, EditorFormModalDefinition> = {};
|
||||||
@ -15,6 +17,7 @@ export class EditorUIManager {
|
|||||||
protected toolbar: EditorContainerUiElement|null = null;
|
protected toolbar: EditorContainerUiElement|null = null;
|
||||||
protected contextToolbarDefinitionsByKey: Record<string, EditorContextToolbarDefinition> = {};
|
protected contextToolbarDefinitionsByKey: Record<string, EditorContextToolbarDefinition> = {};
|
||||||
protected activeContextToolbars: EditorContextToolbar[] = [];
|
protected activeContextToolbars: EditorContextToolbar[] = [];
|
||||||
|
protected selectionChangeHandlers: Set<SelectionChangeHandler> = new Set();
|
||||||
|
|
||||||
setContext(context: EditorUiContext) {
|
setContext(context: EditorUiContext) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@ -72,6 +75,10 @@ export class EditorUIManager {
|
|||||||
return decorator;
|
return decorator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDecoratorByNodeKey(nodeKey: string): EditorDecorator|null {
|
||||||
|
return this.decoratorInstancesByNodeKey[nodeKey] || null;
|
||||||
|
}
|
||||||
|
|
||||||
setToolbar(toolbar: EditorContainerUiElement) {
|
setToolbar(toolbar: EditorContainerUiElement) {
|
||||||
if (this.toolbar) {
|
if (this.toolbar) {
|
||||||
this.toolbar.getDOMElement().remove();
|
this.toolbar.getDOMElement().remove();
|
||||||
@ -94,7 +101,7 @@ export class EditorUIManager {
|
|||||||
for (const toolbar of this.activeContextToolbars) {
|
for (const toolbar of this.activeContextToolbars) {
|
||||||
toolbar.updateState(update);
|
toolbar.updateState(update);
|
||||||
}
|
}
|
||||||
// console.log('selection update', update.selection);
|
this.triggerSelectionChange(update.selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerStateRefresh(): void {
|
triggerStateRefresh(): void {
|
||||||
@ -104,6 +111,24 @@ export class EditorUIManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected triggerSelectionChange(selection: BaseSelection|null): void {
|
||||||
|
if (!selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const handler of this.selectionChangeHandlers) {
|
||||||
|
handler(selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectionChange(handler: SelectionChangeHandler): void {
|
||||||
|
this.selectionChangeHandlers.add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
offSelectionChange(handler: SelectionChangeHandler): void {
|
||||||
|
this.selectionChangeHandlers.delete(handler);
|
||||||
|
}
|
||||||
|
|
||||||
protected updateContextToolbars(update: EditorUiStateUpdate): void {
|
protected updateContextToolbars(update: EditorUiStateUpdate): void {
|
||||||
for (const toolbar of this.activeContextToolbars) {
|
for (const toolbar of this.activeContextToolbars) {
|
||||||
toolbar.empty();
|
toolbar.empty();
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import {LexicalEditor} from "lexical";
|
import {LexicalEditor} from "lexical";
|
||||||
import {getImageToolbarContent, getLinkToolbarContent, getMainEditorFullToolbar} from "./toolbars";
|
import {
|
||||||
|
getCodeToolbarContent,
|
||||||
|
getImageToolbarContent,
|
||||||
|
getLinkToolbarContent,
|
||||||
|
getMainEditorFullToolbar
|
||||||
|
} from "./toolbars";
|
||||||
import {EditorUIManager} from "./framework/manager";
|
import {EditorUIManager} from "./framework/manager";
|
||||||
import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions";
|
import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions";
|
||||||
import {ImageDecorator} from "./decorators/image";
|
import {ImageDecorator} from "./decorators/image";
|
||||||
@ -7,7 +12,7 @@ import {EditorUiContext} from "./framework/core";
|
|||||||
import {CodeBlockDecorator} from "./decorators/code-block";
|
import {CodeBlockDecorator} from "./decorators/code-block";
|
||||||
import {DiagramDecorator} from "./decorators/diagram";
|
import {DiagramDecorator} from "./decorators/diagram";
|
||||||
|
|
||||||
export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor) {
|
export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor): EditorUiContext {
|
||||||
const manager = new EditorUIManager();
|
const manager = new EditorUIManager();
|
||||||
const context: EditorUiContext = {
|
const context: EditorUiContext = {
|
||||||
editor,
|
editor,
|
||||||
@ -48,9 +53,15 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, edit
|
|||||||
selector: 'a',
|
selector: 'a',
|
||||||
content: getLinkToolbarContent(),
|
content: getLinkToolbarContent(),
|
||||||
});
|
});
|
||||||
|
manager.registerContextToolbar('code', {
|
||||||
|
selector: '.editor-code-block-wrap',
|
||||||
|
content: getCodeToolbarContent(),
|
||||||
|
});
|
||||||
|
|
||||||
// Register image decorator listener
|
// Register image decorator listener
|
||||||
manager.registerDecoratorType('image', ImageDecorator);
|
manager.registerDecoratorType('image', ImageDecorator);
|
||||||
manager.registerDecoratorType('code', CodeBlockDecorator);
|
manager.registerDecoratorType('code', CodeBlockDecorator);
|
||||||
manager.registerDecoratorType('diagram', DiagramDecorator);
|
manager.registerDecoratorType('diagram', DiagramDecorator);
|
||||||
|
|
||||||
|
return context;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import {EditorButton} from "./framework/buttons";
|
import {EditorButton} from "./framework/buttons";
|
||||||
import {
|
import {
|
||||||
blockquote, bold, bulletList, clearFormating, code, codeBlock,
|
blockquote, bold, bulletList, clearFormating, code, codeBlock,
|
||||||
dangerCallout, details, fullscreen,
|
dangerCallout, details, editCodeBlock, fullscreen,
|
||||||
h2, h3, h4, h5, highlightColor, horizontalRule, image,
|
h2, h3, h4, h5, highlightColor, horizontalRule, image,
|
||||||
infoCallout, italic, link, numberList, paragraph,
|
infoCallout, italic, link, numberList, paragraph,
|
||||||
redo, source, strikethrough, subscript,
|
redo, source, strikethrough, subscript,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
undo, unlink,
|
undo, unlink,
|
||||||
warningCallout
|
warningCallout
|
||||||
} from "./defaults/button-definitions";
|
} from "./defaults/button-definitions";
|
||||||
import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext, EditorUiElement} from "./framework/core";
|
import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core";
|
||||||
import {el} from "../helpers";
|
import {el} from "../helpers";
|
||||||
import {EditorFormatMenu} from "./framework/blocks/format-menu";
|
import {EditorFormatMenu} from "./framework/blocks/format-menu";
|
||||||
import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
|
import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
|
||||||
@ -112,3 +112,9 @@ export function getLinkToolbarContent(): EditorUiElement[] {
|
|||||||
new EditorButton(unlink),
|
new EditorButton(unlink),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCodeToolbarContent(): EditorUiElement[] {
|
||||||
|
return [
|
||||||
|
new EditorButton(editCodeBlock),
|
||||||
|
];
|
||||||
|
}
|
@ -312,6 +312,9 @@ body.editor-is-fullscreen {
|
|||||||
> * {
|
> * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
&.selected .cm-editor {
|
||||||
|
border: 1px dashed var(--editor-color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor form elements
|
// Editor form elements
|
||||||
|
Loading…
Reference in New Issue
Block a user