5 DOMConversionOutput, DOMExportOutput,
6 LexicalEditor, LexicalNode,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
12 import {CodeEditor} from "../../components";
13 import {el} from "../utils/dom";
15 export type SerializedCodeBlockNode = Spread<{
19 }, SerializedLexicalNode>
21 const getLanguageFromClassList = (classes: string) => {
22 const langClasses = classes.split(' ').filter(cssClass => cssClass.startsWith('language-'));
23 return (langClasses[0] || '').replace('language-', '');
26 export class CodeBlockNode extends DecoratorNode<EditorDecoratorAdapter> {
28 __language: string = '';
31 static getType(): string {
35 static clone(node: CodeBlockNode): CodeBlockNode {
36 const newNode = new CodeBlockNode(node.__language, node.__code, node.__key);
37 newNode.__id = node.__id;
41 constructor(language: string = '', code: string = '', key?: string) {
43 this.__language = language;
47 setLanguage(language: string): void {
48 const self = this.getWritable();
49 self.__language = language;
52 getLanguage(): string {
53 const self = this.getLatest();
54 return self.__language;
57 setCode(code: string): void {
58 const self = this.getWritable();
63 const self = this.getLatest();
68 const self = this.getWritable();
73 const self = this.getLatest();
77 decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
92 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
93 const codeBlock = el('pre', {
94 id: this.__id || null,
97 class: this.__language ? `language-${this.__language}` : null,
101 return el('div', {class: 'editor-code-block-wrap'}, [codeBlock]);
104 updateDOM(prevNode: CodeBlockNode, dom: HTMLElement) {
105 const code = dom.querySelector('code');
106 if (!code) return false;
108 if (prevNode.__language !== this.__language) {
109 code.className = this.__language ? `language-${this.__language}` : '';
112 if (prevNode.__id !== this.__id) {
113 dom.setAttribute('id', this.__id);
116 if (prevNode.__code !== this.__code) {
117 code.textContent = this.__code;
123 exportDOM(editor: LexicalEditor): DOMExportOutput {
124 const dom = this.createDOM(editor._config, editor);
126 element: dom.querySelector('pre') as HTMLElement,
130 static importDOM(): DOMConversionMap|null {
132 pre(node: HTMLElement): DOMConversion|null {
134 conversion: (element: HTMLElement): DOMConversionOutput|null => {
136 const codeEl = element.querySelector('code');
137 const language = getLanguageFromClassList(element.className)
138 || (codeEl && getLanguageFromClassList(codeEl.className))
141 const code = codeEl ? (codeEl.textContent || '').trim() : (element.textContent || '').trim();
142 const node = $createCodeBlockNode(language, code);
145 node.setId(element.id);
156 exportJSON(): SerializedCodeBlockNode {
161 language: this.__language,
166 static importJSON(serializedNode: SerializedCodeBlockNode): CodeBlockNode {
167 const node = $createCodeBlockNode(serializedNode.language, serializedNode.code);
168 node.setId(serializedNode.id || '');
173 export function $createCodeBlockNode(language: string = '', code: string = ''): CodeBlockNode {
174 return new CodeBlockNode(language, code);
177 export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
178 return node instanceof CodeBlockNode;
181 export function $openCodeEditorForNode(editor: LexicalEditor, node: CodeBlockNode): void {
182 const code = node.getCode();
183 const language = node.getLanguage();
186 const codeEditor = window.$components.first('code-editor') as CodeEditor;
187 // TODO - Handle direction
188 codeEditor.open(code, language, 'ltr', (newCode: string, newLang: string) => {
189 editor.update(() => {
190 node.setCode(newCode);
191 node.setLanguage(newLang);