diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/wmap.js | 157 |
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; |