6 LexicalEditor, LexicalNode,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {el} from "../helpers";
12 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
13 import {code} from "../ui/defaults/button-definitions";
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 {
91 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
92 const codeBlock = el('pre', {
93 id: this.__id || null,
96 class: this.__language ? `language-${this.__language}` : null,
100 return el('div', {class: 'editor-code-block-wrap'}, [codeBlock]);
103 updateDOM(prevNode: CodeBlockNode, dom: HTMLElement) {
104 const code = dom.querySelector('code');
105 if (!code) return false;
107 if (prevNode.__language !== this.__language) {
108 code.className = this.__language ? `language-${this.__language}` : '';
111 if (prevNode.__id !== this.__id) {
112 dom.setAttribute('id', this.__id);
115 if (prevNode.__code !== this.__code) {
116 code.textContent = this.__code;
122 static importDOM(): DOMConversionMap|null {
124 pre(node: HTMLElement): DOMConversion|null {
126 conversion: (element: HTMLElement): DOMConversionOutput|null => {
128 const codeEl = element.querySelector('code');
129 const language = getLanguageFromClassList(element.className)
130 || (codeEl && getLanguageFromClassList(codeEl.className))
133 const code = codeEl ? (codeEl.textContent || '').trim() : (element.textContent || '').trim();
136 node: $createCodeBlockNode(language, code),
145 exportJSON(): SerializedCodeBlockNode {
150 language: this.__language,
155 static importJSON(serializedNode: SerializedCodeBlockNode): CodeBlockNode {
156 const node = $createCodeBlockNode(serializedNode.language, serializedNode.code);
157 node.setId(serializedNode.id || '');
162 export function $createCodeBlockNode(language: string = '', code: string = ''): CodeBlockNode {
163 return new CodeBlockNode(language, code);
166 export function $isCodeBlockNode(node: LexicalNode | null | undefined) {
167 return node instanceof CodeBlockNode;