diff --git a/src/effect.ts b/src/effect.ts index fdceec3..b0ac0c6 100644 --- a/src/effect.ts +++ b/src/effect.ts @@ -3,16 +3,16 @@ import type { EditorView, WidgetType } from "@codemirror/view"; export type WidgetInit = | { state: "resolving"; url: string } - | { state: "resolved"; url: string; src: string } + | { state: "resolved"; url: string } | { state: "loaded"; url: string } | { state: "failed"; url: string; error: Error }; -const resolvedEffect = StateEffect.define<{ url: string; src: string }>(); +const resolvedEffect = StateEffect.define<{ url: string }>(); const failedEffect = StateEffect.define<{ url: string; error: Error }>(); const loadedEffect = StateEffect.define<{ url: string }>(); -export function resolved(view: EditorView, url: string, src: string): void { - view.dispatch({ effects: resolvedEffect.of({ url, src }) }); +export function resolved(view: EditorView, url: string): void { + view.dispatch({ effects: resolvedEffect.of({ url }) }); } export function failed(view: EditorView, url: string, error: Error): void { @@ -29,10 +29,7 @@ export function* constructWidget( ): Generator<[string, T]> { for (const effect of effects) { if (effect.is(resolvedEffect)) { - yield [ - effect.value.url, - new widget({ state: "resolved", url: effect.value.url, src: effect.value.src }), - ]; + yield [effect.value.url, new widget({ state: "resolved", url: effect.value.url })]; } else if (effect.is(failedEffect)) { yield [ effect.value.url, diff --git a/src/embed_source.ts b/src/embed_source.ts index 0980ed7..32af783 100644 --- a/src/embed_source.ts +++ b/src/embed_source.ts @@ -20,8 +20,8 @@ export abstract class EmbedSource { constructor(url: string) { this.url = url; } - abstract render(src: string): HTMLElement; - abstract resolveSrc(): string | Promise; + abstract render(): HTMLElement; + resolveSrc(): void | Promise {} get height(): number | undefined { return undefined; } diff --git a/src/source/bluesky.ts b/src/source/bluesky.ts index e0e084f..cf32cc9 100644 --- a/src/source/bluesky.ts +++ b/src/source/bluesky.ts @@ -49,6 +49,7 @@ export class Bluesky extends EmbedSource { static #heightCache: Map = new Map(); static id = 0; #id = (Bluesky.id++).toString(); + #src?: string; constructor(url: string) { super(url); } @@ -61,7 +62,7 @@ export class Bluesky extends EmbedSource { }; } - render(src: string): HTMLElement { + render(): HTMLElement { const searchParams = new URLSearchParams({ id: this.#id, colorMode: document.body.classList.contains("theme-dark") ? "dark" : "light", @@ -69,7 +70,7 @@ export class Bluesky extends EmbedSource { const iframe = createEl("iframe", { cls: ["external-embed", "node-insert-event"], attr: { - src: src + "?" + searchParams.toString(), + src: this.#src + "?" + searchParams.toString(), loading: "lazy", "data-bluesky-id": this.#id, }, @@ -80,8 +81,13 @@ export class Bluesky extends EmbedSource { return iframe; } - override resolveSrc(): string | Promise { - return resolveEmbedSrc(this.url); + override resolveSrc(): void | Promise { + if (this.#src) { + return; + } + return resolveEmbedSrc(this.url).then((src) => { + this.#src = src; + }); } override get height(): number | undefined { diff --git a/src/widget.ts b/src/widget.ts index 981f74c..efa2949 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -30,7 +30,6 @@ function Container(url: string, state: WidgetInit["state"]): HTMLElement { export class EmbedWidget extends WidgetType { #url: string; #state: WidgetInit["state"]; - #src?: string; #error?: Error; #embedSource: EmbedSource; constructor(init: WidgetInit) { @@ -38,9 +37,6 @@ export class EmbedWidget extends WidgetType { this.#url = init.url; this.#state = init.state; switch (init.state) { - case "resolved": - this.#src = init.src; - break; case "failed": this.#error = init.error; break; @@ -58,24 +54,23 @@ export class EmbedWidget extends WidgetType { switch (this.#state) { case "resolving": container.appendChild(Loading(this.#embedSource.height)); - const srcOrPromise = this.#embedSource.resolveSrc(); - if (srcOrPromise instanceof Promise) { - srcOrPromise - .then((src) => resolved(view, this.#url, src)) + const needResolve = this.#embedSource.resolveSrc(); + if (needResolve instanceof Promise) { + needResolve + .then(() => resolved(view, this.#url)) .catch((error) => failed(view, this.#url, error)); } else { this.#state = "resolved"; - this.#src = srcOrPromise; container.setAttribute("data-state", "resolved"); - container.appendChild(this.#embedSource.render(this.#src!)); + container.appendChild(this.#embedSource.render()); } break; case "resolved": container.appendChild(Loading(this.#embedSource.height)); - container.appendChild(this.#embedSource.render(this.#src!)); + container.appendChild(this.#embedSource.render()); break; case "loaded": - container.appendChild(this.#embedSource.render(this.#src!)); + container.appendChild(this.#embedSource.render()); break; case "failed": container.appendChild(ErrorMessage(this.#error!)); @@ -102,7 +97,7 @@ export class EmbedWidget extends WidgetType { case "resolving": return false; case "resolved": - dom.appendChild(this.#embedSource.render(this.#src!)); + dom.appendChild(this.#embedSource.render()); return true; case "loaded": const iframe = dom.querySelector("iframe"); @@ -119,7 +114,6 @@ export class EmbedWidget extends WidgetType { } } -// TODO: use "loading" state export function createElement(url: string, dom: HTMLElement): void { const embedSource = EmbedSourceRegistry.lookup(url); if (!embedSource) { @@ -136,12 +130,12 @@ export function createElement(url: string, dom: HTMLElement): void { } loading.remove(); }); - const srcOrPromise = embedSource.resolveSrc(); - if (srcOrPromise instanceof Promise) { - srcOrPromise - .then((src) => { + const needResolve = embedSource.resolveSrc(); + if (needResolve instanceof Promise) { + needResolve + .then(() => { container.setAttribute("data-state", "resolved"); - container.appendChild(embedSource.render(src)); + container.appendChild(embedSource.render()); }) .catch((error) => { container.setAttribute("data-state", "failed"); @@ -149,7 +143,7 @@ export function createElement(url: string, dom: HTMLElement): void { loading.remove(); }); } else { - container.appendChild(embedSource.render(srcOrPromise)); + container.appendChild(embedSource.render()); } dom.replaceWith(container); }