Fixed pdf export

This commit is contained in:
JK 2025-11-09 16:41:22 +01:00
parent 47b7ad4639
commit e8e9b3e93e
18 changed files with 793 additions and 580 deletions

View file

@ -3,9 +3,10 @@ import process from "process";
import builtins from "builtin-modules";
import fs from "fs";
import inlineWorkerPlugin from "esbuild-plugin-inline-worker";
import esbuildSvelte from "esbuild-svelte";
const banner =
`/*
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
@ -19,7 +20,7 @@ const context = await esbuild.context({
},
entryPoints: ["src/main.ts"],
bundle: true,
plugins: [inlineWorkerPlugin()],
plugins: [inlineWorkerPlugin(), esbuildSvelte({ compilerOptions: { css: "injected" } })],
external: [
"obsidian",
"electron",
@ -41,7 +42,7 @@ const context = await esbuild.context({
minify: prod,
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/main.js",
outfile: "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/main.js", // Ensure this path is correct for your setup
});
if (prod) {
@ -51,4 +52,4 @@ if (prod) {
await context.watch();
}
fs.copyFileSync("styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css");
fs.copyFileSync("src/styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css");

458
package-lock.json generated
View file

@ -11,6 +11,7 @@
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.0.3",
"@lucide/svelte": "^0.562.0",
"esbuild": "^0.25.12",
"esbuild-plugin-inline-worker": "^0.1.1",
"flatqueue": "^2.0.3",
@ -24,9 +25,11 @@
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"deepmerge": "^4.3.1",
"esbuild-svelte": "^0.9.4",
"obsidian": "latest",
"p-limit": "^7.2.0",
"pdf-lib": "^1.17.1",
"sass": "^1.97.2",
"svelte": "^5.43.5",
"tslib": "2.4.0",
"typescript": "4.7.4"
@ -586,7 +589,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@ -597,7 +599,6 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@ -608,7 +609,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@ -618,14 +618,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -653,6 +651,15 @@
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lucide/svelte": {
"version": "0.562.0",
"resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.562.0.tgz",
"integrity": "sha512-wDMULwtTFN2Sc/TFBm6gfuVCNb4Y5P9LDrwxNnUbV52+IEU7NXZmvxwXoz+vrrpad6Xupq+Hw5eUlqIHEGhouw==",
"license": "ISC",
"peerDependencies": {
"svelte": "^5"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -688,6 +695,330 @@
"node": ">= 8"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.4.tgz",
"integrity": "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.4",
"@parcel/watcher-darwin-arm64": "2.5.4",
"@parcel/watcher-darwin-x64": "2.5.4",
"@parcel/watcher-freebsd-x64": "2.5.4",
"@parcel/watcher-linux-arm-glibc": "2.5.4",
"@parcel/watcher-linux-arm-musl": "2.5.4",
"@parcel/watcher-linux-arm64-glibc": "2.5.4",
"@parcel/watcher-linux-arm64-musl": "2.5.4",
"@parcel/watcher-linux-x64-glibc": "2.5.4",
"@parcel/watcher-linux-x64-musl": "2.5.4",
"@parcel/watcher-win32-arm64": "2.5.4",
"@parcel/watcher-win32-ia32": "2.5.4",
"@parcel/watcher-win32-x64": "2.5.4"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.4.tgz",
"integrity": "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz",
"integrity": "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.4.tgz",
"integrity": "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.4.tgz",
"integrity": "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.4.tgz",
"integrity": "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.4.tgz",
"integrity": "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.4.tgz",
"integrity": "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.4.tgz",
"integrity": "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.4.tgz",
"integrity": "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.4.tgz",
"integrity": "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.4.tgz",
"integrity": "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.4.tgz",
"integrity": "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.4.tgz",
"integrity": "sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@pdf-lib/standard-fonts": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
@ -712,7 +1043,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8.9.0"
@ -741,7 +1071,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
@ -953,7 +1282,6 @@
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@ -1026,7 +1354,6 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@ -1045,7 +1372,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@ -1122,11 +1448,26 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -1214,6 +1555,17 @@
"node": ">=0.10.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -1289,6 +1641,23 @@
"find-cache-dir": "^3.3.1"
}
},
"node_modules/esbuild-svelte": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.4.tgz",
"integrity": "sha512-v/a0GjkKN06nal2QLluxjk2GXsei3fdtjIuHRa6pVnri5rQBZ6pj4a2WwjLfRojgRsLwDHl4xSeZ1BeUHsqQrw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.19"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"esbuild": ">=0.17.0",
"svelte": ">=4.2.1 <6"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -1440,7 +1809,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"dev": true,
"license": "MIT"
},
"node_modules/espree": {
@ -1488,7 +1856,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz",
"integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@ -1790,6 +2157,13 @@
"node": ">= 4"
}
},
"node_modules/immutable": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"dev": true,
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -1880,7 +2254,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.6"
@ -1955,7 +2328,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
@ -1997,7 +2369,6 @@
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@ -2088,6 +2459,14 @@
"dev": true,
"peer": true
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/obsidian": {
"version": "1.4.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.11.tgz",
@ -2389,6 +2768,20 @@
}
]
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@ -2460,6 +2853,27 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/sass": {
"version": "1.97.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz",
"integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
@ -2512,6 +2926,16 @@
"resolved": "https://registry.npmjs.org/sorted-btree/-/sorted-btree-1.8.1.tgz",
"integrity": "sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ=="
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -2560,7 +2984,6 @@
"version": "5.43.5",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.5.tgz",
"integrity": "sha512-HQoZArIewxQVNedseDsgMgnRSC4XOXczxXLF9rOJaPIJkg58INOPUiL8aEtzqZIXNSZJyw8NmqObwg/voajiHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
@ -2729,7 +3152,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"dev": true,
"license": "MIT"
}
}

View file

@ -4,10 +4,10 @@
"description": "An Obsidian.md plugin that provides a powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production && sass styles.scss styles.css",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production && sass src/styles/main.css styles.css",
"prod": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"dev-style": "sass --watch styles.scss styles.css",
"build-style": "sass --watch styles.scss styles.css",
"dev-style": "sass --watch src/styles/main.css styles.css",
"build-style": "sass --watch src/styles/main.css styles.css",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
@ -20,9 +20,11 @@
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"deepmerge": "^4.3.1",
"esbuild-svelte": "^0.9.4",
"obsidian": "latest",
"p-limit": "^7.2.0",
"pdf-lib": "^1.17.1",
"sass": "^1.97.2",
"svelte": "^5.43.5",
"tslib": "2.4.0",
"typescript": "4.7.4"
@ -30,10 +32,11 @@
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.0.3",
"@lucide/svelte": "^0.562.0",
"esbuild": "^0.25.12",
"esbuild-plugin-inline-worker": "^0.1.1",
"flatqueue": "^2.0.3",
"monkey-around": "^2.3.0",
"sorted-btree": "^1.8.1"
}
}
}

View file

@ -6,7 +6,7 @@ import { processActiveNoteEquations } from 'features/equations/numbering';
export class EquationCache {
private cache: Map<string, Map<string, EquationBlock>> = new Map();
constructor(private plugin: LatexReferencer) {}
constructor(private plugin: LatexReferencer) { }
/**
* Synchronously gets a cached equation block for a given file path and block ID.
@ -18,6 +18,10 @@ export class EquationCache {
return this.cache.get(filePath)?.get(blockId);
}
getEquationsForFile(filePath: string): Map<string, EquationBlock> | undefined {
return this.cache.get(filePath);
}
/**
* Asynchronously reads a file, processes its equations, and updates the cache.
* @param file The file to process.

View file

@ -3,6 +3,7 @@ import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
import { editorInfoField } from 'obsidian';
import LatexReferencer from 'main';
import { CONVERTER, getEqNumberPrefix } from 'utils/format';
import type { PluginSettings } from "features/settings/settings";
/**
@ -122,7 +123,7 @@ function parseEquationInfo(state: EditorState, plugin: LatexReferencer): Equatio
}
let equationCount = 0;
const eqPrefix = getEqNumberPrefix(plugin.app, file, settings);
const eqPrefix = getEqNumberPrefix(plugin.app, file, settings as Required<PluginSettings>);
const eqSuffix = settings.eqNumberSuffix;
for (const info of equationInfos) {

View file

@ -3,6 +3,7 @@ import { CONVERTER, getEqNumberPrefix } from "utils/format";
import { EquationBlock } from "types";
import LatexReferencer from "main";
import { ActiveNoteEquationProvider } from "features/equations/provider-equation";
import type { PluginSettings } from "features/settings/settings";
interface ReferenceInfo {
totalCount: number;
@ -28,7 +29,7 @@ export function processActiveNoteEquations(plugin: LatexReferencer, file: TFile,
if (!referenceMap.has(baseId)) {
referenceMap.set(baseId, { totalCount: 0, subIndices: new Set() });
}
const refInfo = referenceMap.get(baseId)!;
const refInfo = referenceMap.get(baseId) ?? { totalCount: 0, subIndices: new Set() };
refInfo.totalCount++;
if (subIndexStr) {
@ -41,7 +42,7 @@ export function processActiveNoteEquations(plugin: LatexReferencer, file: TFile,
const processedEquations = new Map<string, EquationBlock>();
let equationCount = 0;
const eqPrefix = getEqNumberPrefix(plugin.app, file, settings);
const eqPrefix = getEqNumberPrefix(plugin.app, file, settings as Required<PluginSettings>);
const eqSuffix = settings.eqNumberSuffix;
// 2. Process each equation using the pre-computed reference map.

View file

@ -1,29 +0,0 @@
export default {
exportCurrentFile: "Export current file to PDF",
exportCurrentFileWithPrevious: "Export to PDF with previous Settings",
exportDialog: {
filenameAsTitle: "Include file name as title",
pageSize: "Page Size",
margin: "Margin",
downscalePercent: "Downscale Percent",
landscape: "Landscape",
displayHeader: "Display Header",
displayFooter: "Display Footer",
openAfterExport: "Open after export",
cssSnippets: "CSS snippets",
},
settings: {
showTitle: "Add file name as title",
displayHeader: "Display headers",
displayFooter: "Display footer",
printBackground: "Print background",
maxLevel: "Max headings level of the outline",
displayMetadata: "PDF metadata",
headerTemplate: "Header Template",
footerTemplate: "Footer Template",
isTimestamp: "Add timestamp",
enabledCss: "Enable select css snippets",
concurrency: "Limit concurrency",
debugMode: "Debug Mode",
},
};

View file

@ -1,16 +0,0 @@
import * as merge from "deepmerge";
import en from "./en";
import zh from "./zh";
export type Lang = typeof en;
export default {
i18n: {
en,
zh,
},
get current() {
const lang = window.localStorage.getItem("language") ?? "en";
return merge(this.i18n.en, this.i18n[lang] ?? {}) as Lang;
},
};

View file

@ -1,33 +0,0 @@
import type { Lang } from ".";
export default {
exportCurrentFile: "导出当前文件为PDF",
exportCurrentFileWithPrevious: "使用上一次设置导出为PDF",
exportDialog: {
filenameAsTitle: "将笔记名作为标题",
pageSize: "纸张尺寸",
margin: "页边距",
downscalePercent: "缩放",
landscape: "横向打印",
displayHeader: "页眉",
displayFooter: "页脚",
openAfterExport: "导出后打开",
cssSnippets: "CSS代码片段",
},
settings: {
showTitle: "将笔记名作为标题",
displayHeader: "显示页眉",
displayFooter: "显示页脚",
printBackground: "打印背景",
maxLevel: "最大标题级别",
displayMetadata: "PDF元数据",
headerTemplate: "页眉模板",
footerTemplate: "页脚模板",
isTimestamp: "文件名添加时间戳",
enabledCss: "启用CSS片段选择",
concurrency: "限制并发数",
debugMode: "调试模式",
},
}

View file

@ -1,223 +0,0 @@
import { App, MarkdownView, Menu, Plugin, type PluginManifest, TFile, TFolder } from "obsidian";
import i18n, { type Lang } from "./i18n";
import { ExportConfigModal, type TConfig } from "./modal";
import ConfigSettingTab from "./setting";
import { traverseFolder } from "./utils";
import * as fs from "fs/promises";
import * as path from "path";
const isDev = process.env.NODE_ENV === "development";
export interface BetterExportPdfPluginSettings {
prevConfig?: TConfig;
showTitle: boolean;
maxLevel: string;
displayHeader: boolean;
displayFooter: boolean;
headerTemplate: string;
footerTemplate: string;
printBackground: boolean;
generateTaggedPDF: boolean;
displayMetadata: boolean;
isTimestamp: boolean;
debug: boolean;
enabledCss: boolean;
concurrency: string;
}
const DEFAULT_SETTINGS: BetterExportPdfPluginSettings = {
showTitle: true,
maxLevel: "6",
displayHeader: true,
displayFooter: true,
headerTemplate: `<div style="width: 100vw;font-size:10px;text-align:center;"><span class="title"></span></div>`,
footerTemplate: `<div style="width: 100vw;font-size:10px;text-align:center;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>`,
printBackground: false,
generateTaggedPDF: false,
displayMetadata: false,
debug: false,
isTimestamp: false,
enabledCss: false,
concurrency: "5",
};
export default class BetterExportPdfPlugin extends Plugin {
settings: BetterExportPdfPluginSettings;
i18n: Lang;
constructor(app: App, manifest: PluginManifest) {
super(app, manifest);
this.i18n = i18n.current;
}
async onload() {
await this.loadSettings();
this.registerCommand();
this.registerSetting();
this.registerEvents();
}
registerCommand() {
this.addCommand({
id: "export-current-file-to-pdf",
name: this.i18n.exportCurrentFile,
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
const file = view?.file;
if (!file) {
return false;
}
if (checking) {
return true;
}
new ExportConfigModal(this, file).open();
return true;
},
});
// this.addCommand({
// id: "better-export-pdf:with-prev-setting",
// name: this.i18n.exportCurrentFileWithPrevious,
// checkCallback: (checking: boolean) => {
// const view = this.app.workspace.getActiveViewOfType(MarkdownView);
// const file = view?.file;
// if (!file) {
// return false;
// }
// if (checking) {
// return true;
// }
// new ExportConfigModal(this, file, this.settings?.prevConfig).open();
// return true;
// },
// });
}
registerSetting() {
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new ConfigSettingTab(this.app, this));
}
registerEvents() {
// Register the Export As HTML button in the file menu
this.registerEvent(
this.app.workspace.on("file-menu", (menu, file: TFile | TFolder) => {
let title = file instanceof TFolder ? "Export folder to PDF" : "Better Export PDF";
if (isDev) {
title = `${title} (dev)`;
}
menu.addItem((item) => {
item
.setTitle(title)
.setIcon("download")
.setSection("action")
.onClick(async () => {
new ExportConfigModal(this, file).open();
});
});
}),
);
this.registerEvent(
this.app.workspace.on("file-menu", (menu, file: TFile | TFolder) => {
if (file instanceof TFolder) {
let title = "Export to PDF...";
if (isDev) {
title = `${title} (dev)`;
}
menu.addItem((item) => {
item.setTitle(title).setIcon("lucide-folder-down").setSection("action");
// @ts-ignore
const subMenu: Menu = item.setSubmenu();
subMenu.addItem((item) =>
item
.setTitle("Export each file to PDF")
.setIcon("lucide-file-stack")
.onClick(async () => {
new ExportConfigModal(this, file, true).open();
}),
);
subMenu.addItem((item) =>
item
.setTitle("Generate TOC.md file")
.setIcon("lucide-file-text")
.onClick(async () => {
await this.generateToc(file);
}),
);
});
}
}),
);
}
async generateToc(root: TFolder | TFile) {
// @ts-ignore
const basePath = this.app.vault.adapter.basePath;
const toc = path.join(basePath, root.path, "_TOC_.md");
const content = `---\ntoc: true\ntitle: ${root.name}\n---\n`;
await fs.writeFile(toc, content);
if (root instanceof TFolder) {
const files = traverseFolder(root);
for (const file of files) {
if (file.name == "_TOC_.md") {
continue;
}
await fs.appendFile(toc, `[[${file.path}]]\n`);
}
}
}
onunload() {}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
changeConfig() {
// @ts-ignore
const theme = "obsidian" === this.app.vault?.getConfig("theme");
if (theme) {
document.body.addClass("theme-light");
document.body.removeClass("theme-dark");
}
document.body.removeClass("theme-dark");
const node = document.body.createDiv("print");
const reset = function () {
node.detach();
if (theme) {
document.body.removeClass("theme-light");
document.body.addClass("theme-dark");
}
// t.hide();
};
node.addEventListener("click", reset);
const el = document.body.createDiv("print");
const el2 = el.createDiv("markdown-preview-view markdown-rendered");
// @ts-ignore
el2.toggleClass("rtl", this.app.vault.getConfig("rightToLeft"));
// @ts-ignore
el2.toggleClass("show-frontmatter", this.app.vault.getConfig("showFrontmatter"));
el2.createEl("h1", {
text: "xxxxx", // a.basename
});
}
}

