1064 Adding tooltip and function calls to click evens in class diagrams

This commit is contained in:
Justin Greywolf
2020-01-02 11:04:37 -08:00
parent 00687f21cd
commit e6fbfcb1e8
6 changed files with 76 additions and 46 deletions

View File

@@ -1,9 +1,9 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { logger } from '../../logger'; import { logger } from '../../logger';
import { getConfig } from '../../config'; import { getConfig } from '../../config';
import utils from '../../utils';
const MERMAID_DOM_ID_PREFIX = ''; const MERMAID_DOM_ID_PREFIX = 'classid-';
const config = getConfig(); const config = getConfig();
@@ -155,14 +155,10 @@ export const setLink = function(ids, linkStr, tooltip) {
let id = _id; let id = _id;
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') {
if (config.securityLevel !== 'loose') { classes[id].link = utils.formatUrl(linkStr, config);
classes[id].link = sanitizeUrl(linkStr);
} else {
classes[id].link = linkStr;
}
if (tooltip) { if (tooltip) {
classes[id].tooltip = tooltip; classes[id].tooltip = utils.sanitize(tooltip, config);
} }
} }
}); });
@@ -182,9 +178,10 @@ export const setClickEvent = function(ids, functionName, tooltip) {
setCssClass(ids, 'clickable'); setCssClass(ids, 'clickable');
}; };
const setClickFunc = function(_id, functionName) { const setClickFunc = function(_id, functionName, tooltip) {
let id = _id; let id = _id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; let elemId = MERMAID_DOM_ID_PREFIX + id;
if (config.securityLevel !== 'loose') { if (config.securityLevel !== 'loose') {
return; return;
} }
@@ -192,14 +189,18 @@ const setClickFunc = function(_id, functionName) {
return; return;
} }
if (typeof classes[id] !== 'undefined') { if (typeof classes[id] !== 'undefined') {
if (tooltip) {
classes[id].tooltip = utils.sanitize(tooltip, config);
}
funs.push(function() { funs.push(function() {
const elem = document.querySelector(`[id="${id}"]`); const elem = document.querySelector(`[id="${elemId}"]`);
if (elem !== null) { if (elem !== null) {
elem.setAttribute('title', classes[id].tooltip);
elem.addEventListener( elem.addEventListener(
'click', 'click',
function() { function() {
window[functionName](id); window[functionName](elemId);
}, },
false false
); );

View File

@@ -8,6 +8,7 @@ import { parser } from './parser/classDiagram';
parser.yy = classDb; parser.yy = classDb;
const MERMAID_DOM_ID_PREFIX = 'classid-';
let idCache = {}; let idCache = {};
let classCnt = 0; let classCnt = 0;
@@ -319,7 +320,7 @@ const drawClass = function(elem, classDef) {
} }
}; };
const id = 'classId' + classCnt; const id = MERMAID_DOM_ID_PREFIX + classDef.id;
const classInfo = { const classInfo = {
id: id, id: id,
label: classDef.id, label: classDef.id,
@@ -339,8 +340,7 @@ const drawClass = function(elem, classDef) {
title = g title = g
.append('svg:a') .append('svg:a')
.attr('xlink:href', classDef.link) .attr('xlink:href', classDef.link)
.attr('xlink:target', '_blank') .attr('target', '_blank')
.attr('xlink:title', classDef.tooltip)
.append('text') .append('text')
.attr('y', conf.textHeight + conf.padding) .attr('y', conf.textHeight + conf.padding)
.attr('x', 0); .attr('x', 0);
@@ -432,6 +432,12 @@ const drawClass = function(elem, classDef) {
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2); x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
}); });
if (classDef.tooltip) {
const tooltip = title
.insert('title')
.text(classDef.tooltip);
}
membersLine.attr('x2', rectWidth); membersLine.attr('x2', rectWidth);
methodsLine.attr('x2', rectWidth); methodsLine.attr('x2', rectWidth);

View File

@@ -1,5 +1,4 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { logger } from '../../logger'; import { logger } from '../../logger';
import utils from '../../utils'; import utils from '../../utils';
import { getConfig } from '../../config'; import { getConfig } from '../../config';
@@ -20,25 +19,6 @@ let direction;
// Functions to be run after graph rendering // Functions to be run after graph rendering
let funs = []; let funs = [];
const sanitize = text => {
let txt = text;
let htmlLabels = true;
if (
config.flowchart &&
(config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false')
)
htmlLabels = false;
if (config.securityLevel !== 'loose' && htmlLabels) { // eslint-disable-line
txt = txt.replace(/<br>/g, '#br#');
txt = txt.replace(/<br\S*?\/>/g, '#br#');
txt = txt.replace(/</g, '&lt;').replace(/>/g, '&gt;');
txt = txt.replace(/=/g, '&equals;');
txt = txt.replace(/#br#/g, '<br/>');
}
return txt;
};
/** /**
* Function called by parser when a node definition has been found * Function called by parser when a node definition has been found
* @param id * @param id
@@ -63,7 +43,7 @@ export const addVertex = function(_id, text, type, style, classes) {
vertices[id] = { id: id, styles: [], classes: [] }; vertices[id] = { id: id, styles: [], classes: [] };
} }
if (typeof text !== 'undefined') { if (typeof text !== 'undefined') {
txt = sanitize(text.trim()); txt = utils.sanitize(text.trim(), config);
// strip quotes if string starts and ends with a quote // strip quotes if string starts and ends with a quote
if (txt[0] === '"' && txt[txt.length - 1] === '"') { if (txt[0] === '"' && txt[txt.length - 1] === '"') {
@@ -113,7 +93,7 @@ export const addSingleLink = function(_start, _end, type, linktext) {
linktext = type.text; linktext = type.text;
if (typeof linktext !== 'undefined') { if (typeof linktext !== 'undefined') {
edge.text = sanitize(linktext.trim()); edge.text = utils.sanitize(linktext.trim(), config);
// strip quotes if string starts and exnds with a quote // strip quotes if string starts and exnds with a quote
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') { if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
@@ -225,7 +205,7 @@ export const setClass = function(ids, className) {
const setTooltip = function(ids, tooltip) { const setTooltip = function(ids, tooltip) {
ids.split(',').forEach(function(id) { ids.split(',').forEach(function(id) {
if (typeof tooltip !== 'undefined') { if (typeof tooltip !== 'undefined') {
tooltips[id] = sanitize(tooltip); tooltips[id] = utils.sanitize(tooltip, config);
} }
}); });
}; };
@@ -266,11 +246,7 @@ export const setLink = function(ids, linkStr, tooltip) {
let id = _id; let id = _id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (typeof vertices[id] !== 'undefined') { if (typeof vertices[id] !== 'undefined') {
if (config.securityLevel !== 'loose') { vertices[id].link = utils.formatUrl(linkStr, config);
vertices[id].link = sanitizeUrl(linkStr); // .replace(/javascript:.*/g, '')
} else {
vertices[id].link = linkStr;
}
} }
}); });
setTooltip(ids, tooltip); setTooltip(ids, tooltip);
@@ -429,7 +405,7 @@ export const addSubGraph = function(_id, list, _title) {
id = id || 'subGraph' + subCount; id = id || 'subGraph' + subCount;
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
title = title || ''; title = title || '';
title = sanitize(title); title = utils.sanitize(title, config);
subCount = subCount + 1; subCount = subCount + 1;
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }; const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
subGraphs.push(subGraph); subGraphs.push(subGraph);

