From e8e9b3e93ea276290f8ee2413240c4e7e092b32a Mon Sep 17 00:00:00 2001 From: JK Date: Sun, 9 Nov 2025 16:41:22 +0100 Subject: [PATCH] Fixed pdf export --- esbuild.config.mjs | 9 +- package-lock.json | 458 +++++++++++++++++- package.json | 11 +- src/features/cache/equation-cache.ts | 6 +- .../equations/live-preview-equations.ts | 3 +- src/features/equations/numbering.ts | 5 +- src/features/export-pdf/i18n/en.ts | 29 -- src/features/export-pdf/i18n/index.ts | 16 - src/features/export-pdf/i18n/zh.ts | 33 -- src/features/export-pdf/main.ts | 223 --------- src/features/export-pdf/modal.ts | 28 +- src/features/export-pdf/pdf.ts | 22 +- src/features/export-pdf/setting.ts | 192 -------- src/features/linker/reading-view-linker.ts | 15 +- src/features/settings/settings.ts | 47 ++ src/features/settings/tab.ts | 158 +++++- src/latex-provider.ts | 23 +- src/main.ts | 95 +++- 18 files changed, 793 insertions(+), 580 deletions(-) delete mode 100644 src/features/export-pdf/i18n/en.ts delete mode 100644 src/features/export-pdf/i18n/index.ts delete mode 100644 src/features/export-pdf/i18n/zh.ts delete mode 100644 src/features/export-pdf/main.ts delete mode 100644 src/features/export-pdf/setting.ts diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 94d7a2d..e9b3cd3 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -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"); \ No newline at end of file +fs.copyFileSync("src/styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css"); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 65a6616..2b846ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } } diff --git a/package.json b/package.json index 788aa19..dc64a6c 100644 --- a/package.json +++ b/package.json @@ -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" } -} +} \ No newline at end of file diff --git a/src/features/cache/equation-cache.ts b/src/features/cache/equation-cache.ts index e300092..e57722a 100644 --- a/src/features/cache/equation-cache.ts +++ b/src/features/cache/equation-cache.ts @@ -6,7 +6,7 @@ import { processActiveNoteEquations } from 'features/equations/numbering'; export class EquationCache { private cache: Map> = 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 | undefined { + return this.cache.get(filePath); + } + /** * Asynchronously reads a file, processes its equations, and updates the cache. * @param file The file to process. diff --git a/src/features/equations/live-preview-equations.ts b/src/features/equations/live-preview-equations.ts index 4062f7f..a8301cd 100644 --- a/src/features/equations/live-preview-equations.ts +++ b/src/features/equations/live-preview-equations.ts @@ -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); const eqSuffix = settings.eqNumberSuffix; for (const info of equationInfos) { diff --git a/src/features/equations/numbering.ts b/src/features/equations/numbering.ts index 1e30baf..4f3629b 100644 --- a/src/features/equations/numbering.ts +++ b/src/features/equations/numbering.ts @@ -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(); let equationCount = 0; - const eqPrefix = getEqNumberPrefix(plugin.app, file, settings); + const eqPrefix = getEqNumberPrefix(plugin.app, file, settings as Required); const eqSuffix = settings.eqNumberSuffix; // 2. Process each equation using the pre-computed reference map. diff --git a/src/features/export-pdf/i18n/en.ts b/src/features/export-pdf/i18n/en.ts deleted file mode 100644 index cc39f58..0000000 --- a/src/features/export-pdf/i18n/en.ts +++ /dev/null @@ -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", - }, -}; diff --git a/src/features/export-pdf/i18n/index.ts b/src/features/export-pdf/i18n/index.ts deleted file mode 100644 index 23d50fc..0000000 --- a/src/features/export-pdf/i18n/index.ts +++ /dev/null @@ -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; - }, -}; diff --git a/src/features/export-pdf/i18n/zh.ts b/src/features/export-pdf/i18n/zh.ts deleted file mode 100644 index ceca8fb..0000000 --- a/src/features/export-pdf/i18n/zh.ts +++ /dev/null @@ -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: "调试模式", - }, -} diff --git a/src/features/export-pdf/main.ts b/src/features/export-pdf/main.ts deleted file mode 100644 index b8842a1..0000000 --- a/src/features/export-pdf/main.ts +++ /dev/null @@ -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: `
`, - footerTemplate: `
/
`, - - 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 - }); - } -} diff --git a/src/features/export-pdf/modal.ts b/src/features/export-pdf/modal.ts index dad42e6..df55c73 100644 --- a/src/features/export-pdf/modal.ts +++ b/src/features/export-pdf/modal.ts @@ -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) diff --git a/src/features/export-pdf/pdf.ts b/src/features/export-pdf/pdf.ts index 1697668..5e90781 100644 --- a/src/features/export-pdf/pdf.ts +++ b/src/features/export-pdf/pdf.ts @@ -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((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); } } diff --git a/src/features/export-pdf/setting.ts b/src/features/export-pdf/setting.ts deleted file mode 100644 index 9e3dc51..0000000 --- a/src/features/export-pdf/setting.ts +++ /dev/null @@ -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, 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(); - }); - }); - } -} diff --git a/src/features/linker/reading-view-linker.ts b/src/features/linker/reading-view-linker.ts index fa1c20c..35092bc 100644 --- a/src/features/linker/reading-view-linker.ts +++ b/src/features/linker/reading-view-linker.ts @@ -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 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 {} \ No newline at end of file +export { } \ No newline at end of file diff --git a/src/features/settings/settings.ts b/src/features/settings/settings.ts index 840e52c..48f8f79 100644 --- a/src/features/settings/settings.ts +++ b/src/features/settings/settings.ts @@ -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 = { @@ -69,4 +86,34 @@ export const DEFAULT_SETTINGS: Required = { 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: `
`, + footerTemplate: `
/
`, + printBackground: false, + generateTaggedPDF: false, + displayMetadata: false, + isTimestamp: false, + debug: false, + enabledCss: false, + concurrency: "5", }; diff --git a/src/features/settings/tab.ts b/src/features/settings/tab.ts index 1a9907c..d69840e 100644 --- a/src/features/settings/tab.ts +++ b/src/features/settings/tab.ts @@ -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, 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]); } } diff --git a/src/latex-provider.ts b/src/latex-provider.ts index 31a441a..e5b9e07 100644 --- a/src/latex-provider.ts +++ b/src/latex-provider.ts @@ -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; } } diff --git a/src/main.ts b/src/main.ts index 72e0470..a05d43b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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`); + } + } + } } \ No newline at end of file