import { Decoration, EditorState, Range, syntaxTree, WidgetType, } from "../deps.ts"; import { decoratorStateField } from "./util.ts"; import type { Space } from "../space.ts"; class InlineImageWidget extends WidgetType { constructor( readonly url: string, readonly title: string, readonly space: Space, ) { super(); // console.log("Creating widget", url); } eq(other: InlineImageWidget) { return other.url === this.url && other.title === this.title; } get estimatedHeight(): number { const cachedHeight = this.space.getCachedImageHeight(this.url); // console.log("Estimated height requested", this.url, cachedHeight); return cachedHeight; } toDOM() { const img = document.createElement("img"); // console.log("Creating DOM", this.url); const cachedImageHeight = this.space.getCachedImageHeight(this.url); img.onload = () => { // console.log("Loaded", this.url, "with height", img.height); if (img.height !== cachedImageHeight) { this.space.setCachedImageHeight(this.url, img.height); } }; if (this.url.startsWith("http")) { img.src = this.url; } else { // This is an attachment image, rewrite the URL a little img.src = `/.fs/${decodeURIComponent(this.url)}`; } img.alt = this.title; img.title = this.title; img.style.display = "block"; img.className = "sb-inline-img"; if (cachedImageHeight > 0) { img.height = cachedImageHeight; } return img; } } export function inlineImagesPlugin(space: Space) { return decoratorStateField((state: EditorState) => { const widgets: Range[] = []; const imageRegex = /!\[(?[^\]]*)\]\((?<url>.+)\)/; syntaxTree(state).iterate({ enter: (node) => { if (node.name !== "Image") { return; } const imageRexexResult = imageRegex.exec( state.sliceDoc(node.from, node.to), ); if (imageRexexResult === null || !imageRexexResult.groups) { return; } const url = imageRexexResult.groups.url; const title = imageRexexResult.groups.title; widgets.push( Decoration.widget({ widget: new InlineImageWidget(url, title, space), block: true, }).range(node.to), ); }, }); return Decoration.set(widgets, true); }); }