aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/wmap.js157
1 files changed, 157 insertions, 0 deletions
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 `
+ <div class="viewer" data-item="viewer" style="width:98%">
+ <div style="width:80%; padding:8px; color:gray; background-color:#eee; margin:0 auto; text-align:center">
+ <i>${text}</i>
+ </div>
+ </div>`;
+ },
+
+ // 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(`
+ <article class="wardley-map"><img src="${imageData}"</article>
+ <nav class="actions">
+ <menu>
+ <li><a href="#" data-action="download" title="Download"><img width="18" height="18" alt="download" src='data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="grey"><g><rect fill="none" height="24" width="24"/></g><g><path d="M5,20h14v-2H5V20z M19,9h-4V3H9v6H5l7,7L19,9z"/></g></svg>'></a></li>
+ <li><a href="#" data-action="zoom" title="Zoom"><img width="18" height="18" alt="toggle zoom" src='data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path fill="grey" d="M15,3l2.3,2.3l-2.89,2.87l1.42,1.42L18.7,6.7L21,9V3H15z M3,9l2.3-2.3l2.87,2.89l1.42-1.42L6.7,5.3L9,3H3V9z M9,21 l-2.3-2.3l2.89-2.87l-1.42-1.42L5.3,17.3L3,15v6H9z M21,15l-2.3,2.3l-2.87-2.89l-1.42,1.42l2.89,2.87L15,21h6V15z"/></g></g></g></svg>'></a></li>
+ </menu>
+ </nav>
+ `);
+ } 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;