`;
},
// Creates a wmap PNG from text.
async renderImage(source, dimensions) {
const parsedText = parse(source);
const canvas = document.createElement("canvas");
canvas.width = dimensions.map.mapWidth + 2 * dimensions.map.padding;
canvas.height = dimensions.map.mapHeight + 2 * dimensions.map.padding;
// Further Improvements. The zoomed version could have a drop down
// to show the different StageTypes and allow one to see the image
// with the different types.
// This could also potentially be in the toolbar that appears
// on hover to also show this in the download. It requires some
// thought on what the right interaction model would be.
await renderToCanvas(parsedText, canvas, StageType.ACTIVITIES, {
theme: {
sizes: {
...dimensions.map,
},
fonts: {
...dimensions.fonts,
},
},
});
return canvas.toDataURL();
},
// Starts a download of the image.
download(filename, text) {
const 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) {
try {
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);
} catch (error) {
console.error(
"wmap: Could not replace image. The DOM wasn't ready!",
error,
);
}
},
};
/**
* 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 the 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", async (event) => {
const { currentTarget } = event;
const action = currentTarget.dataset?.action;
if (!action) {
return;
}
event.stopPropagation();
event.preventDefault();
switch (action) {
case "download":
{
let imageData = await internals.renderImage(
item.text,
internals.kDimensions.full,
);
const slug = $item.parents(".page").attr("id");
internals.download(`${slug}.png`, imageData);
}
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 || dialog.closed) {
console.error("wmap: Failed to open dialog.");
return;
}
let imageData = await internals.renderImage(
item.text,
internals.kDimensions.full,
);
// We *MUST* check both readyState and href, because
// readyState will be complete when it first loads,
// and this will be an empty window.
if (
dialog.document.readyState === "complete" &&
dialog.document.location.href.includes(
"/plugins/wmap/dialog/",
)
) {
internals.replaceImage(dialog, imageData);
} else {
console.info("We not loaded");
dialog.addEventListener(
"load",
() => {
internals.replaceImage(dialog, imageData);
},
{ once: true },
);
}
}
break;
}
});
try {
let thumbnailData = await internals.renderImage(
item.text,
internals.kDimensions.thumbnail,
);
$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;