declare module '*.svg' {
const content: string;
export default content;
+}
+
+declare global {
+ interface Window {
+ $components: {
+ first: (string) => Object,
+ }
+ }
}
\ No newline at end of file
}
decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
- // TODO
return {
type: 'code',
getNode: () => this,
export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
return node instanceof CodeBlockNode;
+}
+
+export function $openCodeEditorForNode(editor: LexicalEditor, node: CodeBlockNode): void {
+ const code = node.getCode();
+ const language = node.getLanguage();
+
+ // @ts-ignore
+ const codeEditor = window.$components.first('code-editor');
+ // TODO - Handle direction
+ codeEditor.open(code, language, 'ltr', (newCode: string, newLang: string) => {
+ editor.update(() => {
+ node.setCode(newCode);
+ node.setLanguage(newLang);
+ });
+ // TODO - Re-focus
+ }, () => {
+ // TODO - Re-focus
+ });
}
\ No newline at end of file
import {EditorDecorator} from "../framework/decorator";
-import {el} from "../../helpers";
import {EditorUiContext} from "../framework/core";
-import {CodeBlockNode} from "../../nodes/code-block";
+import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
+import {ImageNode} from "../../nodes/image";
export class CodeBlockDecorator extends EditorDecorator {
- render(context: EditorUiContext, element: HTMLElement): void {
+ protected completedSetup: boolean = false;
+ protected latestCode: string = '';
+ protected latestLanguage: string = '';
+
+ // @ts-ignore
+ protected editor: any = null;
+
+ setup(context: EditorUiContext, element: HTMLElement) {
const codeNode = this.getNode() as CodeBlockNode;
const preEl = element.querySelector('pre');
+ if (!preEl) {
+ return;
+ }
+
if (preEl) {
preEl.hidden = true;
}
- const code = codeNode.__code;
- const language = codeNode.__language;
- const lines = code.split('\n').length;
+ this.latestCode = codeNode.__code;
+ this.latestLanguage = codeNode.__language;
+ const lines = this.latestCode.split('\n').length;
const height = (lines * 19.2) + 18 + 24;
element.style.height = `${height}px`;
- let editor = null;
const startTime = Date.now();
- // Todo - Handling click/edit control
- // Todo - Add toolbar button for code
+ element.addEventListener('dblclick', event => {
+ context.editor.getEditorState().read(() => {
+ $openCodeEditorForNode(context.editor, (this.getNode() as CodeBlockNode));
+ });
+ });
// @ts-ignore
const renderEditor = (Code) => {
- editor = Code.wysiwygView(element, document, code, language);
+ this.editor = Code.wysiwygView(element, document, this.latestCode, this.latestLanguage);
setTimeout(() => {
element.style.height = '';
}, 12);
const timeout = (Date.now() - startTime < 20) ? 20 : 0;
setTimeout(() => renderEditor(Code), timeout);
});
+
+ this.completedSetup = true;
+ }
+
+ update() {
+ const codeNode = this.getNode() as CodeBlockNode;
+ const code = codeNode.getCode();
+ const language = codeNode.getLanguage();
+
+ if (this.latestCode === code && this.latestLanguage === language) {
+ return;
+ }
+ this.latestLanguage = language;
+ this.latestCode = code;
+
+ if (this.editor) {
+ this.editor.setContent(code);
+ this.editor.setMode(language, code);
+ }
+ }
+
+ render(context: EditorUiContext, element: HTMLElement): void {
+ if (this.completedSetup) {
+ this.update();
+ } else {
+ this.setup(context, element);
+ }
}
}
\ No newline at end of file
import tableIcon from "@icons/editor/table.svg"
import imageIcon from "@icons/editor/image.svg"
import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg"
+import codeBlockIcon from "@icons/editor/code-block.svg"
import detailsIcon from "@icons/editor/details.svg"
import sourceIcon from "@icons/editor/source-view.svg"
import fullscreenIcon from "@icons/editor/fullscreen.svg"
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
+import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
export const undo: EditorButtonDefinition = {
label: 'Undo',
}
};
+export const codeBlock: EditorButtonDefinition = {
+ label: 'Insert code block',
+ icon: codeBlockIcon,
+ action(context: EditorUiContext) {
+ context.editor.getEditorState().read(() => {
+ const selection = $getSelection();
+ const codeBlock = getNodeFromSelection(selection, $isCodeBlockNode) as (CodeBlockNode|null);
+ if (codeBlock === null) {
+ context.editor.update(() => {
+ const codeBlock = $createCodeBlockNode();
+ codeBlock.setCode(selection?.getTextContent() || '');
+ insertNewBlockNodeAtSelection(codeBlock, true);
+ $openCodeEditorForNode(context.editor, codeBlock);
+ codeBlock.selectStart();
+ });
+ } else {
+ $openCodeEditorForNode(context.editor, codeBlock);
+ }
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return selectionContainsNodeType(selection, $isCodeBlockNode);
+ }
+};
+
export const details: EditorButtonDefinition = {
label: 'Insert collapsible block',
icon: detailsIcon,
/**
* Render the decorator.
+ * Can run on both creation and update for a node decorator.
* If an element is returned, this will be appended to the element
* that is being decorated.
*/
// Register our DOM decorate listener with the editor
const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
- const keys = Object.keys(decorators);
- for (const key of keys) {
- const decoratedEl = editor.getElementByKey(key);
- if (!decoratedEl) {
- continue;
+ editor.getEditorState().read(() => {
+ const keys = Object.keys(decorators);
+ for (const key of keys) {
+ const decoratedEl = editor.getElementByKey(key);
+ if (!decoratedEl) {
+ continue;
+ }
+
+ const adapter = decorators[key];
+ const decorator = this.getDecorator(adapter.type, key);
+ decorator.setNode(adapter.getNode());
+ const decoratorEl = decorator.render(this.getContext(), decoratedEl);
+ if (decoratorEl) {
+ decoratedEl.append(decoratorEl);
+ }
}
-
- const adapter = decorators[key];
- const decorator = this.getDecorator(adapter.type, key);
- decorator.setNode(adapter.getNode());
- const decoratorEl = decorator.render(this.getContext(), decoratedEl);
- if (decoratorEl) {
- decoratedEl.append(decoratorEl);
- }
- }
+ });
}
editor.registerDecoratorListener(domDecorateListener);
}
import {EditorButton} from "./framework/buttons";
import {
- blockquote, bold, bulletList, clearFormating, code,
+ blockquote, bold, bulletList, clearFormating, code, codeBlock,
dangerCallout, details, fullscreen,
h2, h3, h4, h5, highlightColor, horizontalRule, image,
infoCallout, italic, link, numberList, paragraph,
]),
new EditorButton(image),
new EditorButton(horizontalRule),
+ new EditorButton(codeBlock),
new EditorButton(details),
]),
cursor: row-resize;
}
+.editor-code-block-wrap {
+ user-select: none;
+ > * {
+ pointer-events: none;
+ }
+}
+
// Editor theme styles
.editor-theme-bold {
font-weight: bold;