import { Attribute, Block, Node, Schema } from '../../prosemirror';
export class CodeBlockNodeType extends Block {
constructor(name: string, schema: Schema) {
super(name, schema);
if (name !== 'code_block') {
throw new Error('CodeBlockNodeType must be named "code_block".');
}
}
get attrs() {
return {
language: new Attribute({default: null})
};
}
get isCode() {
return true;
}
get matchDOMTag() {
return {
'pre': (dom: HTMLElement) => {
const language = getLanguageFromEditorStyle(dom) || getLanguageFromBitbucketStyle(dom);
return [
{
'language': language
},
{
preserveWhitespace: true
}
] as any;
}
};
}
toDOM(node: CodeBlockNode): [string, any, number] {
return ['pre', { 'data-language': node.attrs.language }, 0];
}
}
// example of BB style:
//
const getLanguageFromBitbucketStyle = (dom: HTMLElement): string | undefined => {
const parent = dom.parentElement;
if (parent && parent.classList.contains('codehilite')) {
// code block html from Bitbucket always contains an extra new line
removeLastNewLine(dom);
return extractLanguageFromClass(parent.className);
}
};
const removeLastNewLine = (dom: HTMLElement): void => {
dom.textContent = dom.textContent!.replace(/\n$/, '');
};
const getLanguageFromEditorStyle = (dom: HTMLElement): string => {
return dom.dataset['language'];
};
const extractLanguageFromClass = (className: string): string | undefined => {
const languageRegex = /(?:^|\s)language-([^\s]+)/;
const result = languageRegex.exec(className);
if (result && result[1]) {
return result[1];
}
};
export interface CodeBlockNode extends Node {
type: CodeBlockNodeType;
attrs: {
language: string;
};
}
export function isCodeBlockNode(node: Node): node is CodeBlockNode {
return node.type instanceof CodeBlockNodeType;
}