806 lines
30 KiB
JavaScript
806 lines
30 KiB
JavaScript
/*
|
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
|
if you want to view the source, please visit the github repository of this plugin
|
|
*/
|
|
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// main.ts
|
|
var main_exports = {};
|
|
__export(main_exports, {
|
|
default: () => TypstMathPlugin
|
|
});
|
|
module.exports = __toCommonJS(main_exports);
|
|
var import_child_process = require("child_process");
|
|
var import_fs = require("fs");
|
|
var import_obsidian = require("obsidian");
|
|
var os = __toESM(require("os"));
|
|
var path = __toESM(require("path"));
|
|
var DEFAULT_SETTINGS = {
|
|
typstPath: "typst",
|
|
enableInlineMath: true,
|
|
enableDisplayMath: true,
|
|
debugMode: false,
|
|
showDetailedErrors: false,
|
|
useWatchMode: false,
|
|
enableLivePreview: true,
|
|
forceSyncCompile: false,
|
|
syncOnlyDuringExport: true
|
|
};
|
|
var TypstMathPlugin = class extends import_obsidian.Plugin {
|
|
constructor() {
|
|
super(...arguments);
|
|
// Internal flag set when a print/export flow is active
|
|
this.exportInProgress = false;
|
|
// Hold original window.print so we can restore it on unload
|
|
this._originalWindowPrint = null;
|
|
// Keep reference to matchMedia listener so we can remove it
|
|
this._printMediaListener = null;
|
|
this.watchers = /* @__PURE__ */ new Map();
|
|
// Store typst source content for blocks that are pending compilation
|
|
this.pendingTypstContent = /* @__PURE__ */ new Map();
|
|
this.renderCache = /* @__PURE__ */ new Map();
|
|
this.handleBeforePrint = () => {
|
|
this.exportInProgress = true;
|
|
if (this.settings.debugMode)
|
|
console.log("Typst plugin: export/print started");
|
|
};
|
|
this.handleAfterPrint = () => {
|
|
this.exportInProgress = false;
|
|
if (this.settings.debugMode)
|
|
console.log("Typst plugin: export/print finished");
|
|
};
|
|
}
|
|
async onload() {
|
|
var _a;
|
|
await this.loadSettings();
|
|
this.tempDir = path.join(os.tmpdir(), "obsidian-typst-math");
|
|
await this.ensureTempDir();
|
|
this.registerMarkdownCodeBlockProcessor(
|
|
"math",
|
|
this.processMathBlock.bind(this)
|
|
);
|
|
this.registerMarkdownCodeBlockProcessor(
|
|
"typst",
|
|
this.processMathBlock.bind(this)
|
|
);
|
|
await (0, import_obsidian.loadMathJax)();
|
|
if (!globalThis.MathJax) {
|
|
new import_obsidian.Notice("MathJax failed to load. Math rendering may not work.");
|
|
console.error("MathJax failed to load.");
|
|
} else {
|
|
this.originalTex2chtml = globalThis.MathJax.tex2chtml;
|
|
globalThis.MathJax.tex2chtml = (latex, options) => {
|
|
return this.renderWithTypst(latex, options);
|
|
};
|
|
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView);
|
|
if (activeView) {
|
|
(_a = activeView.previewMode) == null ? void 0 : _a.rerender(true);
|
|
if (this.settings.enableLivePreview) {
|
|
const editor = activeView.editor;
|
|
if (editor) {
|
|
const cursor = editor.getCursor();
|
|
editor.setCursor(cursor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (typeof window !== "undefined") {
|
|
window.addEventListener("beforeprint", this.handleBeforePrint);
|
|
window.addEventListener("afterprint", this.handleAfterPrint);
|
|
try {
|
|
if (typeof window.print === "function") {
|
|
this._originalWindowPrint = window.print.bind(window);
|
|
window.print = (...args) => {
|
|
this.handleBeforePrint();
|
|
try {
|
|
const res = this._originalWindowPrint(...args);
|
|
setTimeout(() => this.handleAfterPrint(), 1500);
|
|
return res;
|
|
} catch (e) {
|
|
this.handleAfterPrint();
|
|
throw e;
|
|
}
|
|
};
|
|
}
|
|
} catch (e) {
|
|
if (this.settings.debugMode)
|
|
console.warn("Failed to patch window.print", e);
|
|
}
|
|
try {
|
|
const mql = window.matchMedia && window.matchMedia("print");
|
|
if (mql) {
|
|
this._printMediaListener = (ev) => {
|
|
if (ev.matches)
|
|
this.handleBeforePrint();
|
|
else
|
|
this.handleAfterPrint();
|
|
};
|
|
if (typeof mql.addEventListener === "function") {
|
|
mql.addEventListener("change", this._printMediaListener);
|
|
} else if (typeof mql.addListener === "function") {
|
|
mql.addListener(this._printMediaListener);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (this.settings.debugMode)
|
|
console.warn("Failed to attach matchMedia print listener", e);
|
|
}
|
|
}
|
|
this.addSettingTab(new TypstMathSettingTab(this.app, this));
|
|
this.addCommand({
|
|
id: "typst-prepare-export",
|
|
name: "Typst: Prepare export (sync compile all math)",
|
|
callback: () => {
|
|
void this.prepareExport();
|
|
}
|
|
});
|
|
console.log("Typst Math Plugin loaded");
|
|
}
|
|
async onunload() {
|
|
var _a;
|
|
if (this.originalTex2chtml && globalThis.MathJax) {
|
|
globalThis.MathJax.tex2chtml = this.originalTex2chtml;
|
|
(_a = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView)) == null ? void 0 : _a.previewMode.rerender(true);
|
|
}
|
|
for (const [id, process] of this.watchers) {
|
|
process.kill();
|
|
}
|
|
this.watchers.clear();
|
|
try {
|
|
await import_fs.promises.rm(this.tempDir, { recursive: true, force: true });
|
|
} catch (error) {
|
|
console.error("Error cleaning up temp directory:", error);
|
|
}
|
|
try {
|
|
if (typeof window !== "undefined") {
|
|
window.removeEventListener("beforeprint", this.handleBeforePrint);
|
|
window.removeEventListener("afterprint", this.handleAfterPrint);
|
|
if (this._printMediaListener && window.matchMedia) {
|
|
const mql = window.matchMedia("print");
|
|
if (mql) {
|
|
if (typeof mql.removeEventListener === "function") {
|
|
mql.removeEventListener("change", this._printMediaListener);
|
|
} else if (typeof mql.removeListener === "function") {
|
|
mql.removeListener(this._printMediaListener);
|
|
}
|
|
}
|
|
}
|
|
if (this._originalWindowPrint) {
|
|
try {
|
|
window.print = this._originalWindowPrint;
|
|
} catch (e) {
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (this.settings.debugMode)
|
|
console.warn("Error restoring print handlers", e);
|
|
}
|
|
console.log("Typst Math Plugin unloaded");
|
|
}
|
|
async loadSettings() {
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
}
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
async ensureTempDir() {
|
|
try {
|
|
await import_fs.promises.mkdir(this.tempDir, { recursive: true });
|
|
} catch (error) {
|
|
console.error("Error creating temp directory:", error);
|
|
}
|
|
}
|
|
formatError(error, isInline = false) {
|
|
const errorMsg = typeof error === "string" ? error : error.message;
|
|
let friendlyMsg = "There was an error with your Typst code";
|
|
if (errorMsg.includes("ENOENT") || errorMsg.includes("not found")) {
|
|
friendlyMsg = "Typst CLI not found";
|
|
} else if (errorMsg.includes("syntax error") || errorMsg.includes("unexpected")) {
|
|
friendlyMsg = "Syntax error in Typst code";
|
|
} else if (errorMsg.includes("undefined")) {
|
|
friendlyMsg = "Undefined symbol or function";
|
|
} else if (errorMsg.includes("type")) {
|
|
friendlyMsg = "Type error in expression";
|
|
}
|
|
const errorClass = isInline ? "typst-error-inline" : "typst-error";
|
|
if (this.settings.showDetailedErrors) {
|
|
return `<span class="${errorClass}">${friendlyMsg}</span><span class="typst-error-details">${errorMsg}</span>`;
|
|
} else {
|
|
return `<span class="${errorClass}">${friendlyMsg}</span>`;
|
|
}
|
|
}
|
|
async processMathBlock(source, el, ctx) {
|
|
const blockId = this.generateBlockId(source, ctx.sourcePath);
|
|
if (this.renderCache.has(blockId)) {
|
|
el.empty();
|
|
const container2 = el.createDiv({ cls: "typst-math-container" });
|
|
const cached = this.renderCache.get(blockId);
|
|
container2.innerHTML = cached != null ? cached : "";
|
|
return;
|
|
}
|
|
el.empty();
|
|
const container = el.createDiv({ cls: "typst-math-container" });
|
|
container.innerHTML = '<div class="typst-loading">Rendering with Typst...</div>';
|
|
container.setAttribute("data-typst-blockid", blockId);
|
|
try {
|
|
const typstContent = this.wrapInTypstDocument(source);
|
|
const typstFile = path.join(this.tempDir, `${blockId}.typ`);
|
|
const htmlFile = path.join(this.tempDir, `${blockId}.html`);
|
|
this.pendingTypstContent.set(blockId, typstContent);
|
|
await import_fs.promises.writeFile(typstFile, typstContent, "utf-8");
|
|
if (this.settings.useWatchMode) {
|
|
await this.renderTypstWithWatch(typstFile, htmlFile, container, blockId);
|
|
} else {
|
|
await this.renderTypstToHtml(typstFile, htmlFile, container, blockId);
|
|
}
|
|
} catch (error) {
|
|
container.innerHTML = this.formatError(error, false);
|
|
if (this.settings.debugMode) {
|
|
console.error("Typst rendering error:", error);
|
|
}
|
|
}
|
|
}
|
|
renderWithTypst(latex, options) {
|
|
const isBlock = options.display || false;
|
|
if (isBlock && !this.settings.enableDisplayMath) {
|
|
return this.originalTex2chtml(latex, options);
|
|
}
|
|
if (!isBlock && !this.settings.enableInlineMath) {
|
|
return this.originalTex2chtml(latex, options);
|
|
}
|
|
if (this.hasLatexCommand(latex)) {
|
|
}
|
|
const typstContent = this.convertLatexToTypst(latex);
|
|
const blockId = this.generateBlockId(typstContent, `math-${Date.now()}`);
|
|
const container = document.createElement(isBlock ? "div" : "span");
|
|
container.className = isBlock ? "typst-math-container" : "typst-math-inline";
|
|
container.innerHTML = '<span class="typst-loading">Rendering with Typst...</span>';
|
|
try {
|
|
container.setAttribute("data-typst-blockid", blockId);
|
|
this.pendingTypstContent.set(blockId, typstContent);
|
|
} catch (e) {
|
|
}
|
|
this.renderTypstMath(typstContent, container, blockId, isBlock);
|
|
return container;
|
|
}
|
|
hasLatexCommand(expr) {
|
|
return false;
|
|
}
|
|
convertLatexToTypst(latex) {
|
|
let content = latex.trim();
|
|
content = content.replace(/^\$\$/, "").replace(/\$\$$/, "");
|
|
content = content.replace(/^\$/, "").replace(/\$$/, "");
|
|
const conversions = [
|
|
// Fractions
|
|
[/\\frac\{([^}]+)\}\{([^}]+)\}/g, "($1)/($2)"],
|
|
// Superscripts and subscripts (already mostly compatible)
|
|
// Greek letters (mostly the same, just remove backslash)
|
|
[/\\alpha\b/g, "alpha"],
|
|
[/\\beta\b/g, "beta"],
|
|
[/\\gamma\b/g, "gamma"],
|
|
[/\\delta\b/g, "delta"],
|
|
[/\\epsilon\b/g, "epsilon"],
|
|
[/\\zeta\b/g, "zeta"],
|
|
[/\\eta\b/g, "eta"],
|
|
[/\\theta\b/g, "theta"],
|
|
[/\\iota\b/g, "iota"],
|
|
[/\\kappa\b/g, "kappa"],
|
|
[/\\lambda\b/g, "lambda"],
|
|
[/\\mu\b/g, "mu"],
|
|
[/\\nu\b/g, "nu"],
|
|
[/\\xi\b/g, "xi"],
|
|
[/\\pi\b/g, "pi"],
|
|
[/\\rho\b/g, "rho"],
|
|
[/\\sigma\b/g, "sigma"],
|
|
[/\\tau\b/g, "tau"],
|
|
[/\\phi\b/g, "phi"],
|
|
[/\\chi\b/g, "chi"],
|
|
[/\\psi\b/g, "psi"],
|
|
[/\\omega\b/g, "omega"],
|
|
// Capital Greek
|
|
[/\\Gamma\b/g, "Gamma"],
|
|
[/\\Delta\b/g, "Delta"],
|
|
[/\\Theta\b/g, "Theta"],
|
|
[/\\Lambda\b/g, "Lambda"],
|
|
[/\\Xi\b/g, "Xi"],
|
|
[/\\Pi\b/g, "Pi"],
|
|
[/\\Sigma\b/g, "Sigma"],
|
|
[/\\Phi\b/g, "Phi"],
|
|
[/\\Psi\b/g, "Psi"],
|
|
[/\\Omega\b/g, "Omega"],
|
|
// Common functions
|
|
[/\\sin\b/g, "sin"],
|
|
[/\\cos\b/g, "cos"],
|
|
[/\\tan\b/g, "tan"],
|
|
[/\\log\b/g, "log"],
|
|
[/\\ln\b/g, "ln"],
|
|
[/\\exp\b/g, "exp"],
|
|
// Sums and integrals
|
|
[/\\sum/g, "sum"],
|
|
[/\\prod/g, "product"],
|
|
[/\\int/g, "integral"],
|
|
[/\\infty\b/g, "oo"],
|
|
// Arrows
|
|
[/\\rightarrow\b/g, "->"],
|
|
[/\\leftarrow\b/g, "<-"],
|
|
[/\\Rightarrow\b/g, "=>"],
|
|
[/\\Leftarrow\b/g, "<="],
|
|
// Operators
|
|
[/\\times\b/g, "times"],
|
|
[/\\cdot\b/g, "dot"],
|
|
[/\\pm\b/g, "plus.minus"],
|
|
[/\\mp\b/g, "minus.plus"],
|
|
// Special sets
|
|
[/\\mathbb\{R\}/g, "RR"],
|
|
[/\\mathbb\{N\}/g, "NN"],
|
|
[/\\mathbb\{Z\}/g, "ZZ"],
|
|
[/\\mathbb\{Q\}/g, "QQ"],
|
|
[/\\mathbb\{C\}/g, "CC"],
|
|
// Limits
|
|
[/\\lim/g, "lim"],
|
|
[/\\to\b/g, "->"],
|
|
// Parentheses (mostly the same)
|
|
[/\\left\(/g, "("],
|
|
[/\\right\)/g, ")"],
|
|
[/\\left\[/g, "["],
|
|
[/\\right\]/g, "]"],
|
|
[/\\left\{/g, "{"],
|
|
[/\\right\}/g, "}"],
|
|
// Sqrt
|
|
[/\\sqrt\{([^}]+)\}/g, "sqrt($1)"],
|
|
// Text
|
|
[/\\text\{([^}]+)\}/g, '"$1"']
|
|
];
|
|
for (const [pattern, replacement] of conversions) {
|
|
content = content.replace(pattern, replacement);
|
|
}
|
|
return content.trim();
|
|
}
|
|
async renderTypstMath(mathContent, container, blockId, isBlock) {
|
|
if (this.renderCache.has(blockId)) {
|
|
container.innerHTML = this.renderCache.get(blockId);
|
|
return;
|
|
}
|
|
try {
|
|
const wrappedContent = `$ ${mathContent} $`;
|
|
const typstContent = this.wrapInTypstDocument(wrappedContent, isBlock);
|
|
const typstFile = path.join(this.tempDir, `${blockId}.typ`);
|
|
const htmlFile = path.join(this.tempDir, `${blockId}.html`);
|
|
this.pendingTypstContent.set(blockId, typstContent);
|
|
await import_fs.promises.writeFile(typstFile, typstContent, "utf-8");
|
|
await this.renderTypstToHtml(typstFile, htmlFile, container, blockId);
|
|
} catch (error) {
|
|
container.innerHTML = this.formatError(error, true);
|
|
if (this.settings.debugMode) {
|
|
console.error("Typst rendering error:", error);
|
|
}
|
|
}
|
|
}
|
|
// Synchronously compile a single block (used by prepareExport)
|
|
async syncCompileBlock(blockId) {
|
|
try {
|
|
const typstContent = this.pendingTypstContent.get(blockId);
|
|
if (!typstContent)
|
|
return;
|
|
const typstFile = path.join(this.tempDir, `${blockId}.typ`);
|
|
const htmlFile = path.join(this.tempDir, `${blockId}.html`);
|
|
(0, import_fs.writeFileSync)(typstFile, typstContent, "utf-8");
|
|
const args = [
|
|
"compile",
|
|
typstFile,
|
|
htmlFile,
|
|
"--features",
|
|
"html",
|
|
"--format",
|
|
"html"
|
|
];
|
|
if (this.settings.debugMode) {
|
|
console.log("Typst sync compile (prepareExport):", this.settings.typstPath, args.join(" "));
|
|
}
|
|
const res = (0, import_child_process.spawnSync)(this.settings.typstPath, args, { encoding: "utf-8" });
|
|
if (res.error || res.status !== 0) {
|
|
const errStr = res.stderr || res.error && res.error.message || `Typst exited ${res.status}`;
|
|
throw new Error(errStr);
|
|
}
|
|
const html = (0, import_fs.readFileSync)(htmlFile, "utf-8");
|
|
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
const content = bodyMatch ? bodyMatch[1] : html;
|
|
this.renderCache.set(blockId, content);
|
|
try {
|
|
const el = document.querySelector(`[data-typst-blockid="${blockId}"]`);
|
|
if (el instanceof HTMLElement)
|
|
el.innerHTML = content;
|
|
} catch (e) {
|
|
}
|
|
} catch (error) {
|
|
if (this.settings.debugMode)
|
|
console.error("Error in syncCompileBlock:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
// Prepare the document for export by sync-compiling all pending Typst blocks
|
|
async prepareExport() {
|
|
const pending = Array.from(this.pendingTypstContent.keys());
|
|
if (pending.length === 0) {
|
|
new import_obsidian.Notice("No pending Typst math blocks to prepare");
|
|
return;
|
|
}
|
|
new import_obsidian.Notice(`Preparing ${pending.length} Typst math blocks for export...`);
|
|
const prevForce = this.settings.forceSyncCompile;
|
|
const prevSyncOnly = this.settings.syncOnlyDuringExport;
|
|
this.settings.forceSyncCompile = true;
|
|
this.settings.syncOnlyDuringExport = false;
|
|
this.exportInProgress = true;
|
|
let success = 0;
|
|
for (const id of pending) {
|
|
try {
|
|
await this.syncCompileBlock(id);
|
|
success++;
|
|
} catch (e) {
|
|
console.error("prepareExport: failed to compile block", id, e);
|
|
}
|
|
}
|
|
this.settings.forceSyncCompile = prevForce;
|
|
this.settings.syncOnlyDuringExport = prevSyncOnly;
|
|
this.exportInProgress = false;
|
|
new import_obsidian.Notice(`Prepared ${success}/${pending.length} Typst math blocks for export`);
|
|
}
|
|
wrapInTypstDocument(mathContent, isBlock = true) {
|
|
const sizeConfig = isBlock ? "16pt" : "14pt";
|
|
return `
|
|
#set text(size: ${sizeConfig})
|
|
#show math.equation: html.frame
|
|
#show math.equation.where(block: false): box
|
|
|
|
${mathContent}
|
|
`;
|
|
}
|
|
generateBlockId(source, sourcePath) {
|
|
const hash = this.simpleHash(source + sourcePath);
|
|
return `block-${hash}`;
|
|
}
|
|
simpleHash(str) {
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str.charCodeAt(i);
|
|
hash = (hash << 5) - hash + char;
|
|
hash = hash & hash;
|
|
}
|
|
return Math.abs(hash).toString(36);
|
|
}
|
|
/**
|
|
* Parse the full Typst-generated HTML and return a cleaned HTML string.
|
|
* - Merges paginated pages if present
|
|
* - Removes placeholder ellipsis-only nodes
|
|
* - Strips scripts/styles that could interfere
|
|
*/
|
|
parseTypstHtml(html) {
|
|
try {
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(html, "text/html");
|
|
doc.querySelectorAll("script, style").forEach((n) => n.remove());
|
|
const pageSelectors = [".typst-page", ".page", ".typst-doc", "#content", "body > div"];
|
|
let parts = [];
|
|
const typstDoc = doc.querySelector(".typst-doc");
|
|
if (typstDoc) {
|
|
const children = Array.from(typstDoc.children);
|
|
if (children.length > 1) {
|
|
for (const c of children)
|
|
parts.push(c.innerHTML);
|
|
} else {
|
|
parts.push(typstDoc.innerHTML);
|
|
}
|
|
} else {
|
|
for (const sel of pageSelectors) {
|
|
const nodes = Array.from(doc.querySelectorAll(sel));
|
|
if (nodes.length > 1) {
|
|
for (const n of nodes)
|
|
parts.push(n.innerHTML);
|
|
break;
|
|
} else if (nodes.length === 1 && parts.length === 0) {
|
|
parts.push(nodes[0].innerHTML);
|
|
}
|
|
}
|
|
}
|
|
if (parts.length === 0) {
|
|
const bodyHtml = doc.body ? doc.body.innerHTML : html;
|
|
parts = [bodyHtml];
|
|
}
|
|
parts = parts.map((p) => {
|
|
var _a;
|
|
const partDoc = parser.parseFromString(`<div>${p}</div>`, "text/html");
|
|
const walker = document.createTreeWalker(partDoc.body, NodeFilter.SHOW_ELEMENT, null);
|
|
const toRemove = [];
|
|
let node = walker.nextNode();
|
|
while (node) {
|
|
const el = node;
|
|
const txt = (_a = el.textContent) == null ? void 0 : _a.trim();
|
|
if (txt && (/^\.{3,}$/.test(txt) || /^…+$/.test(txt) || txt === "\u22EF")) {
|
|
toRemove.push(el);
|
|
}
|
|
node = walker.nextNode();
|
|
}
|
|
toRemove.forEach((e) => e.remove());
|
|
return partDoc.body.innerHTML;
|
|
});
|
|
const joined = parts.map((p, i) => `<div class="typst-merged-page">${p}</div>`).join('\n<hr class="typst-page-break">\n');
|
|
return joined;
|
|
} catch (e) {
|
|
return html;
|
|
}
|
|
}
|
|
async renderTypstToHtml(typstFile, htmlFile, container, blockId) {
|
|
const useSync = this.settings.forceSyncCompile || this.settings.syncOnlyDuringExport && this.exportInProgress;
|
|
if (useSync) {
|
|
try {
|
|
const args = [
|
|
"compile",
|
|
typstFile,
|
|
htmlFile,
|
|
"--features",
|
|
"html",
|
|
"--format",
|
|
"html"
|
|
];
|
|
if (this.settings.debugMode) {
|
|
console.log("Running Typst (sync):", this.settings.typstPath, args.join(" "));
|
|
}
|
|
const res = (0, import_child_process.spawnSync)(this.settings.typstPath, args, { encoding: "utf-8" });
|
|
if (res.error) {
|
|
const errMsg = res.error.message || String(res.error);
|
|
container.innerHTML = this.formatError(errMsg, false);
|
|
throw res.error;
|
|
}
|
|
if (res.status !== 0) {
|
|
const errStr = res.stderr || `Typst process exited with code ${res.status}`;
|
|
container.innerHTML = this.formatError(errStr, false);
|
|
throw new Error(errStr);
|
|
}
|
|
const html = (0, import_fs.readFileSync)(htmlFile, "utf-8");
|
|
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
const content = bodyMatch ? bodyMatch[1] : html;
|
|
this.renderCache.set(blockId, content);
|
|
container.innerHTML = content;
|
|
return Promise.resolve();
|
|
} catch (err) {
|
|
if (this.settings.debugMode)
|
|
console.error("Typst sync render error:", err);
|
|
return Promise.reject(err);
|
|
}
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
var _a;
|
|
if (this.watchers.has(blockId)) {
|
|
(_a = this.watchers.get(blockId)) == null ? void 0 : _a.kill();
|
|
this.watchers.delete(blockId);
|
|
}
|
|
const args = [
|
|
"compile",
|
|
typstFile,
|
|
htmlFile,
|
|
"--features",
|
|
"html",
|
|
"--format",
|
|
"html"
|
|
];
|
|
if (this.settings.debugMode) {
|
|
console.log(
|
|
"Running Typst command:",
|
|
this.settings.typstPath,
|
|
args.join(" ")
|
|
);
|
|
}
|
|
const typstProcess = (0, import_child_process.spawn)(this.settings.typstPath, args);
|
|
let stderr = "";
|
|
typstProcess.stderr.on("data", (data) => {
|
|
stderr += data.toString();
|
|
});
|
|
typstProcess.on("close", async (code) => {
|
|
if (code === 0) {
|
|
try {
|
|
const html = await import_fs.promises.readFile(htmlFile, "utf-8");
|
|
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
const content = bodyMatch ? bodyMatch[1] : html;
|
|
this.renderCache.set(blockId, content);
|
|
container.innerHTML = content;
|
|
resolve();
|
|
} catch (error) {
|
|
container.innerHTML = this.formatError(error, false);
|
|
reject(error);
|
|
}
|
|
} else {
|
|
const errorMsg = stderr || `Typst process exited with code ${code}`;
|
|
container.innerHTML = this.formatError(errorMsg, false);
|
|
reject(new Error(errorMsg));
|
|
}
|
|
});
|
|
typstProcess.on("error", (error) => {
|
|
container.innerHTML = this.formatError(error, false);
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
async renderTypstWithWatch(typstFile, htmlFile, container, blockId) {
|
|
return new Promise((resolve, reject) => {
|
|
var _a;
|
|
if (this.watchers.has(blockId)) {
|
|
(_a = this.watchers.get(blockId)) == null ? void 0 : _a.kill();
|
|
this.watchers.delete(blockId);
|
|
}
|
|
const args = [
|
|
"watch",
|
|
typstFile,
|
|
htmlFile,
|
|
"--features",
|
|
"html",
|
|
"--format",
|
|
"html"
|
|
];
|
|
if (this.settings.debugMode) {
|
|
console.log(
|
|
"Running Typst watch:",
|
|
this.settings.typstPath,
|
|
args.join(" ")
|
|
);
|
|
}
|
|
const typstProcess = (0, import_child_process.spawn)(this.settings.typstPath, args);
|
|
this.watchers.set(blockId, typstProcess);
|
|
let stderr = "";
|
|
let hasRendered = false;
|
|
const checkForUpdates = async () => {
|
|
try {
|
|
const html = await import_fs.promises.readFile(htmlFile, "utf-8");
|
|
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
const content = bodyMatch ? bodyMatch[1] : html;
|
|
this.renderCache.set(blockId, content);
|
|
container.innerHTML = content;
|
|
if (!hasRendered) {
|
|
hasRendered = true;
|
|
resolve();
|
|
}
|
|
} catch (error) {
|
|
if (this.settings.debugMode) {
|
|
console.log("Waiting for Typst to generate output...");
|
|
}
|
|
}
|
|
};
|
|
typstProcess.stderr.on("data", (data) => {
|
|
stderr += data.toString();
|
|
if (stderr.includes("error:")) {
|
|
container.innerHTML = this.formatError(stderr, false);
|
|
}
|
|
});
|
|
typstProcess.stdout.on("data", (data) => {
|
|
const output = data.toString();
|
|
if (this.settings.debugMode) {
|
|
console.log("Typst watch output:", output);
|
|
}
|
|
if (output.includes("written to") || output.includes("compiled")) {
|
|
setTimeout(checkForUpdates, 100);
|
|
}
|
|
});
|
|
setTimeout(checkForUpdates, 500);
|
|
typstProcess.on("close", (code) => {
|
|
this.watchers.delete(blockId);
|
|
if (code !== 0 && code !== null && !hasRendered) {
|
|
const errorMsg = stderr || `Typst watch exited with code ${code}`;
|
|
container.innerHTML = this.formatError(errorMsg, false);
|
|
reject(new Error(errorMsg));
|
|
}
|
|
});
|
|
typstProcess.on("error", (error) => {
|
|
this.watchers.delete(blockId);
|
|
container.innerHTML = this.formatError(error, false);
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
var TypstMathSettingTab = class extends import_obsidian.PluginSettingTab {
|
|
constructor(app, plugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
display() {
|
|
const { containerEl } = this;
|
|
containerEl.empty();
|
|
containerEl.createEl("h2", { text: "Typst Math Renderer Settings" });
|
|
new import_obsidian.Setting(containerEl).setName("Typst CLI path").setDesc('Path to the Typst executable (e.g., "typst" or full path)').addText(
|
|
(text) => text.setPlaceholder("typst").setValue(this.plugin.settings.typstPath).onChange(async (value) => {
|
|
this.plugin.settings.typstPath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Enable inline math").setDesc("Process inline math blocks ($...$)").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.enableInlineMath).onChange(async (value) => {
|
|
this.plugin.settings.enableInlineMath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Enable display math").setDesc("Process display math blocks ($$...$$)").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.enableDisplayMath).onChange(async (value) => {
|
|
this.plugin.settings.enableDisplayMath = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Debug mode").setDesc("Enable debug logging in the console").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.debugMode).onChange(async (value) => {
|
|
this.plugin.settings.debugMode = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Show detailed errors").setDesc("Display detailed Typst error messages (useful for debugging)").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.showDetailedErrors).onChange(async (value) => {
|
|
this.plugin.settings.showDetailedErrors = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Use watch mode").setDesc("Enable watch mode for code blocks (auto-recompile on changes) - experimental").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.useWatchMode).onChange(async (value) => {
|
|
this.plugin.settings.useWatchMode = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Force synchronous compile (for exports)").setDesc("Use a synchronous Typst compile which can improve reliability when exporting to PDF. This will block rendering while Typst runs.").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.forceSyncCompile).onChange(async (value) => {
|
|
this.plugin.settings.forceSyncCompile = value;
|
|
await this.plugin.saveSettings();
|
|
new import_obsidian.Notice("Force sync compile setting updated");
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Sync only during print/export").setDesc("If enabled, synchronous compilation will only be used during a print/export flow. Disable to always use sync compilation when enabled.").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.syncOnlyDuringExport).onChange(async (value) => {
|
|
this.plugin.settings.syncOnlyDuringExport = value;
|
|
await this.plugin.saveSettings();
|
|
new import_obsidian.Notice("Sync-only-during-export setting updated");
|
|
})
|
|
);
|
|
new import_obsidian.Setting(containerEl).setName("Enable live preview").setDesc("Render Typst math in live preview mode (works automatically via MathJax override)").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.enableLivePreview).onChange(async (value) => {
|
|
this.plugin.settings.enableLivePreview = value;
|
|
await this.plugin.saveSettings();
|
|
new import_obsidian.Notice("Reload Obsidian for this change to take full effect");
|
|
})
|
|
);
|
|
containerEl.createEl("h3", { text: "Usage" });
|
|
containerEl.createEl("p", {
|
|
text: "Create a code block with ```math or ```typst and write your Typst math syntax inside."
|
|
});
|
|
containerEl.createEl("pre", {
|
|
text: "```math\n$ sum_(i=1)^n i = (n(n+1))/2 $\n```"
|
|
});
|
|
containerEl.createEl("h3", { text: "Installation" });
|
|
containerEl.createEl("p", {
|
|
text: "Make sure you have Typst CLI installed. Visit https://github.com/typst/typst for installation instructions."
|
|
});
|
|
}
|
|
};
|