From f29b392c6b68b7db98f5d6921ea588b559435f35 Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Mon, 22 Dec 2025 11:29:53 +0100 Subject: Initial implementation --- src/wmap.js | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/wmap.js (limited to 'src') diff --git a/src/wmap.js b/src/wmap.js new file mode 100644 index 0000000..2a179a7 --- /dev/null +++ b/src/wmap.js @@ -0,0 +1,157 @@ +import { parse } from "wmap-parser"; +import { renderToCanvas } from "wmap-renderer-canvas"; +import DOMPurify from "isomorphic-dompurify"; + +const internals = { + kStyleSheet: "/plugins/wmap/wmap.css", + kCanvasWidth: 1384, + kCanvasHeight: 1884, + + // Returns HTML that shows a message in the wiki. + message(text) { + return ` +
+
+ ${text} +
+
`; + }, + + // Creates a wmap PNG from text. + async renderImage(source) { + const parsedText = parse(source); + + const canvas = document.createElement("canvas"); + canvas.width = internals.kCanvasWidth; + canvas.height = internals.kCanvasHeight; + + await renderToCanvas(parsedText, canvas); + return canvas.toDataURL(); + }, + + // Starts a download of the image. + download(filename, text) { + var element = document.createElement("a"); + element.setAttribute("href", text); + element.setAttribute("download", filename); + element.style.display = "none"; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + }, + + // Replaces the image in a window. Intended for use in a dialog. + replaceImage(window, imageData) { + const $container = window.document.querySelector("main"); + $container.querySelectorAll("img").forEach((img) => img.remove()); + + const image = document.createElement("img"); + image.src = imageData; + image.alt = "Wardley map rendered from source."; + $container.appendChild(image); + }, +}; + +/** + * Runs once for each block. Do setup for the block before rendering. + */ +const emit = function emit($item) { + const hasStyleSheet = [...document.styleSheets].some((styleSheet) => + styleSheet.href.endsWith(internals.kStyleSheet), + ); + + if (!hasStyleSheet) { + console.log("Adding wmap stylesheet."); + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = internals.kStyleSheet; + link.type = "text/css"; + document.getElementsByTagName("head")[0].appendChild(link); + } + + return $item.append(internals.message("Loading Wardley Map")); +}; + +/** + * Bind to events on the rendered block. Called once per block. + * Convention is that double click opens the editor. + * + * @param {JQuery} $item the HTML element representing hte block. + * @param {Object} item the configuration for that particular object. + */ +const bind = async function bind($item, item) { + $item.on("dblclick", () => { + return wiki.textEditor($item, item); + }); + + $item.on("click", "a", (event) => { + const { currentTarget } = event; + const action = currentTarget.dataset?.action; + + if (!action) { + return; + } + event.stopPropagation(); + event.preventDefault(); + + switch (action) { + case "download": + { + const slug = $item.parents(".page").attr("id"); + internals.download(`${slug}.png`, item.png); + } + break; + case "zoom": + { + const shouldOpenStandalone = !!event.shiftKey; + const target = shouldOpenStandalone ? "_blank" : "wmap"; + const dialog = window.open( + "/plugins/wmap/dialog/#", + target, + "popup,height=600,width=800", + ); + if (dialog.location.pathname !== "/plugins/wmap/dialog/") { + dialog.addEventListener("load", () => { + internals.replaceImage(dialog, item.png); + }); + } else { + internals.replaceImage(dialog, item.png); + } + } + break; + } + }); + + try { + let imageData = await internals.renderImage(item.text); + item.png = imageData; + $item.find(".viewer").html(` +
+ + `); + } catch (err) { + console.log("Failed to parse wardley map: ", err); + $item.html(internals.message(err.message)); + } +}; + +/** + * On load, attempt to add the emit and bind functions to window, as these + * are called by the wiki plugin manager. + */ +if (typeof window !== "undefined" && window !== null) { + if (!window.plugins.wmap) { + window.plugins.wmap = { emit, bind }; + } +} + +const expand = function (text) { + return DOMPurify.sanitize(text); +}; + +export const wmap = typeof window === "undefined" ? { expand } : undefined; -- cgit