6 LexicalEditor, LexicalNode,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {el} from "../helpers";
12 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
14 export type SerializedCodeBlockNode = Spread<{
18 }, SerializedLexicalNode>
20 const getLanguageFromClassList = (classes: string) => {
21 const langClasses = classes.split(' ').filter(cssClass => cssClass.startsWith('language-'));
22 return (langClasses[0] || '').replace('language-', '');
25 export class CodeBlockNode extends DecoratorNode<EditorDecoratorAdapter> {
27 __language: string = '';
30 static getType(): string {
34 static clone(node: CodeBlockNode): CodeBlockNode {
35 return new CodeBlockNode(node.__language, node.__code);
38 constructor(language: string = '', code: string = '', key?: string) {
40 this.__language = language;
44 setLanguage(language: string): void {
45 const self = this.getWritable();
46 self.__language = language;
49 getLanguage(): string {
50 const self = this.getLatest();
51 return self.__language;
54 setCode(code: string): void {
55 const self = this.getWritable();
60 const self = this.getLatest();
65 const self = this.getWritable();
70 const self = this.getLatest();
74 decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
89 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
90 const codeBlock = el('pre', {
91 id: this.__id || null,
94 class: this.__language ? `language-${this.__language}` : null,
98 return el('div', {class: 'editor-code-block-wrap'}, [codeBlock]);
101 updateDOM(prevNode: CodeBlockNode, dom: HTMLElement) {
102 const code = dom.querySelector('code');
103 if (!code) return false;
105 if (prevNode.__language !== this.__language) {
106 code.className = this.__language ? `language-${this.__language}` : '';
109 if (prevNode.__id !== this.__id) {
110 dom.setAttribute('id', this.__id);
113 if (prevNode.__code !== this.__code) {
114 code.textContent = this.__code;
120 static importDOM(): DOMConversionMap|null {
122 pre(node: HTMLElement): DOMConversion|null {
124 conversion: (element: HTMLElement): DOMConversionOutput|null => {
126 const codeEl = element.querySelector('code');
127 const language = getLanguageFromClassList(element.className)
128 || (codeEl && getLanguageFromClassList(codeEl.className))
131 const code = codeEl ? (codeEl.textContent || '').trim() : (element.textContent || '').trim();
134 node: $createCodeBlockNode(language, code),
143 exportJSON(): SerializedCodeBlockNode {
148 language: this.__language,
153 static importJSON(serializedNode: SerializedCodeBlockNode): CodeBlockNode {
154 const node = $createCodeBlockNode(serializedNode.language, serializedNode.code);
155 node.setId(serializedNode.id || '');
160 export function $createCodeBlockNode(language: string = '', code: string = ''): CodeBlockNode {
161 return new CodeBlockNode(language, code);
164 export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
165 return node instanceof CodeBlockNode;
168 export function $openCodeEditorForNode(editor: LexicalEditor, node: CodeBlockNode): void {
169 const code = node.getCode();
170 const language = node.getLanguage();
173 const codeEditor = window.$components.first('code-editor');
174 // TODO - Handle direction
175 codeEditor.open(code, language, 'ltr', (newCode: string, newLang: string) => {
176 editor.update(() => {
177 node.setCode(newCode);
178 node.setLanguage(newLang);