mirror of
https://github.com/mprojectscode/obsidian-safe-js-plugin.git
synced 2026-07-22 17:20:31 +00:00
new settings tab
This commit is contained in:
parent
c564c13d8a
commit
92a730ce3f
14 changed files with 824 additions and 348 deletions
64
bun.lock
64
bun.lock
|
|
@ -5,24 +5,24 @@
|
|||
"": {
|
||||
"name": "obsidian-safe-js-plugin",
|
||||
"dependencies": {
|
||||
"ses": "^2.0.0",
|
||||
"ses": "^2.1.0",
|
||||
"zod": "^4.4.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "^20.9.0",
|
||||
"@lemons_dev/lemons-obsidian-plugin-automation": "^0.1.3",
|
||||
"@types/bun": "^1.3.13",
|
||||
"@types/bun": "^1.3.14",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-import-x": "^4.16.2",
|
||||
"eslint-plugin-no-relative-import-paths": "^1.6.1",
|
||||
"eslint-plugin-obsidianmd": "^0.2.9",
|
||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||
"eslint-plugin-only-warn": "^1.2.1",
|
||||
"obsidian": "latest",
|
||||
"obsidian": "obsidianmd/obsidian-api#2e88986ef3348e501d390afddcdfa08e132eb2f2",
|
||||
"prettier": "^3.8.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.3",
|
||||
"vite": "^8.0.12",
|
||||
"typescript-eslint": "8.59.3",
|
||||
"vite": "^8.0.14",
|
||||
"vite-plugin-banner": "^0.8.1",
|
||||
"vite-plugin-static-copy": "^4.1.0",
|
||||
},
|
||||
|
|
@ -89,41 +89,41 @@
|
|||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.129.0", "", {}, "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg=="],
|
||||
"@oxc-project/types": ["@oxc-project/types@0.132.0", "", {}, "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ=="],
|
||||
|
||||
"@package-json/types": ["@package-json/types@0.0.12", "", {}, "sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw=="],
|
||||
|
||||
"@pkgr/core": ["@pkgr/core@0.1.2", "", {}, "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA=="],
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.2", "", { "os": "android", "cpu": "arm64" }, "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew=="],
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ=="],
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ=="],
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0", "", { "os": "linux", "cpu": "arm" }, "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A=="],
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ=="],
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA=="],
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg=="],
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA=="],
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA=="],
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw=="],
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0", "", { "os": "none", "cpu": "arm64" }, "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig=="],
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg=="],
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.2", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow=="],
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0", "", { "os": "win32", "cpu": "x64" }, "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg=="],
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0", "", {}, "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ=="],
|
||||
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="],
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/codemirror": ["@types/codemirror@5.60.8", "", { "dependencies": { "@types/tern": "*" } }, "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw=="],
|
||||
|
||||
|
|
@ -249,7 +249,7 @@
|
|||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="],
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="],
|
||||
|
||||
|
|
@ -345,7 +345,7 @@
|
|||
|
||||
"eslint-plugin-no-unsanitized": ["eslint-plugin-no-unsanitized@4.1.5", "", { "peerDependencies": { "eslint": "^9 || ^10" } }, "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg=="],
|
||||
|
||||
"eslint-plugin-obsidianmd": ["eslint-plugin-obsidianmd@0.2.9", "", { "dependencies": { "@eslint/config-helpers": "^0.4.2", "@eslint/js": "^9.30.1", "@eslint/json": "0.14.0", "@microsoft/eslint-plugin-sdl": "^1.1.0", "@types/eslint": "9.6.1", "@types/node": "20.12.12", "@typescript-eslint/types": "^8.33.1", "@typescript-eslint/utils": "^8.33.1", "eslint": ">=9.0.0", "eslint-plugin-depend": "1.3.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-json-schema-validator": "5.1.0", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-security": "2.1.1", "globals": "14.0.0", "obsidian": "1.12.3", "semver": "^7.7.4", "typescript": "5.4.5", "typescript-eslint": "^8.35.1" }, "bin": { "eslint-plugin-obsidian": "dist/lib/index.js" } }, "sha512-+dF5Zz5T6/j0QYGu+wHbY3UZb45Kh+QFkFdfvkVk05o4YIIVqHMlrTFrlRVhuBd6Htu8QxcFOwzeMTN4aysVTA=="],
|
||||
"eslint-plugin-obsidianmd": ["eslint-plugin-obsidianmd@0.3.0", "", { "dependencies": { "@eslint/config-helpers": "^0.4.2", "@eslint/js": "^9.30.1", "@eslint/json": "0.14.0", "@microsoft/eslint-plugin-sdl": "^1.1.0", "@types/eslint": "9.6.1", "@types/node": "20.12.12", "@typescript-eslint/types": "^8.33.1", "@typescript-eslint/utils": "^8.33.1", "eslint": ">=9.0.0", "eslint-plugin-depend": "1.3.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-json-schema-validator": "5.1.0", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-security": "2.1.1", "globals": "14.0.0", "obsidian": "1.12.3", "semver": "^7.7.4", "typescript": "5.4.5", "typescript-eslint": "^8.35.1" }, "bin": { "eslint-plugin-obsidian": "dist/lib/index.js" } }, "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w=="],
|
||||
|
||||
"eslint-plugin-only-warn": ["eslint-plugin-only-warn@1.2.1", "", {}, "sha512-j37hwfaQDEOfkZ1Dpvu/HnWLavlzQxQxfbrU/9Jb4R9qzrE1eTYuRJyrxq7LzLRI8miG5FOV6veoUVhx7AI84w=="],
|
||||
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
|
||||
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
||||
|
||||
"obsidian": ["obsidian@1.12.3", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "6.5.0", "@codemirror/view": "6.38.6" } }, "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw=="],
|
||||
"obsidian": ["obsidian@github:obsidianmd/obsidian-api#2e88986", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "6.5.0", "@codemirror/view": "6.38.6" } }, "obsidianmd-obsidian-api-2e88986", "sha512-miO210/4NYxwNHnoXgDdbskikOzTKBZV39i6nOOD9jokKfMeXbWoRUNEMJGwxyVDsTIvpUeHUjfZ3kDvRaob7A=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
|
|
@ -615,7 +615,7 @@
|
|||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="],
|
||||
"postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
|
|
@ -645,7 +645,7 @@
|
|||
|
||||
"ret": ["ret@0.1.15", "", {}, "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0", "", { "dependencies": { "@oxc-project/types": "=0.129.0", "@rolldown/pluginutils": "1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0", "@rolldown/binding-darwin-arm64": "1.0.0", "@rolldown/binding-darwin-x64": "1.0.0", "@rolldown/binding-freebsd-x64": "1.0.0", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", "@rolldown/binding-linux-arm64-gnu": "1.0.0", "@rolldown/binding-linux-arm64-musl": "1.0.0", "@rolldown/binding-linux-ppc64-gnu": "1.0.0", "@rolldown/binding-linux-s390x-gnu": "1.0.0", "@rolldown/binding-linux-x64-gnu": "1.0.0", "@rolldown/binding-linux-x64-musl": "1.0.0", "@rolldown/binding-openharmony-arm64": "1.0.0", "@rolldown/binding-wasm32-wasi": "1.0.0", "@rolldown/binding-win32-arm64-msvc": "1.0.0", "@rolldown/binding-win32-x64-msvc": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA=="],
|
||||
"rolldown": ["rolldown@1.0.2", "", { "dependencies": { "@oxc-project/types": "=0.132.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.2", "@rolldown/binding-darwin-arm64": "1.0.2", "@rolldown/binding-darwin-x64": "1.0.2", "@rolldown/binding-freebsd-x64": "1.0.2", "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", "@rolldown/binding-linux-arm64-gnu": "1.0.2", "@rolldown/binding-linux-arm64-musl": "1.0.2", "@rolldown/binding-linux-ppc64-gnu": "1.0.2", "@rolldown/binding-linux-s390x-gnu": "1.0.2", "@rolldown/binding-linux-x64-gnu": "1.0.2", "@rolldown/binding-linux-x64-musl": "1.0.2", "@rolldown/binding-openharmony-arm64": "1.0.2", "@rolldown/binding-wasm32-wasi": "1.0.2", "@rolldown/binding-win32-arm64-msvc": "1.0.2", "@rolldown/binding-win32-x64-msvc": "1.0.2" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g=="],
|
||||
|
||||
"safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="],
|
||||
|
||||
|
|
@ -659,7 +659,7 @@
|
|||
|
||||
"semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
|
||||
|
||||
"ses": ["ses@2.0.0", "", { "dependencies": { "@endo/cache-map": "^1.1.0", "@endo/env-options": "^1.1.11", "@endo/immutable-arraybuffer": "^1.1.2" } }, "sha512-Avcinq9dg/LS5W3YW+eRcpolfI0jXP4OsM/JJKCqyqHygUCqpNSsfn8YoNBmfyN2DkSMqjpbWvWPlfY1Jr4oXw=="],
|
||||
"ses": ["ses@2.1.0", "", { "dependencies": { "@endo/cache-map": "^1.1.0", "@endo/env-options": "^1.1.11", "@endo/immutable-arraybuffer": "^1.1.2" } }, "sha512-mM3s60n24C42W5Q14vTBM9h9l2lfcUEtvoFSFkHSF8hMjMSDjLFObn8COjNbDfsRRS2gePkMves3GR2LPAEMKQ=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
|
|
@ -747,7 +747,7 @@
|
|||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"vite": ["vite@8.0.12", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", "rolldown": "1.0.0", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg=="],
|
||||
"vite": ["vite@8.0.14", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw=="],
|
||||
|
||||
"vite-plugin-banner": ["vite-plugin-banner@0.8.1", "", {}, "sha512-0+gGguHk3MH0HvzMSOCJC6fGgH4+jtY9KlKVZh+hwwE+PBkGVzY8xe657JL74vEgbeUJD37XjVqTrmve8XvZBQ=="],
|
||||
|
||||
|
|
@ -815,6 +815,8 @@
|
|||
|
||||
"eslint-plugin-obsidianmd/@types/node": ["@types/node@20.12.12", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw=="],
|
||||
|
||||
"eslint-plugin-obsidianmd/obsidian": ["obsidian@1.12.3", "", { "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { "@codemirror/state": "6.5.0", "@codemirror/view": "6.38.6" } }, "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw=="],
|
||||
|
||||
"eslint-plugin-obsidianmd/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
|
||||
|
||||
"eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
|
@ -849,6 +851,8 @@
|
|||
|
||||
"eslint-plugin-obsidianmd/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"eslint-plugin-obsidianmd/obsidian/moment": ["moment@2.29.4", "", {}, "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="],
|
||||
|
||||
"json-schema-migrate/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "safe-js",
|
||||
"name": "Safe JS",
|
||||
"version": "0.1.3",
|
||||
"minAppVersion": "1.12.3",
|
||||
"minAppVersion": "1.13.0",
|
||||
"description": "Run JavaScript through sandboxed Web Workers and a limited RPC surface.",
|
||||
"author": "Moritz Jung",
|
||||
"authorUrl": "https://www.moritzjung.dev/",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "safe-js",
|
||||
"name": "Safe JS",
|
||||
"version": "0.1.3",
|
||||
"minAppVersion": "1.12.3",
|
||||
"minAppVersion": "1.13.0",
|
||||
"description": "Run JavaScript through sandboxed Web Workers and a limited RPC surface.",
|
||||
"author": "Moritz Jung",
|
||||
"authorUrl": "https://www.moritzjung.dev/",
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -26,23 +26,23 @@
|
|||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "^20.9.0",
|
||||
"@lemons_dev/lemons-obsidian-plugin-automation": "^0.1.3",
|
||||
"@types/bun": "^1.3.13",
|
||||
"@types/bun": "^1.3.14",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-import-x": "^4.16.2",
|
||||
"eslint-plugin-no-relative-import-paths": "^1.6.1",
|
||||
"eslint-plugin-obsidianmd": "^0.2.9",
|
||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||
"eslint-plugin-only-warn": "^1.2.1",
|
||||
"obsidian": "latest",
|
||||
"obsidian": "obsidianmd/obsidian-api#2e88986ef3348e501d390afddcdfa08e132eb2f2",
|
||||
"prettier": "^3.8.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.3",
|
||||
"vite": "^8.0.12",
|
||||
"typescript-eslint": "8.59.3",
|
||||
"vite": "^8.0.14",
|
||||
"vite-plugin-banner": "^0.8.1",
|
||||
"vite-plugin-static-copy": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ses": "^2.0.0",
|
||||
"ses": "^2.1.0",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class SafeJsPlugin extends Plugin {
|
|||
api!: SafeJsPublicApi;
|
||||
private executionService!: SafeJsExecutionService;
|
||||
private scriptManager!: VaultScriptManager;
|
||||
settings!: SafeJsSettings;
|
||||
declare settings: SafeJsSettings;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { App } from 'obsidian';
|
||||
import type { TextComponent } from 'obsidian';
|
||||
import { Modal, Notice, Setting } from 'obsidian';
|
||||
import { Modal, Notice, SettingGroup } from 'obsidian';
|
||||
import { isJavaScriptVaultScriptPath } from 'packages/obsidian/src/scripts/script-settings';
|
||||
|
||||
export interface AddVaultScriptModalValues {
|
||||
|
|
@ -43,55 +43,64 @@ export class AddVaultScriptModal extends Modal {
|
|||
contentEl.empty();
|
||||
contentEl.createEl('h2', { text: this.title });
|
||||
let pathInput: TextComponent | undefined;
|
||||
const group = new SettingGroup(contentEl);
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Vault path')
|
||||
.setDesc('Use a vault-relative path that ends in .js.')
|
||||
.addText(text => {
|
||||
pathInput = text;
|
||||
return text
|
||||
.setPlaceholder('Scripts/example.js')
|
||||
.setValue(this.path)
|
||||
.onChange(value => {
|
||||
this.path = value;
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Command name')
|
||||
.setDesc('Leave blank to use the file name.')
|
||||
.addText(text =>
|
||||
text
|
||||
.setPlaceholder('Example script')
|
||||
.setValue(this.name)
|
||||
.onChange(value => {
|
||||
this.name = value;
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Run on startup')
|
||||
.setDesc('Run this script when Obsidian finishes loading.')
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(this.runOnStartup).onChange(value => {
|
||||
this.runOnStartup = value;
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Vault path')
|
||||
.setDesc('Use a vault-relative path that ends in .js.')
|
||||
.addText(text => {
|
||||
pathInput = text;
|
||||
return text
|
||||
.setPlaceholder('Scripts/example.js')
|
||||
.setValue(this.path)
|
||||
.onChange(value => {
|
||||
this.path = value;
|
||||
});
|
||||
}),
|
||||
);
|
||||
);
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Cancel').onClick(() => {
|
||||
this.close();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText(this.actionText)
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
void this.submit();
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Command name')
|
||||
.setDesc('Leave blank to use the file name.')
|
||||
.addText(text =>
|
||||
text
|
||||
.setPlaceholder('Example script')
|
||||
.setValue(this.name)
|
||||
.onChange(value => {
|
||||
this.name = value;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Run on startup')
|
||||
.setDesc('Run this script when Obsidian finishes loading.')
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(this.runOnStartup).onChange(value => {
|
||||
this.runOnStartup = value;
|
||||
}),
|
||||
);
|
||||
),
|
||||
);
|
||||
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.addButton(button =>
|
||||
button.setButtonText('Cancel').onClick(() => {
|
||||
this.close();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText(this.actionText)
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
void this.submit();
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
pathInput?.inputEl.focus();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { PermissionId } from '@lemons_dev/obsidian-safe-js-api';
|
||||
import type { App } from 'obsidian';
|
||||
import { Modal, Setting } from 'obsidian';
|
||||
import { Modal, SettingGroup } from 'obsidian';
|
||||
import type { PermissionPrompt, PermissionPromptRequest } from 'packages/obsidian/src/execution/execution-service';
|
||||
import { RICH_OUTPUT_PERMISSION } from 'packages/obsidian/src/permissions/permissions';
|
||||
import type { RpcRegistry } from 'packages/obsidian/src/rpc/rpc-registry';
|
||||
|
|
@ -21,17 +21,16 @@ export class ObsidianPermissionPrompt implements PermissionPrompt {
|
|||
return;
|
||||
}
|
||||
|
||||
let modal: PermissionApprovalModal;
|
||||
const modal = new PermissionApprovalModal(this.app, this.rpcRegistry, request, approved => {
|
||||
cleanup();
|
||||
resolve(approved);
|
||||
});
|
||||
const abortListener = (): void => {
|
||||
modal.close();
|
||||
};
|
||||
const cleanup = (): void => {
|
||||
request.signal?.removeEventListener('abort', abortListener);
|
||||
};
|
||||
modal = new PermissionApprovalModal(this.app, this.rpcRegistry, request, approved => {
|
||||
cleanup();
|
||||
resolve(approved);
|
||||
});
|
||||
request.signal?.addEventListener('abort', abortListener, { once: true });
|
||||
modal.open();
|
||||
});
|
||||
|
|
@ -88,20 +87,23 @@ class PermissionApprovalModal extends Modal {
|
|||
this.renderPermission(list, permission);
|
||||
}
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Cancel').onClick(() => {
|
||||
this.resolve(false);
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Allow')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.resolve(true);
|
||||
const group = new SettingGroup(contentEl);
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.addButton(button =>
|
||||
button.setButtonText('Cancel').onClick(() => {
|
||||
this.resolve(false);
|
||||
}),
|
||||
);
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Allow')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.resolve(true);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { App } from 'obsidian';
|
||||
import type { SettingDefinitionItem } from 'obsidian';
|
||||
import { PluginSettingTab } from 'obsidian';
|
||||
import type SafeJsPlugin from 'packages/obsidian/src/main';
|
||||
import type { PermissionSettingsStore } from 'packages/obsidian/src/permissions/approval-store';
|
||||
|
|
@ -15,7 +16,7 @@ import { VaultScriptsSettingsSection } from 'packages/obsidian/src/ui/settings/v
|
|||
export class SafeJsSettingTab extends PluginSettingTab {
|
||||
plugin: SafeJsPlugin;
|
||||
private readonly approvalStore: LocalStoragePermissionApprovalStore;
|
||||
private readonly permissionSettingsStore: PermissionSettingsStore;
|
||||
private readonly runtimeSettingsSection: RuntimeSettingsSection;
|
||||
private readonly scriptManager?: VaultScriptManager;
|
||||
|
||||
constructor(
|
||||
|
|
@ -27,27 +28,42 @@ export class SafeJsSettingTab extends PluginSettingTab {
|
|||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.approvalStore = new LocalStoragePermissionApprovalStore(new AppPermissionStorage(app));
|
||||
this.permissionSettingsStore = permissionSettingsStore;
|
||||
this.runtimeSettingsSection = new RuntimeSettingsSection(plugin, permissionSettingsStore, () => {
|
||||
this.update();
|
||||
});
|
||||
this.scriptManager = scriptManager;
|
||||
}
|
||||
|
||||
getSettingDefinitions(): SettingDefinitionItem[] {
|
||||
return [
|
||||
...this.runtimeSettingsSection.getSettingDefinitions(),
|
||||
...new VaultScriptsSettingsSection(
|
||||
this.app,
|
||||
this.plugin,
|
||||
() => {
|
||||
this.update();
|
||||
},
|
||||
this.scriptManager,
|
||||
).getSettingDefinitions(),
|
||||
...new StoredDataSettingsSection(this.app, this.approvalStore, () => {
|
||||
this.update();
|
||||
}).getSettingDefinitions(),
|
||||
];
|
||||
}
|
||||
|
||||
getControlValue(key: string): unknown {
|
||||
return this.runtimeSettingsSection.getControlValue(key);
|
||||
}
|
||||
|
||||
async setControlValue(key: string, value: unknown): Promise<void> {
|
||||
await this.runtimeSettingsSection.setControlValue(key, value);
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
new RuntimeSettingsSection(this.plugin, this.permissionSettingsStore, () => {
|
||||
this.display();
|
||||
}).render(containerEl);
|
||||
new VaultScriptsSettingsSection(
|
||||
this.app,
|
||||
this.plugin,
|
||||
() => {
|
||||
this.display();
|
||||
},
|
||||
this.scriptManager,
|
||||
).render(containerEl);
|
||||
new StoredDataSettingsSection(this.app, this.approvalStore, () => {
|
||||
this.display();
|
||||
}).render(containerEl);
|
||||
containerEl.createEl('p', {
|
||||
text: 'Safe JS settings require Obsidian 1.13.0 or newer. This app appears to be running an older version.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
import { Notice, SettingGroup, SettingPage } from 'obsidian';
|
||||
import type { PermissionApproval } from 'packages/obsidian/src/permissions/approval-store';
|
||||
import type { LocalStoragePermissionApprovalStore } from 'packages/obsidian/src/permissions/approval-store';
|
||||
import {
|
||||
ALL_FILTER_VALUE,
|
||||
formatCount,
|
||||
formatDate,
|
||||
groupBySorted,
|
||||
renderHiddenSettingItemCount,
|
||||
visibleSettingItems,
|
||||
} from 'packages/obsidian/src/ui/settings/setting-page-utils';
|
||||
|
||||
export class ApprovedHashesSettingPage extends SettingPage {
|
||||
private permissionFilter = ALL_FILTER_VALUE;
|
||||
private readonly approvalStore: LocalStoragePermissionApprovalStore;
|
||||
private readonly onStorageChanged: () => void;
|
||||
private searchQuery = '';
|
||||
private sourceFilter = ALL_FILTER_VALUE;
|
||||
private readonly staleEntryAgeMs: number;
|
||||
|
||||
constructor(approvalStore: LocalStoragePermissionApprovalStore, staleEntryAgeMs: number, onStorageChanged: () => void) {
|
||||
super();
|
||||
this.approvalStore = approvalStore;
|
||||
this.onStorageChanged = onStorageChanged;
|
||||
this.staleEntryAgeMs = staleEntryAgeMs;
|
||||
this.title = 'Approved script hashes';
|
||||
}
|
||||
|
||||
display(): void {
|
||||
this.containerEl.empty();
|
||||
const approvals = this.approvalStore.list();
|
||||
const filteredApprovals = this.filterApprovals(approvals);
|
||||
|
||||
this.renderApprovalActions(approvals);
|
||||
this.renderApprovalFilters(approvals);
|
||||
this.renderApprovalGroups(filteredApprovals);
|
||||
}
|
||||
|
||||
private clearOldApprovals(): void {
|
||||
const deletedCount = this.approvalStore.deleteOlderThan(Date.now() - this.staleEntryAgeMs);
|
||||
new Notice(`Cleared ${deletedCount} approved script ${deletedCount === 1 ? 'hash' : 'hashes'}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private clearAllApprovals(): void {
|
||||
const deletedCount = this.approvalStore.deleteAll();
|
||||
new Notice(`Cleared ${deletedCount} approved script ${deletedCount === 1 ? 'hash' : 'hashes'}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private revokeApproval(approval: PermissionApproval | undefined): void {
|
||||
if (approval === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.approvalStore.delete(approval);
|
||||
new Notice(`Revoked approval for script hash ${approval.codeHash}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private revokeApprovalGroup(approvals: PermissionApproval[], source: string): void {
|
||||
let deletedCount = 0;
|
||||
|
||||
for (const approval of approvals) {
|
||||
if (this.approvalStore.delete(approval)) {
|
||||
deletedCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
new Notice(`Revoked ${deletedCount} approved script ${deletedCount === 1 ? 'hash' : 'hashes'} for ${formatSource(source)}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private renderApprovalActions(approvals: PermissionApproval[]): void {
|
||||
const group = new SettingGroup(this.containerEl);
|
||||
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Clear approved hashes')
|
||||
.setDesc(`${formatCount(approvals.length, 'stored approval hash', 'stored approval hashes')}.`)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear older than 30 days').onClick(() => {
|
||||
this.clearOldApprovals();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Clear all')
|
||||
.setDestructive()
|
||||
.onClick(() => {
|
||||
this.clearAllApprovals();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private renderApprovalFilters(approvals: PermissionApproval[]): void {
|
||||
const group = new SettingGroup(this.containerEl);
|
||||
|
||||
group.addSetting(
|
||||
setting =>
|
||||
void setting
|
||||
.setName('Search')
|
||||
.setDesc('Filter by hash, source, or permission.')
|
||||
.addSearch(search =>
|
||||
search.setValue(this.searchQuery).onChange(value => {
|
||||
this.searchQuery = value;
|
||||
this.display();
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
group.addSetting(
|
||||
setting =>
|
||||
void setting
|
||||
.setName('Filters')
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(this.getPermissionFilterOptions(approvals))
|
||||
.setValue(this.permissionFilter)
|
||||
.onChange(value => {
|
||||
this.permissionFilter = value;
|
||||
this.display();
|
||||
}),
|
||||
)
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(this.getSourceFilterOptions(approvals))
|
||||
.setValue(this.sourceFilter)
|
||||
.onChange(value => {
|
||||
this.sourceFilter = value;
|
||||
this.display();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear filters').onClick(() => {
|
||||
this.permissionFilter = ALL_FILTER_VALUE;
|
||||
this.sourceFilter = ALL_FILTER_VALUE;
|
||||
this.searchQuery = '';
|
||||
this.display();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private renderApprovalGroups(approvals: PermissionApproval[]): void {
|
||||
if (approvals.length === 0) {
|
||||
const group = new SettingGroup(this.containerEl);
|
||||
group.addSetting(setting => void setting.setName('No approved script hashes match the current filters.'));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [source, sourceApprovals] of groupApprovalsBySource(approvals)) {
|
||||
const group = new SettingGroup(this.containerEl).setHeading(formatSource(source));
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Revoke group')
|
||||
.setDesc(formatCount(sourceApprovals.length, 'approval hash'))
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Revoke group')
|
||||
.setDestructive()
|
||||
.onClick(() => {
|
||||
this.revokeApprovalGroup(sourceApprovals, source);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
for (const approval of visibleSettingItems(sourceApprovals)) {
|
||||
group.addSetting(
|
||||
setting =>
|
||||
void setting
|
||||
.setName(approval.codeHash)
|
||||
.setDesc(`${approval.permissions.join(', ')} - ${formatDate(approval.updatedAt)}`)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Revoke').onClick(() => {
|
||||
this.revokeApproval(approval);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
renderHiddenSettingItemCount(group, sourceApprovals.length - visibleSettingItems(sourceApprovals).length, 'approved hashes');
|
||||
}
|
||||
}
|
||||
|
||||
private filterApprovals(approvals: PermissionApproval[]): PermissionApproval[] {
|
||||
const normalizedQuery = this.searchQuery.trim().toLowerCase();
|
||||
|
||||
return approvals.filter(approval => {
|
||||
if (this.permissionFilter !== ALL_FILTER_VALUE && !approval.permissions.some(permission => permission === this.permissionFilter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.sourceFilter !== ALL_FILTER_VALUE && sourceFilterValue(approval) !== this.sourceFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalizedQuery === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return approvalSearchText(approval).includes(normalizedQuery);
|
||||
});
|
||||
}
|
||||
|
||||
private getPermissionFilterOptions(approvals: PermissionApproval[]): Record<string, string> {
|
||||
const options: Record<string, string> = { [ALL_FILTER_VALUE]: 'All permissions' };
|
||||
|
||||
for (const permission of [...new Set(approvals.flatMap(approval => approval.permissions))].sort()) {
|
||||
options[permission] = permission;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private getSourceFilterOptions(approvals: PermissionApproval[]): Record<string, string> {
|
||||
const options: Record<string, string> = { [ALL_FILTER_VALUE]: 'All sources' };
|
||||
|
||||
for (const approval of approvals) {
|
||||
options[sourceFilterValue(approval)] = formatSource(sourceFilterValue(approval));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private refreshSettings(): void {
|
||||
this.onStorageChanged();
|
||||
this.display();
|
||||
}
|
||||
}
|
||||
|
||||
function approvalSearchText(approval: PermissionApproval): string {
|
||||
return `${approval.codeHash} ${formatSource(sourceFilterValue(approval))} ${approval.permissions.join(' ')}`.toLowerCase();
|
||||
}
|
||||
|
||||
function groupApprovalsBySource(approvals: PermissionApproval[]): [string, PermissionApproval[]][] {
|
||||
return groupBySorted(
|
||||
approvals,
|
||||
approval => sourceFilterValue(approval),
|
||||
(left, right) => formatSource(left).localeCompare(formatSource(right)),
|
||||
);
|
||||
}
|
||||
|
||||
function sourceFilterValue(approval: PermissionApproval): string {
|
||||
return approval.callerPluginId === undefined ? 'source:notes' : `plugin:${approval.callerPluginId}`;
|
||||
}
|
||||
|
||||
function formatSource(source: string): string {
|
||||
return source === 'source:notes' ? 'notes' : source.slice('plugin:'.length);
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
import { Setting } from 'obsidian';
|
||||
import type { SettingDefinitionItem } from 'obsidian';
|
||||
import type SafeJsPlugin from 'packages/obsidian/src/main';
|
||||
import type { PermissionSettingsStore } from 'packages/obsidian/src/permissions/approval-store';
|
||||
import type { SafeJsSettings } from 'packages/obsidian/src/settings/settings-schema';
|
||||
import { DEFAULT_SETTINGS } from 'packages/obsidian/src/settings/settings-schema';
|
||||
|
||||
export type RuntimeSettingsKey = keyof Pick<SafeJsSettings, 'debugBlocksEnabled' | 'executionTimeoutMs' | 'executionTimeoutsEnabled'>;
|
||||
export const AUTO_ALLOW_LOW_RISK_PERMISSIONS_KEY = 'autoAllowLowRiskPermissions';
|
||||
export type RuntimeControlKey = RuntimeSettingsKey | typeof AUTO_ALLOW_LOW_RISK_PERMISSIONS_KEY;
|
||||
|
||||
export class RuntimeSettingsSection {
|
||||
private readonly onSettingsChanged: () => void;
|
||||
private readonly permissionSettingsStore: PermissionSettingsStore;
|
||||
|
|
@ -15,54 +19,75 @@ export class RuntimeSettingsSection {
|
|||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
render(containerEl: HTMLElement): void {
|
||||
this.createSection(containerEl, 'Runtime settings');
|
||||
new Setting(containerEl)
|
||||
.setName('Execution timeouts')
|
||||
.setDesc('Cancel scripts that run longer than the configured timeout.')
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(this.plugin.settings.executionTimeoutsEnabled).onChange(async value => {
|
||||
this.plugin.settings.executionTimeoutsEnabled = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.onSettingsChanged();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Execution timeout')
|
||||
.setDesc(this.executionTimeoutDescription(this.plugin.settings))
|
||||
.addText(text =>
|
||||
text
|
||||
.setPlaceholder(String(DEFAULT_SETTINGS.executionTimeoutMs))
|
||||
.setValue(String(this.plugin.settings.executionTimeoutMs))
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.executionTimeoutMs = this.normalizeTimeoutMs(value);
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Debug blocks')
|
||||
.setDesc('Enable support for the safe-js-debug code block language.')
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(this.plugin.settings.debugBlocksEnabled).onChange(async value => {
|
||||
this.plugin.settings.debugBlocksEnabled = value;
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Auto-allow low-risk permissions')
|
||||
.setDesc('Allow low-risk permissions without prompting. Approvals are still remembered per script hash on this device.')
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(this.permissionSettingsStore.loadAutoAllowLowRiskPermissions()).onChange(value => {
|
||||
this.permissionSettingsStore.saveAutoAllowLowRiskPermissions(value);
|
||||
}),
|
||||
);
|
||||
getSettingDefinitions(): SettingDefinitionItem<RuntimeControlKey>[] {
|
||||
return [
|
||||
{
|
||||
type: 'group',
|
||||
items: [
|
||||
{
|
||||
name: 'Execution timeouts',
|
||||
desc: 'Cancel scripts that run longer than the configured timeout.',
|
||||
control: { type: 'toggle', key: 'executionTimeoutsEnabled' },
|
||||
},
|
||||
{
|
||||
name: 'Execution timeout',
|
||||
desc: this.executionTimeoutDescription(this.plugin.settings),
|
||||
control: {
|
||||
type: 'number',
|
||||
key: 'executionTimeoutMs',
|
||||
min: 1,
|
||||
placeholder: String(DEFAULT_SETTINGS.executionTimeoutMs),
|
||||
validate: value => (this.isValidTimeoutMs(value) ? undefined : 'Enter a positive timeout in milliseconds.'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Debug blocks',
|
||||
desc: 'Enable support for the safe-js-debug code block language.',
|
||||
control: { type: 'toggle', key: 'debugBlocksEnabled' },
|
||||
},
|
||||
{
|
||||
name: 'Auto-allow low-risk permissions',
|
||||
desc: 'Allow low-risk permissions without prompting. Approvals are still remembered per script hash on this device.',
|
||||
control: { type: 'toggle', key: AUTO_ALLOW_LOW_RISK_PERMISSIONS_KEY },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private createSection(containerEl: HTMLElement, heading: string): void {
|
||||
new Setting(containerEl).setName(heading).setHeading();
|
||||
getControlValue(key: string): unknown {
|
||||
if (!isRuntimeControlKey(key)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (key === AUTO_ALLOW_LOW_RISK_PERMISSIONS_KEY) {
|
||||
return this.permissionSettingsStore.loadAutoAllowLowRiskPermissions();
|
||||
}
|
||||
|
||||
return this.plugin.settings[key];
|
||||
}
|
||||
|
||||
async setControlValue(key: string, value: unknown): Promise<void> {
|
||||
if (!isRuntimeControlKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === AUTO_ALLOW_LOW_RISK_PERMISSIONS_KEY) {
|
||||
this.permissionSettingsStore.saveAutoAllowLowRiskPermissions(value === true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'executionTimeoutsEnabled') {
|
||||
await this.saveExecutionTimeoutsEnabled(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'debugBlocksEnabled') {
|
||||
await this.saveDebugBlocksEnabled(value);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.saveExecutionTimeoutMs(value);
|
||||
}
|
||||
|
||||
private executionTimeoutDescription(settings: SafeJsSettings): string {
|
||||
|
|
@ -71,8 +96,39 @@ export class RuntimeSettingsSection {
|
|||
: 'Timeouts are disabled. This value is kept for later use.';
|
||||
}
|
||||
|
||||
private normalizeTimeoutMs(value: string): number {
|
||||
const parsedValue = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : DEFAULT_SETTINGS.executionTimeoutMs;
|
||||
private isValidTimeoutMs(value: number): boolean {
|
||||
return Number.isFinite(value) && value > 0;
|
||||
}
|
||||
|
||||
private async saveDebugBlocksEnabled(value: unknown): Promise<void> {
|
||||
if (typeof value !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.settings.debugBlocksEnabled = value;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
private async saveExecutionTimeoutMs(value: unknown): Promise<void> {
|
||||
if (typeof value !== 'number' || !this.isValidTimeoutMs(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.settings.executionTimeoutMs = value;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
private async saveExecutionTimeoutsEnabled(value: unknown): Promise<void> {
|
||||
if (typeof value !== 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.settings.executionTimeoutsEnabled = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.onSettingsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
function isRuntimeControlKey(key: string): key is RuntimeControlKey {
|
||||
return key === AUTO_ALLOW_LOW_RISK_PERMISSIONS_KEY || key === 'debugBlocksEnabled' || key === 'executionTimeoutMs' || key === 'executionTimeoutsEnabled';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
import type { App } from 'obsidian';
|
||||
import { Notice, SettingGroup, SettingPage } from 'obsidian';
|
||||
import type { ScriptStorageEntry } from 'packages/obsidian/src/storage/script-storage';
|
||||
import { ScriptStorageManager } from 'packages/obsidian/src/storage/script-storage';
|
||||
import {
|
||||
ALL_FILTER_VALUE,
|
||||
formatBytes,
|
||||
formatCount,
|
||||
formatDate,
|
||||
groupBySorted,
|
||||
renderHiddenSettingItemCount,
|
||||
visibleSettingItems,
|
||||
} from 'packages/obsidian/src/ui/settings/setting-page-utils';
|
||||
|
||||
export class ScriptStorageSettingPage extends SettingPage {
|
||||
private readonly app: App;
|
||||
private readonly onStorageChanged: () => void;
|
||||
private scopeFilter = ALL_FILTER_VALUE;
|
||||
private searchQuery = '';
|
||||
private readonly staleEntryAgeMs: number;
|
||||
|
||||
constructor(app: App, staleEntryAgeMs: number, onStorageChanged: () => void) {
|
||||
super();
|
||||
this.app = app;
|
||||
this.onStorageChanged = onStorageChanged;
|
||||
this.staleEntryAgeMs = staleEntryAgeMs;
|
||||
this.title = 'Script storage';
|
||||
}
|
||||
|
||||
display(): void {
|
||||
this.containerEl.empty();
|
||||
const entries = ScriptStorageManager.listAll(this.app);
|
||||
const filteredEntries = this.filterEntries(entries);
|
||||
|
||||
this.renderScriptStorageActions(entries);
|
||||
this.renderScriptStorageFilters(entries);
|
||||
this.renderScriptStorageGroups(filteredEntries);
|
||||
}
|
||||
|
||||
private clearOldScriptStorage(): void {
|
||||
const deletedCount = ScriptStorageManager.deleteOlderThanAll(this.app, Date.now() - this.staleEntryAgeMs);
|
||||
new Notice(`Cleared ${deletedCount} script storage ${deletedCount === 1 ? 'key' : 'keys'}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private clearAllScriptStorage(): void {
|
||||
const deletedCount = ScriptStorageManager.deleteAllKnown(this.app);
|
||||
new Notice(`Cleared ${deletedCount} script storage ${deletedCount === 1 ? 'key' : 'keys'}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private clearScriptStorageScope(scope: string | null): void {
|
||||
const deletedCount = new ScriptStorageManager(this.app, Date.now, scope).deleteAll();
|
||||
new Notice(`Cleared ${deletedCount} script storage ${deletedCount === 1 ? 'key' : 'keys'} from ${formatScope(scope)}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private deleteScriptStorageEntry(entry: ScriptStorageEntry): void {
|
||||
new ScriptStorageManager(this.app, Date.now, entry.scope).delete(entry.key);
|
||||
new Notice(`Deleted script storage key ${entry.key}.`);
|
||||
this.refreshSettings();
|
||||
}
|
||||
|
||||
private renderScriptStorageActions(entries: ScriptStorageEntry[]): void {
|
||||
const group = new SettingGroup(this.containerEl);
|
||||
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Clear script storage')
|
||||
.setDesc(`${formatCount(entries.length, 'indexed storage key')}.`)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear older than 30 days').onClick(() => {
|
||||
this.clearOldScriptStorage();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Clear all')
|
||||
.setDestructive()
|
||||
.onClick(() => {
|
||||
this.clearAllScriptStorage();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private renderScriptStorageFilters(entries: ScriptStorageEntry[]): void {
|
||||
const group = new SettingGroup(this.containerEl);
|
||||
|
||||
group.addSetting(setting => void setting
|
||||
.setName('Search')
|
||||
.setDesc('Filter by key or scope.')
|
||||
.addSearch(search =>
|
||||
search.setValue(this.searchQuery).onChange(value => {
|
||||
this.searchQuery = value;
|
||||
this.display();
|
||||
}),
|
||||
));
|
||||
|
||||
group.addSetting(setting => void setting
|
||||
.setName('Filters')
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(this.getScopeFilterOptions(entries))
|
||||
.setValue(this.scopeFilter)
|
||||
.onChange(value => {
|
||||
this.scopeFilter = value;
|
||||
this.display();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear filters').onClick(() => {
|
||||
this.scopeFilter = ALL_FILTER_VALUE;
|
||||
this.searchQuery = '';
|
||||
this.display();
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
private renderScriptStorageGroups(entries: ScriptStorageEntry[]): void {
|
||||
if (entries.length === 0) {
|
||||
const group = new SettingGroup(this.containerEl);
|
||||
group.addSetting(setting => void setting.setName('No indexed script storage keys match the current filters.'));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [scope, scopeEntries] of groupEntriesByScope(entries)) {
|
||||
const group = new SettingGroup(this.containerEl).setHeading(formatScope(scope));
|
||||
group.addSetting(setting =>
|
||||
void setting
|
||||
.setName('Clear scope')
|
||||
.setDesc(`${formatCount(scopeEntries.length, 'key')} - ${formatBytes(sumEntryBytes(scopeEntries))}`)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Clear scope')
|
||||
.setDestructive()
|
||||
.onClick(() => {
|
||||
this.clearScriptStorageScope(scope);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
for (const entry of visibleSettingItems(scopeEntries)) {
|
||||
group.addSetting(setting => void setting
|
||||
.setName(entry.key)
|
||||
.setDesc(`${formatBytes(entry.sizeBytes)} - ${formatDate(entry.updatedAt)}`)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Delete').onClick(() => {
|
||||
this.deleteScriptStorageEntry(entry);
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
renderHiddenSettingItemCount(group, scopeEntries.length - visibleSettingItems(scopeEntries).length, 'storage keys');
|
||||
}
|
||||
}
|
||||
|
||||
private filterEntries(entries: ScriptStorageEntry[]): ScriptStorageEntry[] {
|
||||
const normalizedQuery = this.searchQuery.trim().toLowerCase();
|
||||
|
||||
return entries.filter(entry => {
|
||||
if (this.scopeFilter !== ALL_FILTER_VALUE && scopeFilterValue(entry.scope) !== this.scopeFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalizedQuery === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return `${entry.key} ${formatScope(entry.scope)}`.toLowerCase().includes(normalizedQuery);
|
||||
});
|
||||
}
|
||||
|
||||
private getScopeFilterOptions(entries: ScriptStorageEntry[]): Record<string, string> {
|
||||
const options: Record<string, string> = { [ALL_FILTER_VALUE]: 'All scopes' };
|
||||
|
||||
for (const entry of entries) {
|
||||
options[scopeFilterValue(entry.scope)] = formatScope(entry.scope);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private refreshSettings(): void {
|
||||
this.onStorageChanged();
|
||||
this.display();
|
||||
}
|
||||
}
|
||||
|
||||
function groupEntriesByScope(entries: ScriptStorageEntry[]): [string | null, ScriptStorageEntry[]][] {
|
||||
return groupBySorted(
|
||||
entries,
|
||||
entry => entry.scope,
|
||||
(left, right) => formatScope(left).localeCompare(formatScope(right)),
|
||||
);
|
||||
}
|
||||
|
||||
function scopeFilterValue(scope: string | null): string {
|
||||
return scope === null ? 'global' : `scope:${scope}`;
|
||||
}
|
||||
|
||||
function sumEntryBytes(entries: ScriptStorageEntry[]): number {
|
||||
return entries.reduce((total, entry) => total + entry.sizeBytes, 0);
|
||||
}
|
||||
|
||||
function formatScope(scope: string | null): string {
|
||||
return scope === null ? 'global' : `scoped ${scope.slice(0, 12)}`;
|
||||
}
|
||||
49
packages/obsidian/src/ui/settings/setting-page-utils.ts
Normal file
49
packages/obsidian/src/ui/settings/setting-page-utils.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import type { SettingGroup } from 'obsidian';
|
||||
|
||||
export const ALL_FILTER_VALUE = '__all__';
|
||||
export const DEFAULT_VISIBLE_SETTING_ITEMS = 20;
|
||||
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
}
|
||||
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
}
|
||||
|
||||
export function formatDate(timestamp: number): string {
|
||||
return new Date(timestamp).toLocaleString();
|
||||
}
|
||||
|
||||
export function formatCount(count: number, singularLabel: string, pluralLabel: string = `${singularLabel}s`): string {
|
||||
return `${count} ${count === 1 ? singularLabel : pluralLabel}`;
|
||||
}
|
||||
|
||||
export function groupBySorted<TItem, TKey>(items: TItem[], getKey: (item: TItem) => TKey, compareKeys: (left: TKey, right: TKey) => number): [TKey, TItem[]][] {
|
||||
const groupedItems = new Map<TKey, TItem[]>();
|
||||
|
||||
for (const item of items) {
|
||||
const key = getKey(item);
|
||||
const group = groupedItems.get(key);
|
||||
|
||||
if (group === undefined) {
|
||||
groupedItems.set(key, [item]);
|
||||
} else {
|
||||
group.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return [...groupedItems.entries()].sort(([left], [right]) => compareKeys(left, right));
|
||||
}
|
||||
|
||||
export function visibleSettingItems<TItem>(items: TItem[], limit: number = DEFAULT_VISIBLE_SETTING_ITEMS): TItem[] {
|
||||
return items.slice(0, limit);
|
||||
}
|
||||
|
||||
export function renderHiddenSettingItemCount(group: SettingGroup, hiddenCount: number, itemLabel: string): void {
|
||||
if (hiddenCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
group.addSetting(setting => void setting.setName(`${hiddenCount} more ${itemLabel} ${hiddenCount === 1 ? 'is' : 'are'} hidden.`));
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import type { App } from 'obsidian';
|
||||
import { Notice, Setting, SettingGroup } from 'obsidian';
|
||||
import type { PermissionApproval } from 'packages/obsidian/src/permissions/approval-store';
|
||||
import type { SettingDefinitionItem } from 'obsidian';
|
||||
import type { LocalStoragePermissionApprovalStore } from 'packages/obsidian/src/permissions/approval-store';
|
||||
import type { ScriptStorageEntry } from 'packages/obsidian/src/storage/script-storage';
|
||||
import { ScriptStorageManager } from 'packages/obsidian/src/storage/script-storage';
|
||||
import { ApprovedHashesSettingPage } from 'packages/obsidian/src/ui/settings/approved-hashes-settings-page';
|
||||
import { ScriptStorageSettingPage } from 'packages/obsidian/src/ui/settings/script-storage-settings-page';
|
||||
|
||||
export class StoredDataSettingsSection {
|
||||
private readonly app: App;
|
||||
|
|
@ -17,133 +17,23 @@ export class StoredDataSettingsSection {
|
|||
this.onStorageChanged = onStorageChanged;
|
||||
}
|
||||
|
||||
render(containerEl: HTMLElement): void {
|
||||
this.renderApprovalStorage(containerEl);
|
||||
this.renderScriptStorage(containerEl);
|
||||
}
|
||||
|
||||
private renderApprovalStorage(containerEl: HTMLElement): void {
|
||||
getSettingDefinitions(): SettingDefinitionItem[] {
|
||||
const approvals = this.approvalStore.list();
|
||||
this.createSection(containerEl, 'Approved script hashes');
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Clear approved hashes')
|
||||
.setDesc('Remove remembered permission approvals for changed or old scripts.')
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear older than 30 days').onClick(() => {
|
||||
const deletedCount = this.approvalStore.deleteOlderThan(Date.now() - this.staleEntryAgeMs);
|
||||
new Notice(`Cleared ${deletedCount} approved script ${deletedCount === 1 ? 'hash' : 'hashes'}.`);
|
||||
this.onStorageChanged();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear all').onClick(() => {
|
||||
const deletedCount = this.approvalStore.deleteAll();
|
||||
new Notice(`Cleared ${deletedCount} approved script ${deletedCount === 1 ? 'hash' : 'hashes'}.`);
|
||||
this.onStorageChanged();
|
||||
}),
|
||||
);
|
||||
|
||||
this.renderApprovalList(new SettingGroup(containerEl), approvals);
|
||||
}
|
||||
|
||||
private renderApprovalList(group: SettingGroup, approvals: PermissionApproval[]): void {
|
||||
if (approvals.length === 0) {
|
||||
group.addSetting(setting => {
|
||||
setting.setName('No approved script hashes are stored.');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const approval of approvals.slice(0, 20)) {
|
||||
group.addSetting(setting => {
|
||||
setting
|
||||
.setName(approval.codeHash)
|
||||
.setDesc(`${formatCaller(approval.callerPluginId)} - ${approval.permissions.join(', ')} - ${formatDate(approval.updatedAt)}`)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Revoke').onClick(() => {
|
||||
this.approvalStore.delete(approval);
|
||||
new Notice(`Revoked approval for script hash ${approval.codeHash}.`);
|
||||
this.onStorageChanged();
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (approvals.length > 20) {
|
||||
group.addSetting(setting => {
|
||||
setting.setName(`${approvals.length - 20} more approved hashes are hidden.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private renderScriptStorage(containerEl: HTMLElement): void {
|
||||
const entries = ScriptStorageManager.listAll(this.app);
|
||||
this.createSection(containerEl, 'Script storage');
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Clear script storage')
|
||||
.setDesc('Remove data written through the storage API by scripts.')
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear older than 30 days').onClick(() => {
|
||||
const deletedCount = ScriptStorageManager.deleteOlderThanAll(this.app, Date.now() - this.staleEntryAgeMs);
|
||||
new Notice(`Cleared ${deletedCount} script storage ${deletedCount === 1 ? 'key' : 'keys'}.`);
|
||||
this.onStorageChanged();
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Clear all').onClick(() => {
|
||||
const deletedCount = ScriptStorageManager.deleteAllKnown(this.app);
|
||||
new Notice(`Cleared ${deletedCount} script storage ${deletedCount === 1 ? 'key' : 'keys'}.`);
|
||||
this.onStorageChanged();
|
||||
}),
|
||||
);
|
||||
|
||||
this.renderScriptStorageList(new SettingGroup(containerEl), entries);
|
||||
}
|
||||
|
||||
private renderScriptStorageList(group: SettingGroup, entries: ScriptStorageEntry[]): void {
|
||||
if (entries.length === 0) {
|
||||
group.addSetting(setting => {
|
||||
setting.setName('No indexed script storage keys are stored.');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries.slice(0, 20)) {
|
||||
group.addSetting(setting => {
|
||||
setting.setName(entry.key).setDesc(`${formatScope(entry.scope)} - ${formatBytes(entry.sizeBytes)} - ${formatDate(entry.updatedAt)}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (entries.length > 20) {
|
||||
group.addSetting(setting => {
|
||||
setting.setName(`${entries.length - 20} more storage keys are hidden.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private createSection(containerEl: HTMLElement, heading: string): void {
|
||||
new Setting(containerEl).setName(heading).setHeading();
|
||||
return [
|
||||
{
|
||||
type: 'page',
|
||||
name: 'Approved script hashes',
|
||||
desc: `${approvals.length} stored`,
|
||||
page: () => new ApprovedHashesSettingPage(this.approvalStore, this.staleEntryAgeMs, this.onStorageChanged),
|
||||
},
|
||||
{
|
||||
type: 'page',
|
||||
name: 'Script storage',
|
||||
desc: `${entries.length} indexed`,
|
||||
page: () => new ScriptStorageSettingPage(this.app, this.staleEntryAgeMs, this.onStorageChanged),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(timestamp: number): string {
|
||||
return new Date(timestamp).toLocaleString();
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
}
|
||||
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
}
|
||||
|
||||
function formatCaller(callerPluginId: string | undefined): string {
|
||||
return callerPluginId ?? 'notes';
|
||||
}
|
||||
|
||||
function formatScope(scope: string | null): string {
|
||||
return scope === null ? 'global' : `scoped ${scope.slice(0, 12)}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { App } from 'obsidian';
|
||||
import { Setting, SettingGroup } from 'obsidian';
|
||||
import type { SettingDefinitionItem } from 'obsidian';
|
||||
import type SafeJsPlugin from 'packages/obsidian/src/main';
|
||||
import type { SafeJsScriptConfig } from 'packages/obsidian/src/scripts/script-settings';
|
||||
import { createScriptConfig, displayNameFromPath, isJavaScriptVaultScriptPath } from 'packages/obsidian/src/scripts/script-settings';
|
||||
|
|
@ -20,49 +20,40 @@ export class VaultScriptsSettingsSection {
|
|||
this.scriptManager = scriptManager;
|
||||
}
|
||||
|
||||
render(containerEl: HTMLElement): void {
|
||||
this.createSection(containerEl, 'Vault scripts');
|
||||
const group = new SettingGroup(containerEl);
|
||||
|
||||
for (const script of this.plugin.settings.scripts) {
|
||||
this.renderVaultScript(group, script);
|
||||
}
|
||||
|
||||
this.renderAddVaultScript(group);
|
||||
}
|
||||
|
||||
private renderVaultScript(group: SettingGroup, script: SafeJsScriptConfig): void {
|
||||
group.addSetting(setting => {
|
||||
setting
|
||||
.setName(script.name)
|
||||
.setDesc(formatVaultScriptDescription(script))
|
||||
.addButton(button =>
|
||||
button.setButtonText('Edit').onClick(() => {
|
||||
this.openEditVaultScriptModal(script);
|
||||
}),
|
||||
)
|
||||
.addButton(button =>
|
||||
button.setButtonText('Remove').onClick(() => {
|
||||
void this.removeVaultScript(script);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private renderAddVaultScript(group: SettingGroup): void {
|
||||
group.addSetting(setting => {
|
||||
setting
|
||||
.setName('Add script')
|
||||
.setDesc('Configure a vault .js file as a command.')
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText('Add script')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.openAddVaultScriptModal();
|
||||
}),
|
||||
);
|
||||
});
|
||||
getSettingDefinitions(): SettingDefinitionItem[] {
|
||||
return [
|
||||
{
|
||||
type: 'page',
|
||||
name: 'Vault scripts',
|
||||
desc: 'Configure vault .js files as commands.',
|
||||
items: [
|
||||
{
|
||||
type: 'list',
|
||||
emptyState: 'No vault scripts are configured.',
|
||||
addItem: {
|
||||
name: 'Add script',
|
||||
action: (): void => {
|
||||
this.openAddVaultScriptModal();
|
||||
},
|
||||
},
|
||||
onDelete: (index): void => {
|
||||
void this.removeVaultScript(this.plugin.settings.scripts[index]);
|
||||
},
|
||||
items: this.plugin.settings.scripts.map(script => ({
|
||||
name: script.name,
|
||||
desc: formatVaultScriptDescription(script),
|
||||
render: (setting): void => {
|
||||
setting.addExtraButton(button =>
|
||||
button.setIcon('edit').onClick(() => {
|
||||
this.openEditVaultScriptModal(script);
|
||||
}),
|
||||
);
|
||||
},
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private openAddVaultScriptModal(): void {
|
||||
|
|
@ -81,7 +72,11 @@ export class VaultScriptsSettingsSection {
|
|||
).open();
|
||||
}
|
||||
|
||||
private async removeVaultScript(script: SafeJsScriptConfig): Promise<void> {
|
||||
private async removeVaultScript(script: SafeJsScriptConfig | undefined): Promise<void> {
|
||||
if (script === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.settings.scripts = this.plugin.settings.scripts.filter(candidate => candidate.id !== script.id);
|
||||
await this.saveScriptSettings();
|
||||
this.onSettingsChanged();
|
||||
|
|
@ -143,10 +138,6 @@ export class VaultScriptsSettingsSection {
|
|||
await this.plugin.saveSettings();
|
||||
this.scriptManager?.reloadCommands();
|
||||
}
|
||||
|
||||
private createSection(containerEl: HTMLElement, heading: string): void {
|
||||
new Setting(containerEl).setName(heading).setHeading();
|
||||
}
|
||||
}
|
||||
|
||||
function formatVaultScriptDescription(script: SafeJsScriptConfig): string {
|
||||
|
|
|
|||
Loading…
Reference in a new issue