View File

@@ -613,6 +613,9 @@ const render = function(id, txt, cb, container) {
case 'gantt': case 'gantt':
cb(svgCode, ganttDb.bindFunctions); cb(svgCode, ganttDb.bindFunctions);
break; break;
case 'class':
cb(svgCode, classDb.bindFunctions);
break;
default: default:
cb(svgCode); cb(svgCode);
} }

View File

@@ -10,6 +10,10 @@ g.classGroup text {
} }
} }
g.clickable {
cursor: pointer;
}
g.classGroup rect { g.classGroup rect {
fill: $nodeBkg; fill: $nodeBkg;
stroke: $nodeBorder; stroke: $nodeBorder;

View File

@@ -1,5 +1,6 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { logger } from './logger'; import { logger } from './logger';
import { sanitizeUrl } from '@braintree/sanitize-url';
/** /**
* @function detectType * @function detectType
@@ -73,6 +74,43 @@ export const interpolateToCurve = (interpolate, defaultCurve) => {
return d3[curveName] || defaultCurve; return d3[curveName] || defaultCurve;
}; };
export const sanitize = (text, config) => {
let txt = text;
let htmlLabels = true;
if (
config.flowchart &&
(config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false')
)
htmlLabels = false;
if (config.securityLevel !== 'loose' && htmlLabels) { // eslint-disable-line
txt = txt.replace(/<br>/g, '#br#');
txt = txt.replace(/<br\S*?\/>/g, '#br#');
txt = txt.replace(/</g, '&lt;').replace(/>/g, '&gt;');
txt = txt.replace(/=/g, '&equals;');
txt = txt.replace(/#br#/g, '<br/>');
}
return txt;
};
export const formatUrl = (linkStr, config) => {
let url = linkStr;
if (config.securityLevel !== 'loose') {
return sanitizeUrl(url);
} else {
url = url.trim();
if (!!url) {
if (!/^(https?:)?\/\//i.test(url)) {
url = 'http://' + url;
}
}
return url;
}
}
const distance = (p1, p2) => const distance = (p1, p2) =>
p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0; p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
@@ -174,5 +212,7 @@ export default {
isSubstringInArray, isSubstringInArray,
interpolateToCurve, interpolateToCurve,
calcLabelPosition, calcLabelPosition,
calcCardinalityPosition calcCardinalityPosition,
sanitize,
formatUrl
}; };