feat: add support for links in sanbox mode

This commit is contained in:
Knut Sveidqvist
2022-01-28 14:45:43 +01:00
parent e3e9c67f5b
commit fa55b7c824
8 changed files with 138 additions and 32 deletions

View File

@@ -25,14 +25,13 @@
</style> </style>
</head> </head>
<body> <body>
<div id="apa" style="background: aliceblue; width: 150px; height: 75px;">APA</div>
<div class="mermaid2" style="width: 100%;"> <div class="mermaid2" style="width: 100%;">
pie title Pets adopted by volunteers pie title Pets adopted by volunteers
"Dogs" : 386 "Dogs" : 386
"Cats" : 85 "Cats" : 85
"Rats" : 15 "Rats" : 15
</div> </div>
<div class="mermaid" style="width: 100%;"> <div class="mermaid2" style="width: 100%;">
gantt gantt
title Adding GANTT diagram functionality to mermaid title Adding GANTT diagram functionality to mermaid
excludes :excludes the named dates/days from being included in a charted task.. excludes :excludes the named dates/days from being included in a charted task..
@@ -64,7 +63,7 @@ commit
commit commit
merge newbranch merge newbranch
</div> </div>
<div class="mermaid" style="width: 100%;"> <div class="mermaid2" style="width: 100%;">
sequenceDiagram sequenceDiagram
participant a as Alice participant a as Alice
participant j as John participant j as John
@@ -146,6 +145,76 @@ requirementDiagram
end end
</div> </div>
<div class="mermaid2" style="width: 100%;"> <div class="mermaid2" style="width: 100%;">
flowchart TB
Function-->URL
click Function clickByFlow "Add a div"
click URL "https://mermaid-js.github.io/mermaid/#/" "Visit <strong>mermaid docs</strong>" _blank
</div>
<div class="mermaid2" style="width: 100%;">
classDiagram-v2
class Test
class ShapeLink
link ShapeLink "https://mermaid-js.github.io/mermaid/#/" "This is a tooltip for a link"
class ShapeCallback
callback ShapeCallback "clickByClass" "This is a tooltip for a callback"
</div>
<div class="mermaid" style="width: 100%;">
gantt
dateFormat YYYY-MM-DD
axisFormat %d/%m
title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d
section Documentation
Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Clickable
Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10
Calling a Callback (look at the console log) :cl2, after cl1, 3d
Calling a Callback with args :cl3, after cl1, 3d
click cl1 href "https://mermaid-js.github.io/mermaid/#/"
click cl2 call clickByGantt()
click cl3 call clickByGantt("test1", test2, test3)
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page : 20h
Add another diagram to demo page : 48h
</div>
<div class="mermaid2" style="width: 100%;">
classDiagram
Class01 <|-- AveryLongClass : Cool
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
class Class10 {
int id
size()
}
</div>
<div class="mermaid2" style="width: 100%;">
stateDiagram stateDiagram
[*] --> S1 [*] --> S1
state "Some long name" as S1 state "Some long name" as S1
@@ -181,7 +250,7 @@ requirementDiagram
}; };
mermaid.initialize({ mermaid.initialize({
// theme: 'dark', // theme: 'dark',
theme: 'forest', // theme: 'forest',
// arrowMarkerAbsolute: true, // arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}', // themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
flowchart: { flowchart: {
@@ -192,6 +261,7 @@ requirementDiagram
}, },
class: { class: {
defaultRenderer: 'dagre-d3', defaultRenderer: 'dagre-d3',
htmlLabels: true,
}, },
// gantt: { axisFormat: '%m/%d/%Y' }, // gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorFontFamily: 'courier', actorMargin: 50, showSequenceNumbers: false }, sequence: { actorFontFamily: 'courier', actorMargin: 50, showSequenceNumbers: false },
@@ -212,6 +282,15 @@ requirementDiagram
function callback() { function callback() {
alert('It worked'); alert('It worked');
} }
function clickByFlow(elemName) {
const div = document.createElement('div');
div.className = 'created-by-click';
div.style = 'padding: 20px; background: green; color: white;';
div.innerText = 'Clicked By Flow';
document.getElementsByTagName('body')[0].appendChild(div);
}
</script> </script>
</body> </body>
</html> </html>

View File

@@ -967,10 +967,13 @@ export const insertNode = (elem, node, dir) => {
// Add link when appropriate // Add link when appropriate
if (node.link) { if (node.link) {
newEl = elem let target;
.insert('svg:a') if (getConfig().securityLevel === 'sandbox') {
.attr('xlink:href', node.link) target = '_top';
.attr('target', node.linkTarget || '_blank'); } else if (node.linkTarget) {
target = node.linkTarget || '_blank';
}
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
el = shapes[node.shape](newEl, node, dir); el = shapes[node.shape](newEl, node, dir);
} else { } else {
el = shapes[node.shape](elem, node, dir); el = shapes[node.shape](elem, node, dir);

View File

@@ -213,7 +213,9 @@ export const setLink = function (ids, linkStr, target) {
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (typeof classes[id] !== 'undefined') { if (typeof classes[id] !== 'undefined') {
classes[id].link = utils.formatUrl(linkStr, config); classes[id].link = utils.formatUrl(linkStr, config);
if (typeof target === 'string') { if (config.securityLevel === 'sandbox') {
classes[id].linkTarget = '_top';
} else if (typeof target === 'string') {
classes[id].linkTarget = target; classes[id].linkTarget = target;
} else { } else {
classes[id].linkTarget = '_blank'; classes[id].linkTarget = '_blank';

View File

@@ -496,7 +496,9 @@ export const draw = function (text, id) {
link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' ')); link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link); link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
if (vertex.linkTarget) { if (securityLevel === 'sandbox') {
link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top');
} else if (vertex.linkTarget) {
link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget); link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget);
} }

View File

@@ -503,7 +503,9 @@ export const draw = function (text, id) {
link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' ')); link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link); link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener'); link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
if (vertex.linkTarget) { if (securityLevel === 'sandbox') {
link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top');
} else if (vertex.linkTarget) {
link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget); link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget);
} }

View File

@@ -10,6 +10,7 @@ let axisFormat = '';
let todayMarker = ''; let todayMarker = '';
let includes = []; let includes = [];
let excludes = []; let excludes = [];
let links = {};
let title = ''; let title = '';
let sections = []; let sections = [];
let tasks = []; let tasks = [];
@@ -44,6 +45,7 @@ export const clear = function () {
inclusiveEndDates = false; inclusiveEndDates = false;
topAxis = false; topAxis = false;
lastOrder = 0; lastOrder = 0;
links = {};
}; };
export const setAxisFormat = function (txt) { export const setAxisFormat = function (txt) {
@@ -101,6 +103,10 @@ export const getExcludes = function () {
return excludes; return excludes;
}; };
export const getLinks = function () {
return links;
};
export const setTitle = function (txt) { export const setTitle = function (txt) {
title = txt; title = txt;
}; };
@@ -505,6 +511,7 @@ export const setLink = function (ids, _linkStr) {
pushFun(id, () => { pushFun(id, () => {
window.open(linkStr, '_self'); window.open(linkStr, '_self');
}); });
links[id] = linkStr;
} }
}); });
setClass(ids, 'clickable'); setClass(ids, 'clickable');
@@ -642,6 +649,7 @@ export default {
getExcludes, getExcludes,
setClickEvent, setClickEvent,
setLink, setLink,
getLinks,
bindFunctions, bindFunctions,
durationToDate, durationToDate,
isInvalidDate, isInvalidDate,

View File

@@ -185,6 +185,10 @@ export const draw = function (text, id) {
// Draw the rects representing the tasks // Draw the rects representing the tasks
const rectangles = svg.append('g').selectAll('rect').data(theArray).enter(); const rectangles = svg.append('g').selectAll('rect').data(theArray).enter();
const links = ganttDb.getLinks();
// Render the tasks with links
// Render the other tasks
rectangles rectangles
.append('rect') .append('rect')
.attr('id', function (d) { .attr('id', function (d) {
@@ -381,6 +385,32 @@ export const draw = function (text, id) {
return classStr + ' taskText taskText' + secNum + ' ' + taskType + ' width-' + textWidth; return classStr + ' taskText taskText' + secNum + ' ' + taskType + ' width-' + textWidth;
} }
}); });
const securityLevel = getConfig().securityLevel;
// Wrap the tasks in an a tag for working links without javascript
if (securityLevel === 'sandbox') {
let sandboxElement;
sandboxElement = select('#i' + id);
const root = select(sandboxElement.nodes()[0].contentDocument.body);
const doc = sandboxElement.nodes()[0].contentDocument;
rectangles
.filter(function (d) {
return typeof links[d.id] !== 'undefined';
})
.each(function (o) {
var taskRect = doc.querySelector('#' + o.id);
var taskText = doc.querySelector('#' + o.id + '-text');
const oldParent = taskRect.parentNode;
var Link = doc.createElement('a');
Link.setAttribute('xlink:href', links[o.id]);
Link.setAttribute('target', '_top');
oldParent.appendChild(Link);
Link.appendChild(taskRect);
Link.appendChild(taskText);
});
}
} }
/** /**
* @param theGap * @param theGap

View File

@@ -518,32 +518,12 @@ const render = function (id, _txt, cb, container) {
svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g'); svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g');
} }
// const iframe = document.createElement('iframe');
// iframe.setAttribute('frameBorder', '0');
// iframe.setAttribute('id', id);
// iframe.setAttribute('sanbox', '');
// iframe.setAttribute('src', 'about:blank');
// iframe.contentWindow.document.body = svgCode;
// element.innerHTML = '';
// // element.appendChild(iframe);
// // element.innerHTML = '<iframe sandbox>' + svgCode + '</iframe>';
svgCode = decodeEntities(svgCode); svgCode = decodeEntities(svgCode);
// Fix for when the br tag is used // Fix for when the br tag is used
svgCode = svgCode.replace(/<br>/g, '<br/>'); svgCode = svgCode.replace(/<br>/g, '<br/>');
if (cnf.securityLevel === 'sandbox') { if (cnf.securityLevel === 'sandbox') {
// const newSvgCode =
// '<iframe id="' +
// id +
// '" sandbox src="data:text/html;base64,' +
// btoa(btoa(unescape(encodeURIComponent(svgCode)))) +
// '"></iframe>';
// svgCode = newSvgCode;
// svgCode = `<iframe src="data:text/html;base64,V2VsY29tZSB0byA8Yj5iYXNlNjQuZ3VydTwvYj4h">
// The “iframe” tag is not supported by your browser.
// </iframe>`;
let svgEl = root.select('#d' + id + ' svg').node(); let svgEl = root.select('#d' + id + ' svg').node();
let width = '100%'; let width = '100%';
let height = '100%'; let height = '100%';
@@ -553,7 +533,7 @@ const render = function (id, _txt, cb, container) {
} }
svgCode = `<iframe style="width:${width};height:${height};border:0;margin:0;" src="data:text/html;base64,${btoa( svgCode = `<iframe style="width:${width};height:${height};border:0;margin:0;" src="data:text/html;base64,${btoa(
'<body style="margin:0">' + svgCode + '</body>' '<body style="margin:0">' + svgCode + '</body>'
)}" sandbox> )}" sandbox="allow-top-navigation-by-user-activation allow-popups">
The “iframe” tag is not supported by your browser. The “iframe” tag is not supported by your browser.
</iframe>`; </iframe>`;
} }