View file

@ -1,10 +1,8 @@
import * as electron from "electron";
import * as fs from "fs/promises";
import { ButtonComponent, type FrontMatterCache, Modal, Setting, TFile, TFolder, debounce } from "obsidian";
import * as path from "path";
import { PageSize } from "./constant";
import i18n, { type Lang } from "./i18n";
import BetterExportPdfPlugin from "./main";
import LatexReferencer from "../../main";
import { exportToPDF, getOutputFile, getOutputPath } from "./pdf";
import { createWebview, fixDoc, getAllStyles, getPatchStyle, renderMarkdown, type ParamType } from "./render";
import { isNumber, mm2px, px2mm, safeParseFloat, safeParseInt, traverseFolder } from "./utils";
@ -53,7 +51,6 @@ export class ExportConfigModal extends Modal {
canceled: boolean;
multiplePdf?: boolean;
callback: Callback;
plugin: BetterExportPdfPlugin;
file: TFile | TFolder;
preview: Electron.WebViewElement;
webviews: Electron.WebViewElement[];
@ -62,18 +59,15 @@ export class ExportConfigModal extends Modal {
docs: DocType[];
title: string;
frontMatter: FrontMatterCache;
i18n: Lang;
scale: number;
// @ts-ignore
svelte: Progress;
constructor(plugin: BetterExportPdfPlugin, file: TFile | TFolder, multiplePdf?: boolean) {
constructor(public plugin: LatexReferencer, file: TFile | TFolder, multiplePdf?: boolean) {
super(plugin.app);
this.canceled = true;
this.plugin = plugin;
this.file = file;
this.completed = false;
this.i18n = i18n.current;
this.docs = [];
this.scale = 0.75;
this.webviews = [];
@ -410,7 +404,7 @@ export class ExportConfigModal extends Modal {
}
private generateForm(contentEl: HTMLDivElement) {
new Setting(contentEl).setName(this.i18n.exportDialog.filenameAsTitle).addToggle((toggle) =>
new Setting(contentEl).setName("Include file name as title").addToggle((toggle) =>
toggle
.setTooltip("Include file name as title")
.setValue(this.config["showTitle"])
@ -444,7 +438,7 @@ export class ExportConfigModal extends Modal {
"Ledger",
"Custom",
];
new Setting(contentEl).setName(this.i18n.exportDialog.pageSize).addDropdown((dropdown) => {
new Setting(contentEl).setName("Page Size").addDropdown((dropdown) => {
dropdown
.addOptions(Object.fromEntries(pageSizes.map((size) => [size, size])))
.setValue(this.config.pageSize as string)
@ -493,7 +487,7 @@ export class ExportConfigModal extends Modal {
sizeEl.settingEl.hidden = this.config["pageSize"] !== "Custom";
new Setting(contentEl)
.setName(this.i18n.exportDialog.margin)
.setName("Margin")
.setDesc("The unit is millimeters.")
.addDropdown((dropdown) => {
dropdown
@ -557,7 +551,7 @@ export class ExportConfigModal extends Modal {
});
btmEl.settingEl.hidden = this.config["marginType"] != "3";
new Setting(contentEl).setName(this.i18n.exportDialog.downscalePercent).addSlider((slider) => {
new Setting(contentEl).setName("Downscale Percent").addSlider((slider) => {
slider
.setLimits(0, 100, 1)
.setValue(this.config["scale"] as number)
@ -566,7 +560,7 @@ export class ExportConfigModal extends Modal {
slider.showTooltip();
});
});
new Setting(contentEl).setName(this.i18n.exportDialog.landscape).addToggle((toggle) =>
new Setting(contentEl).setName("Landscape").addToggle((toggle) =>
toggle
.setTooltip("landscape")
.setValue(this.config["landscape"])
@ -575,7 +569,7 @@ export class ExportConfigModal extends Modal {
}),
);
new Setting(contentEl).setName(this.i18n.exportDialog.displayHeader).addToggle((toggle) =>
new Setting(contentEl).setName("Display Header").addToggle((toggle) =>
toggle
.setTooltip("Display header")
.setValue(this.config["displayHeader"])
@ -584,7 +578,7 @@ export class ExportConfigModal extends Modal {
}),
);
new Setting(contentEl).setName(this.i18n.exportDialog.displayFooter).addToggle((toggle) =>
new Setting(contentEl).setName("Display Footer").addToggle((toggle) =>
toggle
.setTooltip("Display footer")
.setValue(this.config["displayFooter"])
@ -593,7 +587,7 @@ export class ExportConfigModal extends Modal {
}),
);
new Setting(contentEl).setName(this.i18n.exportDialog.openAfterExport).addToggle((toggle) =>
new Setting(contentEl).setName("Open after export").addToggle((toggle) =>
toggle
.setTooltip("Open the exported file after exporting.")
.setValue(this.config["open"])
@ -605,7 +599,7 @@ export class ExportConfigModal extends Modal {
const snippets = this.cssSnippets();
if (Object.keys(snippets).length > 0 && this.plugin.settings.enabledCss) {
new Setting(contentEl).setName(this.i18n.exportDialog.cssSnippets).addDropdown((dropdown) => {
new Setting(contentEl).setName("CSS snippets").addDropdown((dropdown) => {
dropdown
.addOption("0", "Not select")
.addOptions(snippets)

View file

@ -1,9 +1,10 @@
import * as electron from "electron";
import * as fs from "fs/promises";
import { type FrontMatterCache } from "obsidian";
import { type FrontMatterCache, Notice } from "obsidian";
import { PDFArray, PDFDict, PDFDocument, PDFHexString, PDFName, PDFRef, StandardFonts } from "pdf-lib";
import type { BetterExportPdfPluginSettings } from "./main";
import type { PluginSettings } from "../settings/settings";
import type { DocType, PageSizeType, TConfig } from "./modal";
import { TreeNode, getHeadingTree, safeParseFloat, safeParseInt } from "./utils";
@ -261,9 +262,9 @@ export const setOutline = async (doc: PDFDocument, outlines: readonly PDFOutline
Type: "Outlines",
...(rootCount > 0
? {
First: refMap.get(outlines[0])!,
Last: refMap.get(outlines[outlines.length - 1])!,
}
First: refMap.get(outlines[0])!,
Last: refMap.get(outlines[outlines.length - 1])!,
}
: {}),
Count: rootCount,
}),
@ -367,7 +368,7 @@ export function setMetadata(
export async function exportToPDF(
outputFile: string,
config: TConfig & BetterExportPdfPluginSettings,
config: TConfig & PluginSettings, // Changed from BetterExportPdfPluginSettings
w: Electron.WebViewElement,
{ doc, frontMatter }: DocType,
) {
@ -434,12 +435,8 @@ export async function exportToPDF(
}
try {
let data: Uint8Array = await new Promise<Buffer>((resolve, reject) => {
(w as any).printToPDF(printOptions, (error: any, data: Buffer) => {
if (error) return reject(error);
resolve(data);
});
});
const buffer = await (w as any).printToPDF(printOptions);
let data: Uint8Array = new Uint8Array(buffer);
data = await editPDF(data, {
headings: getHeadingTree(doc),
@ -456,6 +453,7 @@ export async function exportToPDF(
}
} catch (error) {
console.error(error);
new Notice("Export to PDF failed: " + error);
}
}

View file

@ -1,192 +0,0 @@
import { App, PluginSettingTab, Setting, TextAreaComponent } from "obsidian";
import i18n, { type Lang } from "./i18n";
import BetterExportPdfPlugin from "./main";
function setAttributes(element: HTMLTextAreaElement, attributes: { [x: string]: string }) {
for (const key in attributes) {
element.setAttribute(key, attributes[key]);
}
}
export const renderBuyMeACoffeeBadge = (contentEl: HTMLElement | DocumentFragment, width = 175) => {
const linkEl = contentEl.createEl("a", {
href: "https://www.buymeacoffee.com/l1xnan",
});
const imgEl = linkEl.createEl("img");
imgEl.src =
"https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=nathangeorge&button_colour=6a8696&font_colour=ffffff&font_family=Poppins&outline_colour=000000&coffee_colour=FFDD00";
imgEl.alt = "Buy me a coffee";
imgEl.width = width;
};
export default class ConfigSettingTab extends PluginSettingTab {
plugin: BetterExportPdfPlugin;
i18n: Lang;
constructor(app: App, plugin: BetterExportPdfPlugin) {
super(app, plugin);
this.plugin = plugin;
this.i18n = i18n.current;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
const supportDesc = new DocumentFragment();
supportDesc.createDiv({
text: "Support the continued development of this plugin.",
});
new Setting(containerEl).setDesc(supportDesc);
renderBuyMeACoffeeBadge(containerEl);
new Setting(containerEl).setName(this.i18n.settings.showTitle).addToggle((toggle) =>
toggle
.setTooltip(this.i18n.settings.showTitle)
.setValue(this.plugin.settings.showTitle)
.onChange(async (value) => {
this.plugin.settings.showTitle = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName(this.i18n.settings.displayHeader).addToggle((toggle) =>
toggle
.setTooltip("Display header")
.setValue(this.plugin.settings.displayHeader)
.onChange(async (value) => {
this.plugin.settings.displayHeader = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName(this.i18n.settings.displayFooter).addToggle((toggle) =>
toggle
.setTooltip("Display footer")
.setValue(this.plugin.settings.displayFooter)
.onChange(async (value) => {
this.plugin.settings.displayFooter = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName(this.i18n.settings.printBackground)
.setDesc("Whether to print background graphics")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.printBackground).onChange(async (value) => {
this.plugin.settings.printBackground = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName("Generate tagged PDF")
.setDesc(
"Whether or not to generate a tagged (accessible) PDF. Defaults to false. As this property is experimental, the generated PDF may not adhere fully to PDF/UA and WCAG standards.",
)
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.generateTaggedPDF).onChange(async (value) => {
this.plugin.settings.generateTaggedPDF = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName(this.i18n.settings.maxLevel).addDropdown((dropdown) => {
dropdown
.addOptions(Object.fromEntries(["1", "2", "3", "4", "5", "6"].map((level) => [level, `h${level}`])))
.setValue(this.plugin.settings.maxLevel)
.onChange(async (value: string) => {
this.plugin.settings.maxLevel = value;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName(this.i18n.settings.displayMetadata)
.setDesc("Add frontMatter(title, author, keywords, subject creator, etc) to pdf metadata")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.displayMetadata).onChange(async (value) => {
this.plugin.settings.displayMetadata = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName("Advanced").setHeading();
const headerContentAreaSetting = new Setting(containerEl);
headerContentAreaSetting.settingEl.setAttribute("style", "display: grid; grid-template-columns: 1fr;");
headerContentAreaSetting
.setName(this.i18n.settings.headerTemplate)
.setDesc(
"HTML template for the print header. " +
"Should be valid HTML markup with following classes used to inject printing values into them: " +
'date (formatted print date), title (document title), url (document location), pageNumber (current page number) and totalPages (total pages in the document). For example, <span class="title"></span> would generate span containing the title.',
);
const hederContentArea = new TextAreaComponent(headerContentAreaSetting.controlEl);
setAttributes(hederContentArea.inputEl, {
style: "margin-top: 12px; width: 100%; height: 6vh;",
});
hederContentArea.setValue(this.plugin.settings.headerTemplate).onChange(async (value) => {
this.plugin.settings.headerTemplate = value;
this.plugin.saveSettings();
});
const footerContentAreaSetting = new Setting(containerEl);
footerContentAreaSetting.settingEl.setAttribute("style", "display: grid; grid-template-columns: 1fr;");
footerContentAreaSetting
.setName(this.i18n.settings.footerTemplate)
.setDesc("HTML template for the print footer. Should use the same format as the headerTemplate.");
const footerContentArea = new TextAreaComponent(footerContentAreaSetting.controlEl);
setAttributes(footerContentArea.inputEl, {
style: "margin-top: 12px; width: 100%; height: 6vh;",
});
footerContentArea.setValue(this.plugin.settings.footerTemplate).onChange(async (value) => {
this.plugin.settings.footerTemplate = value;
this.plugin.saveSettings();
});
new Setting(containerEl)
.setName(this.i18n.settings.isTimestamp)
.setDesc("Add timestamp to output file name")
.addToggle((cb) => {
cb.setValue(this.plugin.settings.isTimestamp).onChange(async (value) => {
this.plugin.settings.isTimestamp = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName(this.i18n.settings.enabledCss)
.setDesc("Select the css snippet that are not enabled")
.addToggle((cb) => {
cb.setValue(this.plugin.settings.enabledCss).onChange(async (value) => {
this.plugin.settings.enabledCss = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName(this.i18n.settings.concurrency)
.setDesc("Limit the number of concurrent renders")
.addText((cb) => {
const concurrency = this.plugin.settings?.concurrency;
cb.setValue(concurrency?.length > 0 ? concurrency : "5").onChange(async (value) => {
this.plugin.settings.concurrency = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl).setName("Debug").setHeading();
new Setting(containerEl)
.setName(this.i18n.settings.debugMode)
.setDesc("This is useful for troubleshooting.")
.addToggle((cb) => {
cb.setValue(this.plugin.settings.debug).onChange(async (value) => {
this.plugin.settings.debug = value;
await this.plugin.saveSettings();
});
});
}
}

View file

@ -46,10 +46,11 @@ export class LatexRenderChild extends MarkdownRenderChild {
const mathLink = getMathLink(this.plugin, this.targetLink, this.sourcePath);
if (mathLink) {
// Check if the link element is still in the DOM
const linkEl = this.containerEl.querySelector('a.internal-link');
if (linkEl) {
setMathLink(mathLink, linkEl as HTMLElement);
// The containerEl is now the link element itself
const linkEl = this.containerEl as HTMLElement;
// Verify it's still in the DOM (though MarkdownRenderChild handles unloading)
if (linkEl.isConnected) {
setMathLink(mathLink, linkEl);
}
}
finishRenderMath();
@ -63,12 +64,12 @@ export const CustomMathLinksProcessor = (plugin: LatexReferencer): MarkdownPostP
const href = link.getAttribute('data-href');
if (href && href.contains('#^eq-')) {
// This is one of our equation links.
// The link is already a proper <a href> tag, so we just need to change its text.
// Pass the specific link element as the container to scope the child correctly.
context.addChild(
new LatexRenderChild(element, plugin, context.sourcePath, href)
new LatexRenderChild(link, plugin, context.sourcePath, href)
);
}
}
};
};
export {}
export { }

View file

@ -1,4 +1,5 @@
import { Modifier } from "obsidian";
import type { TConfig } from "../export-pdf/modal";
import { LeafArgs } from "../../declarations";
// Types
@ -43,6 +44,22 @@ export interface PluginSettings {
modifierToJump: Modifier;
showModifierInstruction: boolean;
suggestLeafOption: LeafOption;
// PDF Export Settings
prevConfig?: TConfig;
showTitle: boolean;
maxLevel: string;
displayHeader: boolean;
displayFooter: boolean;
headerTemplate: string;
footerTemplate: string;
printBackground: boolean;
generateTaggedPDF: boolean;
displayMetadata: boolean;
isTimestamp: boolean;
debug: boolean;
enabledCss: boolean;
concurrency: string;
}
export const DEFAULT_SETTINGS: Required<PluginSettings> = {
@ -69,4 +86,34 @@ export const DEFAULT_SETTINGS: Required<PluginSettings> = {
modifierToJump: "Mod",
showModifierInstruction: true,
suggestLeafOption: "Current tab",
// PDF Export Settings
prevConfig: {
pageSize: "A4",
marginType: "1",
open: true,
landscape: false,
scale: 100,
showTitle: true,
displayHeader: true,
displayFooter: true,
marginTop: "10",
marginBottom: "10",
marginLeft: "10",
marginRight: "10",
cssSnippet: "0",
},
showTitle: true,
maxLevel: "6",
displayHeader: true,
displayFooter: true,
headerTemplate: `<div style="width: 100vw;font-size:10px;text-align:center;"><span class="title"></span></div>`,
footerTemplate: `<div style="width: 100vw;font-size:10px;text-align:center;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>`,
printBackground: false,
generateTaggedPDF: false,
displayMetadata: false,
isTimestamp: false,
debug: false,
enabledCss: false,
concurrency: "5",
};

View file

@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import { App, PluginSettingTab, Setting, TextAreaComponent } from "obsidian";
import LatexReferencer from "../../main";
import { NUMBER_STYLES } from "./settings";
@ -133,5 +133,161 @@ export class MathSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
containerEl.createEl("h2", { text: "PDF Export" });
new Setting(containerEl).setName("Add file name as title").addToggle((toggle) =>
toggle
.setTooltip("Add file name as title")
.setValue(this.plugin.settings.showTitle)
.onChange(async (value) => {
this.plugin.settings.showTitle = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName("Display headers").addToggle((toggle) =>
toggle
.setTooltip("Display header")
.setValue(this.plugin.settings.displayHeader)
.onChange(async (value) => {
this.plugin.settings.displayHeader = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName("Display footer").addToggle((toggle) =>
toggle
.setTooltip("Display footer")
.setValue(this.plugin.settings.displayFooter)
.onChange(async (value) => {
this.plugin.settings.displayFooter = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName("Print background")
.setDesc("Whether to print background graphics")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.printBackground).onChange(async (value) => {
this.plugin.settings.printBackground = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName("Generate tagged PDF")
.setDesc(
"Whether or not to generate a tagged (accessible) PDF. Defaults to false. As this property is experimental, the generated PDF may not adhere fully to PDF/UA and WCAG standards.",
)
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.generateTaggedPDF).onChange(async (value) => {
this.plugin.settings.generateTaggedPDF = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName("Max headings level of the outline").addDropdown((dropdown) => {
dropdown
.addOptions(Object.fromEntries(["1", "2", "3", "4", "5", "6"].map((level) => [level, `h${level}`])))
.setValue(this.plugin.settings.maxLevel)
.onChange(async (value: string) => {
this.plugin.settings.maxLevel = value;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName("PDF metadata")
.setDesc("Add frontMatter(title, author, keywords, subject creator, etc) to pdf metadata")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.displayMetadata).onChange(async (value) => {
this.plugin.settings.displayMetadata = value;
this.plugin.saveSettings();
}),
);
new Setting(containerEl).setName("Advanced").setHeading();
const headerContentAreaSetting = new Setting(containerEl);
headerContentAreaSetting.settingEl.setAttribute("style", "display: grid; grid-template-columns: 1fr;");
headerContentAreaSetting
.setName("Header Template")
.setDesc(
"HTML template for the print header. " +
"Should be valid HTML markup with following classes used to inject printing values into them: " +
'date (formatted print date), title (document title), url (document location), pageNumber (current page number) and totalPages (total pages in the document). For example, <span class="title"></span> would generate span containing the title.',
);
const hederContentArea = new TextAreaComponent(headerContentAreaSetting.controlEl);
setAttributes(hederContentArea.inputEl, {
style: "margin-top: 12px; width: 100%; height: 6vh;",
});
hederContentArea.setValue(this.plugin.settings.headerTemplate).onChange(async (value) => {
this.plugin.settings.headerTemplate = value;
this.plugin.saveSettings();
});
const footerContentAreaSetting = new Setting(containerEl);
footerContentAreaSetting.settingEl.setAttribute("style", "display: grid; grid-template-columns: 1fr;");
footerContentAreaSetting
.setName("Footer Template")
.setDesc("HTML template for the print footer. Should use the same format as the headerTemplate.");
const footerContentArea = new TextAreaComponent(footerContentAreaSetting.controlEl);
setAttributes(footerContentArea.inputEl, {
style: "margin-top: 12px; width: 100%; height: 6vh;",
});
footerContentArea.setValue(this.plugin.settings.footerTemplate).onChange(async (value) => {
this.plugin.settings.footerTemplate = value;
this.plugin.saveSettings();
});
new Setting(containerEl)
.setName("Add timestamp to output file name")
.setDesc("Add timestamp to output file name")
.addToggle((cb) => {
cb.setValue(this.plugin.settings.isTimestamp).onChange(async (value) => {
this.plugin.settings.isTimestamp = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName("Select the css snippet that are not enabled")
.setDesc("Select the css snippet that are not enabled")
.addToggle((cb) => {
cb.setValue(this.plugin.settings.enabledCss).onChange(async (value) => {
this.plugin.settings.enabledCss = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName("Limit the number of concurrent renders")
.setDesc("Limit the number of concurrent renders")
.addText((cb) => {
const concurrency = this.plugin.settings?.concurrency;
cb.setValue(concurrency?.length > 0 ? concurrency : "5").onChange(async (value) => {
this.plugin.settings.concurrency = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl).setName("Debug").setHeading();
new Setting(containerEl)
.setName("This is useful for troubleshooting.")
.setDesc("This is useful for troubleshooting.")
.addToggle((cb) => {
cb.setValue(this.plugin.settings.debug).onChange(async (value) => {
this.plugin.settings.debug = value;
await this.plugin.saveSettings();
});
});
}
}
function setAttributes(element: HTMLTextAreaElement, attributes: { [x: string]: string }) {
for (const key in attributes) {
element.setAttribute(key, attributes[key]);
}
}

View file

@ -21,21 +21,6 @@ export class LatexLinkProvider extends Provider {
return null;
}
let content: string | null = null;
for (const leaf of this.app.workspace.getLeavesOfType("markdown")) {
const view = leaf.view;
if (view instanceof MarkdownView && view.file?.path === targetFile.path) {
content = view.editor.getValue();
break;
}
}
if (content === null) {
// If the target file is not open, read it from the vault
// Note: This is a fallback and might not be perfectly in sync with unsaved changes.
this.app.vault.cachedRead(targetFile).then(fileContent => content = fileContent);
if (content === null) return null;
}
const subpath = parsedLinktext.subpath.substring(2); // remove #^
const subpathMatch = subpath.match(/^(eq-[\w]+)(?:-(\d+))?$/);
if (!subpathMatch) return null;
@ -43,9 +28,9 @@ export class LatexLinkProvider extends Provider {
const [, blockId, subIndexStr] = subpathMatch;
const subIndex = subIndexStr ? parseInt(subIndexStr) : undefined;
const equations = processActiveNoteEquations(this.plugin, targetFile, content);
const targetEquation = equations.get(blockId);
// Use the cache instead of parsing the file content manually
const targetEquation = this.plugin.equationCache.get(targetFile.path, blockId);
if (targetEquation?.$printName) {
let result: string;
const settings = this.plugin.settings;
@ -62,7 +47,7 @@ export class LatexLinkProvider extends Provider {
}
return result;
}
return null;
}
}

View file

@ -1,6 +1,8 @@
import { MarkdownView, Plugin, TFile, parseLinktext } from 'obsidian';
import { MarkdownView, Plugin, TFile, parseLinktext, Menu, TFolder } from 'obsidian';
import type { Extension } from '@codemirror/state';
import { around } from 'monkey-around';
import * as fs from "fs/promises";
import * as path from "path";
import { PluginSettings, DEFAULT_SETTINGS } from './features/settings/settings';
import { MathSettingTab } from "./features/settings/tab";
@ -20,6 +22,10 @@ import { EquationBlock } from 'types';
// ADDED: Import our new internal patcher function
import { patchSuggesterWithQuickPreview } from 'features/quick-preview/patcher';
import { EquationCache } from './features/cache/equation-cache';
import { ExportConfigModal } from "./features/export-pdf/modal";
import { traverseFolder } from "./features/export-pdf/utils";
const isDev = process.env.NODE_ENV === "development";
export default class LatexReferencer extends Plugin {
settings: PluginSettings;
@ -65,6 +71,76 @@ export default class LatexReferencer extends Plugin {
}
});
this.addCommand({
id: "export-current-file-to-pdf",
name: "Export current file to PDF",
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
const file = view?.file;
if (!file) {
return false;
}
if (checking) {
return true;
}
new ExportConfigModal(this, file).open();
return true;
},
});
// Menu items for file export
this.registerEvent(
this.app.workspace.on("file-menu", (menu, file: TFile | TFolder) => {
let title = file instanceof TFolder ? "Export folder to PDF" : "Better Export PDF";
if (isDev) {
title = `${title} (dev)`;
}
menu.addItem((item) => {
item
.setTitle(title)
.setIcon("download")
.setSection("action")
.onClick(async () => {
new ExportConfigModal(this, file).open();
});
});
}),
);
this.registerEvent(
this.app.workspace.on("file-menu", (menu, file: TFile | TFolder) => {
if (file instanceof TFolder) {
let title = "Export to PDF...";
if (isDev) {
title = `${title} (dev)`;
}
menu.addItem((item) => {
item.setTitle(title).setIcon("lucide-folder-down").setSection("action");
// @ts-ignore
const subMenu: Menu = item.setSubmenu();
subMenu.addItem((item) =>
item
.setTitle("Export each file to PDF")
.setIcon("lucide-file-stack")
.onClick(async () => {
new ExportConfigModal(this, file, true).open();
}),
);
subMenu.addItem((item) =>
item
.setTitle("Generate TOC.md file")
.setIcon("lucide-file-text")
.onClick(async () => {
await this.generateToc(file);
}),
);
});
}
}),
);
// Editor Extensions
this.editorExtensions = []
this.registerEditorExtension(this.editorExtensions);
@ -160,4 +236,21 @@ export default class LatexReferencer extends Plugin {
this.register(uninstaller);
}
async generateToc(root: TFolder | TFile) {
// @ts-ignore
const basePath = this.app.vault.adapter.basePath;
const toc = path.join(basePath, root.path, "_TOC_.md");
const content = `---\ntoc: true\ntitle: ${root.name}\n---\n`;
await fs.writeFile(toc, content);
if (root instanceof TFolder) {
const files = traverseFolder(root);
for (const file of files) {
if (file.name == "_TOC_.md") {
continue;
}
await fs.appendFile(toc, `[[${file.path}]]\n`);
}
}
}
}