6 LexicalEditor, LexicalNode,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {el} from "../helpers";
12 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
13 import {CodeEditor} from "../../components";
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 return new CodeBlockNode(node.__language, node.__code);
39 constructor(language: string = '', code: string = '', key?: string) {
41 this.__language = language;
45 setLanguage(language: string): void {
46 const self = this.getWritable();
47 self.__language = language;
50 getLanguage(): string {
51 const self = this.getLatest();
52 return self.__language;
55 setCode(code: string): void {
56 const self = this.getWritable();
61 const self = this.getLatest();
66 const self = this.getWritable();
71 const self = this.getLatest();
75 decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
90 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
91 const codeBlock = el('pre', {
92 id: this.__id || null,
95 class: this.__language ? `language-${this.__language}` : null,
99 return el('div', {class: 'editor-code-block-wrap'}, [codeBlock]);
102 updateDOM(prevNode: CodeBlockNode, dom: HTMLElement) {
103 const code = dom.querySelector('code');
104 if (!code) return false;
106 if (prevNode.__language !== this.__language) {
107 code.className = this.__language ? `language-${this.__language}` : '';
110 if (prevNode.__id !== this.__id) {
111 dom.setAttribute('id', this.__id);
114 if (prevNode.__code !== this.__code) {
115 code.textContent = this.__code;
121 static importDOM(): DOMConversionMap|null {
123 pre(node: HTMLElement): DOMConversion|null {
125 conversion: (element: HTMLElement): DOMConversionOutput|null => {
127 const codeEl = element.querySelector('code');
128 const language = getLanguageFromClassList(element.className)
129 || (codeEl && getLanguageFromClassList(codeEl.className))
132 const code = codeEl ? (codeEl.textContent || '').trim() : (element.textContent || '').trim();
135 node: $createCodeBlockNode(language, code),
144 exportJSON(): SerializedCodeBlockNode {
149 language: this.__language,
154 static importJSON(serializedNode: SerializedCodeBlockNode): CodeBlockNode {
155 const node = $createCodeBlockNode(serializedNode.language, serializedNode.code);
156 node.setId(serializedNode.id || '');
161 export function $createCodeBlockNode(language: string = '', code: string = ''): CodeBlockNode {
162 return new CodeBlockNode(language, code);
165 export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
166 return node instanceof CodeBlockNode;
169 export function $openCodeEditorForNode(editor: LexicalEditor, node: CodeBlockNode): void {
170 const code = node.getCode();
171 const language = node.getLanguage();
174 const codeEditor = window.$components.first('code-editor') as CodeEditor;
175 // TODO - Handle direction
176 codeEditor.open(code, language, 'ltr', (newCode: string, newLang: string) => {
177 editor.update(() => {
178 node.setCode(newCode);
179 node.setLanguage(newLang);