diff --git a/.changeset/dry-ladybugs-pretend.md b/.changeset/dry-ladybugs-pretend.md new file mode 100644 index 0000000..3b06100 --- /dev/null +++ b/.changeset/dry-ladybugs-pretend.md @@ -0,0 +1,5 @@ +--- +"obsidian-modules": minor +--- + +Add support for importing external modules via HTTP and HTTPS. diff --git a/README.md b/README.md index a343fcb..12681a2 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This file is automatically opened on first install. You can reopen it in setting - Reuse JavaScript and TypeScript code from anywhere in your vault on all platforms. - No configuration needed. -- Resolves relative paths, vault paths, Markdown links, and wikilinks. +- Resolves relative paths, vault paths, Markdown links, wikilinks, and external links. - Loads Markdown files as code. - Supports using other modules inside modules. - Loads CommonJS (`module.exports`) and ES modules (`export`). diff --git a/sources/require/require.ts b/sources/require/require.ts index eb8b3b7..aa61c6e 100644 --- a/sources/require/require.ts +++ b/sources/require/require.ts @@ -10,6 +10,7 @@ import { } from "@polyipseity/obsidian-plugin-library" import { CompositeResolve, + ExternalResolve, InternalModulesResolve, MarkdownLinkResolve, RelativePathResolve, @@ -50,6 +51,7 @@ export function loadRequire(context: ModulesPlugin): void { new VaultPathResolve(context, transpiles), new WikilinkResolve(context, transpiles), new MarkdownLinkResolve(context, transpiles), + new ExternalResolve(context), ]) context.register(patchWindows(workspace, self0 => patchRequire(context, self0, resolve))) diff --git a/sources/require/resolve.ts b/sources/require/resolve.ts index 158ad95..5ae2ed2 100644 --- a/sources/require/resolve.ts +++ b/sources/require/resolve.ts @@ -7,7 +7,7 @@ import { dynamicRequireSync, } from "@polyipseity/obsidian-plugin-library" import type { Context, Resolve, Resolved } from "obsidian-modules" -import { TFile, getLinkpath, normalizePath } from "obsidian" +import { TFile, getLinkpath, normalizePath, requestUrl } from "obsidian" import type { AsyncOrSync } from "ts-essentials" import type { ModulesPlugin } from "../main.js" import type { Transpile } from "./transpile.js" @@ -508,3 +508,52 @@ function parseWikilink( if (str !== link || isUndefined(path) || isUndefined(display)) { return null } return { display: display || (str.includes("|") ? "" : path), path } } + +export class ExternalResolve + extends AbstractResolve + implements Resolve { + protected readonly identities: Record = {} + + public constructor(context: ModulesPlugin) { + super(context) + } + + public override resolve(id: string, _1: Context): Resolved | null { + const href = this.normalizeURL(id) + if (href === null) { return null } + const { identities: { [href]: identity } } = this + return (identity && { code: identity.code, id: href, identity }) ?? null + } + + public override async aresolve( + id: string, + _1: Context, + ): Promise { + const href = this.normalizeURL(id) + if (href === null) { return null } + let { identities: { [href]: identity } } = this + if (isUndefined(identity)) { + let code = null + try { + ({ text: code } = await requestUrl(href)) + } catch (error) { + self.console.debug(error) + } + // eslint-disable-next-line no-multi-assign + this.identities[href] = identity = code === null ? null : { code } + } + return identity && { code: identity.code, id: href, identity } + } + + protected normalizeURL(id: string): string | null { + try { + const { href, protocol } = new URL(id) + if ((/^https?:$/u).test(protocol)) { return href } + } catch (error) { + self.console.debug(error) + } + return null + } +}