import { classRegistry } from '../../ClassRegistry';
import { XTextbase } from '../canvasx/XTextbase';
import html2canvas from 'html2canvas';
import { FabricImage } from '../../shapes/Image';
import hljs from 'highlight.js'; // https://highlightjs.org
//@ts-ignore
import markdownit from 'markdown-it';
//@ts-ignore
import javascript from 'highlight.js/lib/languages/javascript';
import { WidgetMarkdownInterface, EntityKeys } from './type/widget.entity.markdown';
import { WidgetType } from './type/widget.type';
hljs.registerLanguage('javascript', javascript);
class XMarkdown extends XTextbase implements WidgetMarkdownInterface {
public markdownText: string;
isEditing: boolean = false;
private renderedImage: FabricImage | null = null;
private md: any;
static type: WidgetType = 'XMarkdown';
static objType: WidgetType = 'XMarkdown';
constructor(text: string, options: WidgetMarkdownInterface) {
super(text, options);
this.markdownText = options?.markdownText || text;
this.objType = 'XMarkdown';
// full options list (defaults)
this.md = markdownit({
// Enable HTML tags in source
html: true,
// Use '/' to close single tags (
).
// This is only for full CommonMark compatibility.
xhtmlOut: false,
// Convert '\n' in paragraphs into
breaks: false,
// CSS language prefix for fenced blocks. Can be
// useful for external highlighters.
langPrefix: 'language-',
// Autoconvert URL-like text to links
linkify: false,
// Enable some language-neutral replacement + quotes beautification
// For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.mjs
typographer: true,
// Double + single quotes replacement pairs, when typographer enabled,
// and smartquotes on. Could be either a String or an Array.
//
// For example, you can use '«»„“' for Russian, '„“‚‘' for German,
// and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
quotes: '“”‘’',
// Highlighter function. Should return escaped HTML,
// or '' if the source string is not changed and should be escaped externally.
// If result starts with
{
this.renderedImage = img;
this.dirty = true;
this.canvas?.renderAll();
});
}
getObject() {
const entityKeys: string[] = EntityKeys;
const result: Record = {};
entityKeys.forEach((key) => {
if (key in this) {
result[key] = (this as any)[key];
}
});
return result;
}
private parseHtmlToImage(html: string): Promise {
return new Promise((resolve) => {
const blob = new Blob([html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const iframe = document.createElement('iframe');
iframe.style.width = `${this.width}px`;
iframe.style.height = `${this.height}px`;
iframe.style.visibility = 'hidden';
iframe.style.position = 'absolute';
iframe.style.left = '-9999px';
document.body.appendChild(iframe);
iframe.onload = async () => {
if (iframe.contentDocument) {
const style = iframe.contentDocument.createElement('style');
style.textContent = `
body {
font-family: Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
color: #333;
padding: 20px;
margin: 0;
width: ${this.width}px;
height: ${this.height}px;
}
h1, h2, h3, h4, h5, h6 { margin-top: 0; }
ul, ol { padding-left: 20px; }
/* Code block styling */
code {
background-color: #f5f5f5; /* Slightly lighter background for better contrast */
padding: 3px 5px; /* More padding for better spacing */
border-radius: 5px; /* Smoother border radius */
font-family: 'Courier New', Courier, monospace; /* Monospace font for code */
}
pre {
background-color: #f5f5f5; /* Matching background color with inline code */
padding: 12px; /* Slightly more padding for comfort */
border-radius: 5px; /* Smoother border radius */
overflow-x: auto; /* Ensure horizontal scroll for long code lines */
font-family: 'Courier New', Courier, monospace; /* Monospace font for code */
}
pre code {
display: block;
}
/* Table styling */
table {
border-collapse: collapse;
margin-bottom: 1em; /* Add some margin below tables */
font-family: Arial, sans-serif; /* Better font for readability */
}
table, th, td {
border: 1px solid #ddd; /* Lighter border color for a cleaner look */
}
th, td {
padding: 10px; /* More padding for better spacing */
text-align: left;
}
th {
background-color: #f2f2f2; /* Slight background color for headers */
font-weight: bold; /* Bold headers */
}
td {
background-color: #fff; /* Ensure a white background for table cells */
}
/* Additional table row hover effect */
tr:hover {
background-color: #f1f1f1; /* Highlight row on hover */
}
`;
iframe.contentDocument.head.appendChild(style);
}
iframe.contentDocument!.body.innerHTML = html;
await new Promise((resolve) => setTimeout(resolve, 100)); // Small delay to ensure styles are applied
const canvas = await html2canvas(
iframe.contentDocument?.body as HTMLElement
);
const img = await FabricImage.fromURL(canvas.toDataURL());
img.set({
left: this.left,
top: this.top,
width: this.width,
height: this.height,
scaleX: 1,
scaleY: 1,
});
resolve(img);
document.body.removeChild(iframe);
URL.revokeObjectURL(url);
};
iframe.src = url;
});
}
setMarkdown(newMarkdownText: string) {
this.markdownText = newMarkdownText;
if (this.isEditing) {
this.text = this.markdownText;
this.dirty = true;
} else {
this.renderMarkdown();
}
this.canvas?.renderAll();
}
_render(ctx: CanvasRenderingContext2D) {
if (this.isEditing) {
super._render(ctx);
} else if (this.renderedImage) {
this.renderedImage._render(ctx);
}
}
}
export { XMarkdown };
classRegistry.setClass(XMarkdown);