import { parse } from "wmap-parser";
import { renderToSVG, StageType } from "wmap-renderer-svg";
import DOMPurify from "isomorphic-dompurify";
const internals = {
kStyleSheet: "/plugins/wmap/wmap.css",
kDimensions: {
full: {
map: {
mapWidth: 1300,
mapHeight: 1000,
padding: 42,
},
fonts: {},
},
thumbnail: {
map: {
mapWidth: 403,
mapHeight: 310,
padding: 15,
vertexWidth: 8,
vertexHeight: 8,
arrowHeadSize: 3,
stageHeight: 50,
},
fonts: {
axisLabel: 8,
vertexLabel: 9,
note: 9,
},
},
},
// Returns HTML that shows a message in the wiki.
message(text) {
return `
`;
},
// Creates a wmap PNG from text.
async renderImage(source, dimensions) {
const parsedText = parse(source);
// 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.
return await renderToSVG(parsedText, StageType.ACTIVITIES, {
theme: {
sizes: {
...dimensions.map,
},
fonts: {
...dimensions.fonts,
},
},
});
},
// Starts a download of the image.
download(filename, text) {
const svgWithNamespace = text.replace(
"
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}.svg`, 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(`
${thumbnailData}
`);
$item.find('svg text:gt(7)').on('click', function(event) {
const title = this.innerHTML;
let $page = $item.parents('.page')
wiki.pageHandler.context = wiki.lineup.atKey($page.data('key')).getContext()
wiki.doInternalLink(title, event.shiftKey ? null : $page)
});
} 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;