diff --git a/src/core/UAIEditor.ts b/src/core/UAIEditor.ts index a826354..8552ebb 100644 --- a/src/core/UAIEditor.ts +++ b/src/core/UAIEditor.ts @@ -24,6 +24,7 @@ import i18next from "i18next"; import { zh } from "../i18n/zh.ts"; import { Resource } from "i18next"; import { allExtensions } from "./UAIExtensions.ts"; +import { markdownToHtml } from "../utils/MarkdownUtil.ts"; import { Uploader } from "../utils/FileUploader.ts"; self.MonacoEnvironment = { @@ -307,7 +308,7 @@ export class UAIEditor { this.innerEditor = new InnerEditor(this, { element: this.editor.editorContainer, - content, + content: markdownToHtml(content ?? ""), extensions: allExtensions(this, this.options), onCreate: (props) => this.onCreate(props), onTransaction: (props) => this.onTransaction(props), diff --git a/src/utils/MarkdownUtil.ts b/src/utils/MarkdownUtil.ts new file mode 100644 index 0000000..4dcd9c1 --- /dev/null +++ b/src/utils/MarkdownUtil.ts @@ -0,0 +1,103 @@ +// Copyright (c) 2024-present AI-Labs + +import markdownItKatex from '@vscode/markdown-it-katex'; +import MarkdownIt from 'markdown-it'; +import container from 'markdown-it-container'; + +import TurndownService from 'turndown'; + +import hljs from 'highlight.js'; + +const md = MarkdownIt({ + html: true, + linkify: true, + typographer: true +}); + + +const containerClasses = ['info', 'tip', 'warning', 'danger']; +containerClasses.forEach(name => { + md.use(container, name, { + validate: function (params: any) { + params = params.trim() + if (params === '') return true; + return params.indexOf(name) >= 0; + }, + render: function (tokens: any, idx: any) { + if (tokens[idx].nesting === 1) { + return `
${hljs.highlight(content, { language }).value}${tail}`
+ } else {
+ return `${md.utils.escapeHtml(content)}`
+ }
+}
+
+//options https://github.com/mixmark-io/turndown?tab=readme-ov-file#options
+const turndownService = new TurndownService({
+ headingStyle: 'atx',
+ hr: '---',
+ bulletListMarker: '-',
+ codeBlockStyle: 'fenced',
+ fence: '```',
+ emDelimiter: '_',
+ strongDelimiter: '**',
+ linkStyle: 'inlined',
+ linkReferenceStyle: 'full',
+ preformattedCode: false,
+});
+
+turndownService.keep((node: any) => {
+ return !(node && node.nodeName === "DIV");
+});
+
+export const markdownToHtml = (markdown: string) => {
+ if (!markdown) return markdown;
+ const renderHtml = md.render(markdown).trim();
+ if (!renderHtml) return markdown;
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(renderHtml, 'text/html');
+ const lis = doc.querySelectorAll("li");
+
+ //"tiptap" does not support empty list items. Here to fill in the gaps
+ if (lis) lis.forEach(li => {
+ if (!li.innerHTML) li.innerHTML = ""
+ })
+
+ let html = '';
+ for (let i = 0; i < doc.body.children.length; i++) {
+ const element = doc.body.children[i];
+ if (i == 0 && element.tagName === "P") {
+ html += element.innerHTML;
+ } else {
+ html += element.querySelector("img")
+ ? element.innerHTML : element.outerHTML;
+ }
+ }
+ return html;
+}
+
+
+export const htmlToMarkdown = (html: string) => {
+ if (!html) return html;
+ return turndownService.turndown(html)
+}