diff --git a/apps/web/src/common/export.ts b/apps/web/src/common/export.ts index 64fc83cf1..7e6273a36 100644 --- a/apps/web/src/common/export.ts +++ b/apps/web/src/common/export.ts @@ -136,3 +136,28 @@ export async function exportNotes( } }); } + +function renderMath(content: string) { + const katex = require("katex"); + require("katex/contrib/mhchem"); + + const div = document.createElement("div"); + div.innerHTML = content; + + const mathBlocks = div.querySelectorAll(".math-block.math-node"); + const mathInlines = div.querySelectorAll(".math-inline.math-node"); + + for (const mathBlock of mathBlocks) { + const text = mathBlock.textContent; + mathBlock.innerHTML = katex.renderToString(text, { + displayMode: true, + output: "mathml" + }); + } + for (const mathInline of mathInlines) { + const text = mathInline.textContent; + mathInline.innerHTML = katex.renderToString(text, { output: "mathml" }); + } + + return div.innerHTML; +} diff --git a/packages/core/models/note.js b/packages/core/models/note.js index e6ea807d4..2ab445f52 100644 --- a/packages/core/models/note.js +++ b/packages/core/models/note.js @@ -120,7 +120,7 @@ export default class Note { case "html": templateData.content = rawHTML || content.toHTML(); return template - ? HTMLBuilder.buildHTML(templateData) + ? await HTMLBuilder.buildHTML(templateData) : templateData.content; case "txt": templateData.content = rawHTML || content.toTXT(); diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 812659c5c..3a5988858 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -18,9 +18,11 @@ "entities": "^4.3.1", "html-to-text": "^9.0.5", "htmlparser2": "^8.0.1", + "katex": "^0.16.8", "linkedom": "^0.14.17", "liqe": "^1.13.0", "mime-db": "1.52.0", + "prismjs": "^1.29.0", "qclone": "^1.2.0", "spark-md5": "^3.0.2" }, @@ -32,6 +34,8 @@ "@notesnook/crypto": "file:../crypto", "@types/html-to-text": "^9.0.0", "@types/jest": "^28.1.6", + "@types/katex": "^0.16.1", + "@types/prismjs": "^1.26.0", "@types/showdown": "^2.0.0", "abortcontroller-polyfill": "^1.7.3", "analyze-es6-modules": "^0.6.2", @@ -2743,6 +2747,12 @@ "pretty-format": "^28.0.0" } }, + "node_modules/@types/katex": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.1.tgz", + "integrity": "sha512-cwglq2A63Yk082CQk0t8LIoDhZAVgJqkumLyk3grpg3K8sevaDW//Qsspmxj9Sf+97biqt79CfAlPrvizHlP0w==", + "dev": true + }, "node_modules/@types/node": { "version": "18.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", @@ -2755,6 +2765,12 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "node_modules/@types/prismjs": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz", + "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==", + "dev": true + }, "node_modules/@types/showdown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz", @@ -7943,6 +7959,29 @@ "node": ">=6" } }, + "node_modules/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -8587,6 +8626,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -11630,6 +11677,12 @@ "pretty-format": "^28.0.0" } }, + "@types/katex": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.1.tgz", + "integrity": "sha512-cwglq2A63Yk082CQk0t8LIoDhZAVgJqkumLyk3grpg3K8sevaDW//Qsspmxj9Sf+97biqt79CfAlPrvizHlP0w==", + "dev": true + }, "@types/node": { "version": "18.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", @@ -11642,6 +11695,12 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "@types/prismjs": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz", + "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==", + "dev": true + }, "@types/showdown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz", @@ -15296,7 +15355,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "28.0.2", @@ -15970,7 +16030,8 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -15992,6 +16053,21 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "requires": { + "commander": "^8.3.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + } + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -16483,6 +16559,11 @@ } } }, + "prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==" + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -17230,7 +17311,8 @@ "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} }, "xml-name-validator": { "version": "4.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index d51262e4a..5c892fa0e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,6 +15,8 @@ "@notesnook/crypto": "file:../crypto", "@types/html-to-text": "^9.0.0", "@types/jest": "^28.1.6", + "@types/katex": "^0.16.1", + "@types/prismjs": "^1.26.0", "@types/showdown": "^2.0.0", "abortcontroller-polyfill": "^1.7.3", "analyze-es6-modules": "^0.6.2", @@ -45,9 +47,11 @@ "entities": "^4.3.1", "html-to-text": "^9.0.5", "htmlparser2": "^8.0.1", + "katex": "^0.16.8", "linkedom": "^0.14.17", "liqe": "^1.13.0", "mime-db": "1.52.0", + "prismjs": "^1.29.0", "qclone": "^1.2.0", "spark-md5": "^3.0.2" }, diff --git a/packages/core/utils/templates/html/builder.js b/packages/core/utils/templates/html/builder.js index 873753a31..60d387a78 100644 --- a/packages/core/utils/templates/html/builder.js +++ b/packages/core/utils/templates/html/builder.js @@ -17,10 +17,69 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { parseHTML } from "../../html-parser"; import HTMLTemplate from "./template"; -function buildHTML(templateData) { - return HTMLTemplate(templateData); +const LANGUAGE_REGEX = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i; + +async function buildHTML(templateData) { + return HTMLTemplate(await preprocessHTML(templateData)); +} + +async function preprocessHTML(templateData) { + const { content } = templateData; + const doc = parseHTML( + content.replaceAll(/<\/p>/gm, "

") + ); + + const mathBlocks = doc.querySelectorAll(".math-block.math-node"); + const mathInlines = doc.querySelectorAll(".math-inline.math-node"); + + if (mathBlocks.length || mathInlines.length) { + const { default: katex } = require("katex"); + require("katex/contrib/mhchem"); + + for (const mathBlock of mathBlocks) { + const text = mathBlock.textContent; + mathBlock.innerHTML = katex.renderToString(text, { + displayMode: true, + output: "mathml" + }); + } + + for (const mathInline of mathInlines) { + const text = mathInline.textContent; + mathInline.innerHTML = katex.renderToString(text, { output: "mathml" }); + } + templateData.hasMathBlocks = true; + } + + const codeblocks = doc.querySelectorAll("pre > code"); + if (codeblocks.length) { + const { default: prismjs } = require("prismjs"); + prismjs.register = () => {}; + for (const codeblock of codeblocks) { + const language = LANGUAGE_REGEX.exec( + codeblock.parentElement.className + )?.[1]; + if (!language) continue; + const { + default: grammar + } = require(`../../../../editor/languages/${language}.js`); + + grammar(prismjs); + + codeblock.innerHTML = prismjs.highlight( + codeblock.textContent, + prismjs.languages[language], + language + ); + } + templateData.hasCodeblock = true; + } + + templateData.content = doc.body.innerHTML; + return templateData; } export default { buildHTML }; diff --git a/packages/core/utils/templates/html/template.js b/packages/core/utils/templates/html/template.js index 7f63d5375..19351cd10 100644 --- a/packages/core/utils/templates/html/template.js +++ b/packages/core/utils/templates/html/template.js @@ -30,6 +30,8 @@ const template = (data) => ` ${data.tags ? `` : ""} + +