diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2eb93a..c2879dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,3 +52,4 @@ jobs: main.js manifest.json styles.css + JsEngine.d.ts diff --git a/JsEngine.d.ts b/JsEngine.d.ts index 303a715..ed44212 100644 --- a/JsEngine.d.ts +++ b/JsEngine.d.ts @@ -158,11 +158,10 @@ declare module 'jsEngine/messages/MessageManager' { } declare module 'jsEngine/settings/StartupScriptModal' { import type JsEnginePlugin from 'jsEngine/main'; - import StartupScripts from 'jsEngine/settings/StartupScripts.svelte'; import { Modal } from 'obsidian'; export class StartupScriptsModal extends Modal { - plugin: JsEnginePlugin; - component?: ReturnType; + private readonly plugin; + private component?; constructor(plugin: JsEnginePlugin); onOpen(): void; onClose(): void; @@ -194,7 +193,8 @@ declare module 'jsEngine/api/markdown/MarkdownElementType' { } declare module 'jsEngine/api/markdown/MarkdownString' { import type { API } from 'jsEngine/api/API'; - import type { App, Component } from 'obsidian'; + import type { Component } from 'obsidian'; + import { App } from 'obsidian'; /** * A string that should be rendered as markdown by the plugin. */ @@ -689,6 +689,7 @@ declare module 'jsEngine/utils/Validators' { import { z } from 'zod'; export function schemaForType(): >(arg: S) => S; export function validateAPIArgs(validator: z.ZodType, args: T): void; + export function zodFunction(): z.ZodCustom; export class Validators { htmlElement: z.ZodType; voidFunction: z.ZodType<() => void, any, any>; @@ -1005,6 +1006,7 @@ declare module 'jsEngine/api/Internal' { */ executeStartupScripts(): Promise; private getFileWithExtension; + private tryGetFileWithExtension; } } declare module 'jsEngine/api/LibAPI' { @@ -1370,8 +1372,9 @@ declare module 'jsEngine/engine/JsExecution' { executionSource: ExecutionSource.MarkdownCodeBlock; /** * The file that the code block is in. + * Since rendered markdown does not necessarily have an associated file, this can be undefined. */ - file: TFile; + file?: TFile; /** * The metadata of the file. */ @@ -1389,8 +1392,9 @@ declare module 'jsEngine/engine/JsExecution' { executionSource: ExecutionSource.MarkdownCallingJSFile; /** * The markdown file that the JS File is called from. + * Since rendered markdown does not necessarily have an associated file, this can be undefined. */ - file: TFile; + file?: TFile; /** * The metadata of the markdown file. */ @@ -1403,9 +1407,10 @@ declare module 'jsEngine/engine/JsExecution' { export interface MarkdownOtherExecutionContext { executionSource: ExecutionSource.MarkdownOther; /** - * The file that the code block is in. + * The file that the markdown is associated with. + * Since rendered markdown does not necessarily have an associated file, this can be undefined. */ - file: TFile; + file?: TFile; /** * The metadata of the file. */ @@ -1413,6 +1418,10 @@ declare module 'jsEngine/engine/JsExecution' { } export interface JSFileExecutionContext { executionSource: ExecutionSource.JSFile; + /** + * There is no associated markdown file. + */ + file: undefined; /** * The JS that is being executed. */ diff --git a/bun.lockb b/bun.lockb index e554b59..5f7172f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/jsEngine/JsMDRC.ts b/jsEngine/JsMDRC.ts index 646e751..ec232fc 100644 --- a/jsEngine/JsMDRC.ts +++ b/jsEngine/JsMDRC.ts @@ -36,15 +36,13 @@ export class JsMDRC extends MarkdownRenderChild { } buildExecutionContext(): ExecutionContext { - // console.log(this.ctx); const file = this.getExecutionFile(); - if (file === undefined) { - throw new Error('Could not find file for execution context.'); - } + const metadata = file ? (this.plugin.app.metadataCache.getFileCache(file) ?? undefined) : undefined; + return { executionSource: ExecutionSource.MarkdownCodeBlock, file: file, - metadata: this.plugin.app.metadataCache.getFileCache(file) ?? undefined, + metadata: metadata, block: undefined, }; } diff --git a/jsEngine/api/Internal.ts b/jsEngine/api/Internal.ts index 154db9c..2897f99 100644 --- a/jsEngine/api/Internal.ts +++ b/jsEngine/api/Internal.ts @@ -79,6 +79,7 @@ export class InternalAPI { return await this.execute({ context: { executionSource: ExecutionSource.JSFile, + file: undefined, jsFile: file, }, ...params, @@ -145,8 +146,8 @@ export class InternalAPI { public async getContextForMarkdownCodeBlock(path: string): Promise { validateAPIArgs(z.object({ path: z.string() }), { path }); - const file = this.getFileWithExtension(path, 'md'); - const metadata = this.apiInstance.app.metadataCache.getFileCache(file); + const file = this.tryGetFileWithExtension(path, 'md'); + const metadata = file ? this.apiInstance.app.metadataCache.getFileCache(file) : undefined; return { executionSource: ExecutionSource.MarkdownCodeBlock, @@ -167,8 +168,8 @@ export class InternalAPI { public async getContextForMarkdownCallingJSFile(markdownPath: string, jsPath: string): Promise { validateAPIArgs(z.object({ markdownPath: z.string(), jsPath: z.string() }), { markdownPath, jsPath }); - const markdownFile = this.getFileWithExtension(markdownPath, 'md'); - const metadata = this.apiInstance.app.metadataCache.getFileCache(markdownFile); + const markdownFile = this.tryGetFileWithExtension(markdownPath, 'md'); + const metadata = markdownFile ? this.apiInstance.app.metadataCache.getFileCache(markdownFile) : undefined; const jsFile = this.getFileWithExtension(jsPath, 'js'); @@ -189,8 +190,8 @@ export class InternalAPI { public async getContextForMarkdownOther(path: string): Promise { validateAPIArgs(z.object({ path: z.string() }), { path }); - const file = this.getFileWithExtension(path, 'md'); - const metadata = this.apiInstance.app.metadataCache.getFileCache(file); + const file = this.tryGetFileWithExtension(path, 'md'); + const metadata = file ? this.apiInstance.app.metadataCache.getFileCache(file) : undefined; return { executionSource: ExecutionSource.MarkdownOther, @@ -212,6 +213,7 @@ export class InternalAPI { return { executionSource: ExecutionSource.JSFile, + file: undefined, jsFile: file, }; } @@ -279,4 +281,15 @@ export class InternalAPI { } return file; } + + private tryGetFileWithExtension(path: string, extension: string): TFile | undefined { + const file = this.apiInstance.app.vault.getAbstractFileByPath(path); + if (!file || !(file instanceof TFile)) { + return undefined; + } + if (file.extension !== extension && file.extension !== `.${extension}`) { + return undefined; + } + return file; + } } diff --git a/jsEngine/api/QueryAPI.ts b/jsEngine/api/QueryAPI.ts index 7e8d7a8..dd9edf4 100644 --- a/jsEngine/api/QueryAPI.ts +++ b/jsEngine/api/QueryAPI.ts @@ -1,5 +1,5 @@ import type { API } from 'jsEngine/api/API'; -import { validateAPIArgs } from 'jsEngine/utils/Validators'; +import { validateAPIArgs, zodFunction } from 'jsEngine/utils/Validators'; import type { CachedMetadata, TFile } from 'obsidian'; import { getAllTags } from 'obsidian'; import { z } from 'zod'; @@ -27,7 +27,7 @@ export class QueryAPI { * ``` */ public files(query: (file: TFile) => T | undefined): T[] { - validateAPIArgs(z.object({ query: z.function().args(this.apiInstance.validators.tFile).returns(z.unknown()) }), { query }); + validateAPIArgs(z.object({ query: zodFunction() }), { query }); return this.apiInstance.app.vault .getMarkdownFiles() @@ -47,10 +47,7 @@ export class QueryAPI { public filesWithMetadata(query: (file: TFile, cache: CachedMetadata | undefined, tags: string[], frontmatterTags: string[]) => T | undefined): T[] { validateAPIArgs( z.object({ - query: z - .function() - .args(this.apiInstance.validators.tFile, this.apiInstance.validators.cachedMetadata.optional(), z.string().array(), z.string().array()) - .returns(z.unknown()), + query: zodFunction(), }), { query }, ); diff --git a/jsEngine/api/markdown/MarkdownString.ts b/jsEngine/api/markdown/MarkdownString.ts index a80e733..1b9c7a2 100644 --- a/jsEngine/api/markdown/MarkdownString.ts +++ b/jsEngine/api/markdown/MarkdownString.ts @@ -1,6 +1,7 @@ import type { API } from 'jsEngine/api/API'; import { validateAPIArgs } from 'jsEngine/utils/Validators'; -import type { App, Component } from 'obsidian'; +import type { Component } from 'obsidian'; +import { App } from 'obsidian'; import { MarkdownRenderer } from 'obsidian'; import { z } from 'zod'; @@ -22,7 +23,7 @@ export class MarkdownString { async render(app: App, element: HTMLElement, sourcePath: string, component: Component): Promise { validateAPIArgs( z.object({ - app: z.object({}), + app: z.instanceof(App), element: this.apiInstance.validators.htmlElement, sourcePath: z.string(), component: this.apiInstance.validators.component, diff --git a/jsEngine/api/prompts/SvelteModal.ts b/jsEngine/api/prompts/SvelteModal.ts index ea1658e..a32685b 100644 --- a/jsEngine/api/prompts/SvelteModal.ts +++ b/jsEngine/api/prompts/SvelteModal.ts @@ -42,7 +42,7 @@ export class SvelteModal extends Modal } if (this.component) { - unmount(this.component); + void unmount(this.component); } this.contentEl.empty(); } diff --git a/jsEngine/engine/ExecutionStatsModal.ts b/jsEngine/engine/ExecutionStatsModal.ts index 36b4e33..94ed3ac 100644 --- a/jsEngine/engine/ExecutionStatsModal.ts +++ b/jsEngine/engine/ExecutionStatsModal.ts @@ -26,7 +26,7 @@ export class ExecutionStatsModal extends Modal { public onOpen(): void { this.contentEl.empty(); if (this.component) { - unmount(this.component); + void unmount(this.component); } if (!this.contentEl.hasClass('js-engine-execution-stats-modal')) { @@ -49,7 +49,7 @@ export class ExecutionStatsModal extends Modal { public onClose(): void { this.contentEl.empty(); if (this.component) { - unmount(this.component); + void unmount(this.component); } } } diff --git a/jsEngine/engine/JsExecution.ts b/jsEngine/engine/JsExecution.ts index e5a929a..a9b194a 100644 --- a/jsEngine/engine/JsExecution.ts +++ b/jsEngine/engine/JsExecution.ts @@ -24,8 +24,9 @@ export interface MarkdownCodeBlockExecutionContext { executionSource: ExecutionSource.MarkdownCodeBlock; /** * The file that the code block is in. + * Since rendered markdown does not necessarily have an associated file, this can be undefined. */ - file: TFile; + file?: TFile; /** * The metadata of the file. */ @@ -45,8 +46,9 @@ export interface MarkdownCallingJSFileExecutionContext { executionSource: ExecutionSource.MarkdownCallingJSFile; /** * The markdown file that the JS File is called from. + * Since rendered markdown does not necessarily have an associated file, this can be undefined. */ - file: TFile; + file?: TFile; /** * The metadata of the markdown file. */ @@ -60,9 +62,10 @@ export interface MarkdownCallingJSFileExecutionContext { export interface MarkdownOtherExecutionContext { executionSource: ExecutionSource.MarkdownOther; /** - * The file that the code block is in. + * The file that the markdown is associated with. + * Since rendered markdown does not necessarily have an associated file, this can be undefined. */ - file: TFile; + file?: TFile; /** * The metadata of the file. */ @@ -71,6 +74,10 @@ export interface MarkdownOtherExecutionContext { export interface JSFileExecutionContext { executionSource: ExecutionSource.JSFile; + /** + * There is no associated markdown file. + */ + file: undefined; /** * The JS that is being executed. */ diff --git a/jsEngine/engine/ResultRenderer.ts b/jsEngine/engine/ResultRenderer.ts index 5bcdbde..c99d9a1 100644 --- a/jsEngine/engine/ResultRenderer.ts +++ b/jsEngine/engine/ResultRenderer.ts @@ -68,7 +68,7 @@ export class ResultRenderer { }); this.component.register(() => { - unmount(svelteComponent); + void unmount(svelteComponent); }); return; } diff --git a/jsEngine/messages/MessageDisplay.ts b/jsEngine/messages/MessageDisplay.ts index 38ad913..89f7dd3 100644 --- a/jsEngine/messages/MessageDisplay.ts +++ b/jsEngine/messages/MessageDisplay.ts @@ -20,7 +20,7 @@ export class MessageDisplay extends Modal { public onOpen(): void { this.contentEl.empty(); if (this.component) { - unmount(this.component); + void unmount(this.component); } this.component = mount(MessageDisplayComponent, { @@ -34,7 +34,7 @@ export class MessageDisplay extends Modal { public onClose(): void { this.contentEl.empty(); if (this.component) { - unmount(this.component); + void unmount(this.component); } } } diff --git a/jsEngine/settings/StartupScriptModal.ts b/jsEngine/settings/StartupScriptModal.ts index 6e9357d..15ccf64 100644 --- a/jsEngine/settings/StartupScriptModal.ts +++ b/jsEngine/settings/StartupScriptModal.ts @@ -1,11 +1,12 @@ import type JsEnginePlugin from 'jsEngine/main'; import StartupScripts from 'jsEngine/settings/StartupScripts.svelte'; +import type { MountedComponent } from 'jsEngine/utils/SvelteUtils'; import { Modal } from 'obsidian'; import { mount, unmount } from 'svelte'; export class StartupScriptsModal extends Modal { - plugin: JsEnginePlugin; - component?: ReturnType; + readonly plugin: JsEnginePlugin; + private component?: MountedComponent | undefined; constructor(plugin: JsEnginePlugin) { super(plugin.app); @@ -26,7 +27,7 @@ export class StartupScriptsModal extends Modal { onClose(): void { if (this.component) { - unmount(this.component); + void unmount(this.component); } } diff --git a/jsEngine/utils/Validators.ts b/jsEngine/utils/Validators.ts index e6ad80e..7f8b2a2 100644 --- a/jsEngine/utils/Validators.ts +++ b/jsEngine/utils/Validators.ts @@ -51,6 +51,13 @@ export function validateAPIArgs(validator: z.ZodType, args: T): void { } } +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type,@typescript-eslint/explicit-function-return-type +export function zodFunction() { + return z.custom(val => { + return typeof val === 'function'; + }); +} + export class Validators { htmlElement: z.ZodType; voidFunction: z.ZodType<() => void, any, any>; @@ -83,11 +90,11 @@ export class Validators { numberInputPromptOptions: z.ZodType; constructor() { - this.htmlElement = schemaForType()(z.instanceof(HTMLElement)); - this.voidFunction = schemaForType<() => void>()(z.function().args().returns(z.void())); + this.htmlElement = schemaForType()(z.any()); + this.voidFunction = schemaForType<() => void>()(zodFunction<() => void>()); this.component = schemaForType()(z.instanceof(Component)); this.tFile = schemaForType()(z.instanceof(TFile)); - this.cachedMetadata = schemaForType()(z.record(z.unknown())) as z.ZodType; + this.cachedMetadata = schemaForType()(z.record(z.string(), z.unknown())) as z.ZodType; this.block = schemaForType()( z.object({ from: z.number(), @@ -99,7 +106,7 @@ export class Validators { this.markdownCodeBlockExecutionContext = schemaForType()( z.object({ executionSource: z.literal(ExecutionSource.MarkdownCodeBlock), - file: this.tFile, + file: this.tFile.optional(), metadata: this.cachedMetadata.optional(), block: this.block.optional(), }), @@ -107,7 +114,7 @@ export class Validators { this.markdownCallingJSFileExecutionContext = schemaForType()( z.object({ executionSource: z.literal(ExecutionSource.MarkdownCallingJSFile), - file: this.tFile, + file: this.tFile.optional(), metadata: this.cachedMetadata.optional(), jsFile: this.tFile, }), @@ -115,7 +122,7 @@ export class Validators { this.markdownOtherExecutionContext = schemaForType()( z.object({ executionSource: z.literal(ExecutionSource.MarkdownOther), - file: this.tFile, + file: this.tFile.optional(), metadata: this.cachedMetadata.optional(), }), ); @@ -146,7 +153,7 @@ export class Validators { component: this.component, container: this.htmlElement.optional(), context: this.executionContext, - contextOverrides: z.record(z.unknown()).optional(), + contextOverrides: z.record(z.string(), z.unknown()).optional(), }), ); this.engineExecutionParamsFile = schemaForType()( @@ -154,21 +161,21 @@ export class Validators { component: this.component, container: this.htmlElement.optional(), context: z.union([this.markdownCallingJSFileExecutionContext, this.jsFileExecutionContext]).optional(), - contextOverrides: z.record(z.unknown()).optional(), + contextOverrides: z.record(z.string(), z.unknown()).optional(), }), ); this.engineExecutionParamsFileSimple = schemaForType()( z.object({ container: this.htmlElement.optional(), context: z.union([this.markdownCallingJSFileExecutionContext, this.jsFileExecutionContext]).optional(), - contextOverrides: z.record(z.unknown()).optional(), + contextOverrides: z.record(z.string(), z.unknown()).optional(), }), ); this.jsExecutionGlobalsConstructionOptions = schemaForType()( z.object({ engine: z.instanceof(API).optional(), component: this.component, - context: z.intersection(this.executionContext, z.record(z.unknown())), + context: z.intersection(this.executionContext, z.record(z.string(), z.unknown())), container: this.htmlElement.optional(), }), ); diff --git a/manifest-beta.json b/manifest-beta.json index aefa189..e24b753 100644 --- a/manifest-beta.json +++ b/manifest-beta.json @@ -1,7 +1,7 @@ { "id": "js-engine", "name": "JS Engine", - "version": "0.3.0", + "version": "0.3.2", "minAppVersion": "1.4.0", "description": "Run JavaScript from within your notes.", "author": "Moritz Jung", diff --git a/manifest.json b/manifest.json index aefa189..e24b753 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "js-engine", "name": "JS Engine", - "version": "0.3.0", + "version": "0.3.2", "minAppVersion": "1.4.0", "description": "Run JavaScript from within your notes.", "author": "Moritz Jung", diff --git a/package.json b/package.json index 061d390..4b90a8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-js-engine-plugin", - "version": "0.3.0", + "version": "0.3.2", "description": "This plugin for obsidian allows you to use and run JavaScript from within your notes.", "main": "main.js", "scripts": { @@ -23,41 +23,40 @@ "author": "Moritz Jung", "license": "GPL-3.0", "devDependencies": { - "@codemirror/autocomplete": "^6.18.2", - "@codemirror/lang-javascript": "^6.2.2", - "@codemirror/language": "^6.10.3", - "@codemirror/lint": "^6.8.2", - "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.34.1", - "@eslint/js": "^9.14.0", - "@happy-dom/global-registrator": "^15.8.0", + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/language": "^6.11.2", + "@codemirror/lint": "^6.8.5", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.38.1", + "@eslint/js": "^9.33.0", + "@happy-dom/global-registrator": "^18.0.1", "@tsconfig/svelte": "^5.0.4", - "@types/bun": "^1.1.13", - "builtin-modules": "^4.0.0", - "esbuild": "^0.24.0", + "@types/bun": "^1.2.20", + "builtin-modules": "^5.0.0", + "esbuild": "^0.25.9", "esbuild-plugin-copy-watch": "^2.3.1", - "esbuild-svelte": "^0.8.2", - "eslint": "^9.14.0", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-no-relative-import-paths": "^1.5.5", + "esbuild-svelte": "^0.9.3", + "eslint": "^9.33.0", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-no-relative-import-paths": "^1.6.1", "eslint-plugin-only-warn": "^1.1.0", "obsidian": "latest", - "prettier": "^3.3.3", - "prettier-plugin-svelte": "^3.2.7", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", "string-argv": "^0.3.2", - "svelte": "^5.1.9", - "svelte-check": "^4.0.5", + "svelte": "^5.38.1", + "svelte-check": "^4.3.1", "svelte-preprocess": "^6.0.3", "tslib": "^2.8.1", - "typedoc": "^0.26.11", - "typescript": "^5.6.3", - "typescript-eslint": "^8.12.2" + "typescript": "^5.9.2", + "typescript-eslint": "^8.39.1" }, "dependencies": { - "@codemirror/legacy-modes": "^6.4.1", + "@codemirror/legacy-modes": "^6.5.1", "@lemons_dev/parsinom": "^0.0.12", - "itertools-ts": "^1.27.1", - "zod": "^3.23.8" + "itertools-ts": "^2.2.0", + "zod": "^4.0.17" }, "trustedDependencies": [ "svelte-preprocess" diff --git a/test/placeholder.test.ts b/test/placeholder.test.ts new file mode 100644 index 0000000..a1c414e --- /dev/null +++ b/test/placeholder.test.ts @@ -0,0 +1,5 @@ +import { test, expect } from 'bun:test'; + +test('placeholder test', () => { + expect(true).toBe(true); +}); diff --git a/versions.json b/versions.json index 3082495..f1a6b46 100644 --- a/versions.json +++ b/versions.json @@ -28,5 +28,7 @@ "0.2.0": "1.4.0", "0.2.1": "1.4.0", "0.2.2": "1.4.0", - "0.3.0": "1.4.0" + "0.3.0": "1.4.0", + "0.3.1": "1.4.0", + "0.3.2": "1.4.0" }