Updated the click_security_loose test case for classDiagram and classDiagram-v2 for new callback function arg

This commit is contained in:
Ashish Jain 2020-12-17 20:26:12 +01:00
parent ddea5b57a5
commit 032d8781dc
7 changed files with 278 additions and 34 deletions

View File

@ -35,7 +35,7 @@
+withdrawl(amount) int
}
cssClass "BankAccount" customCss
</div>
<div class="mermaid" style="width: 100%; height: 20%;">
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
@ -47,7 +47,7 @@
+withdrawl(amount) int
}
cssClass "BankAccount" customCss
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
@ -69,8 +69,8 @@
int id
test()
}
callback Class01 "callback" "A Tooltip"
callback Class01 "callback" "A Tooltip"
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart TB
@ -79,7 +79,7 @@
classDef apa fill:#f9f,stroke:#333,stroke-width:4px;
class a_a apa;
click a_a "http://www.aftonbladet.se" "apa"
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
@ -110,7 +110,7 @@
class Shape
callback Shape "callbackFunction" "This is a tooltip for a callback"
</div>
<script src="./mermaid.js"></script>
<script>

View File

@ -108,8 +108,25 @@
click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div"
click URL "http://localhost:9000/webpackUsage.html" "Visit <strong>mermaid docs</strong>"
</div>
</div>
<div id="FirstLine" class="mermaid">
classDiagram
class ShapeLink
link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
class ShapeCallback
click ShapeCallback call clickByClass(123) "This is a tooltip for a callback"
</div>
<div id="FirstLine" class="mermaid">
classDiagram-v2
class ShapeLink2
link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
class ShapeCallback2
click ShapeCallback2 call clickByClass(123) "This is a tooltip for a callback"
</div>
</div>
<script src="./mermaid.js"></script>
<script>
function clickByFlow(elemName) {
@ -139,11 +156,11 @@
document.getElementsByTagName('body')[0].appendChild(div)
}
function clickByClass() {
function clickByClass(arg) {
const div = document.createElement('div')
div.className = 'created-by-class-click'
div.style = 'padding: 20px; background: purple; color: white;'
div.innerText = 'Clicked By Class'
div.innerText = 'Clicked By Class' + (arg?arg:'')
document.getElementsByTagName('body')[0].appendChild(div)
}

View File

@ -451,12 +451,14 @@ You would define these actions on a separate line after all classes have been de
```
action className "reference" "tooltip"
click className call callback() "tooltip"
click className href "url" "tooltip"
```
- _action_ is either `link` or `callback`, depending on which type of interaction you want to have called
- _className_ is the id of the node that the action will be associated with
- _reference_ is either the url link, or the function name for callback. (note: callback function will be called with the nodeId as parameter).
- _reference_ is either the url link, or the function name for callback.
- (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
- note: callback function will be called with the nodeId as parameter.
### Examples
@ -466,6 +468,8 @@ _URL Link:_
classDiagram
class Shape
link Shape "http://www.github.com" "This is a tooltip for a link"
class Shape2
click Shape2 href "http://www.github.com" "This is a tooltip for a link"
```
_Callback:_
@ -474,6 +478,8 @@ _Callback:_
classDiagram
class Shape
callback Shape "callbackFunction" "This is a tooltip for a callback"
class Shape2
click Shape2 call callbackFunction() "This is a tooltip for a callback"
```
```
@ -490,6 +496,10 @@ classDiagram
class Class02
callback Class01 "callbackFunction" "Callback tooltip"
link Class02 "http://www.github.com" "This is a link"
class Class03
class Class04
click Class03 call callbackFunction() "Callback tooltip"
click Class04 href "http://www.github.com" "This is a link"
```
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
@ -522,7 +532,7 @@ Beginners tip, a full example using interactive links in an html context:
}
callback Duck callback "Tooltip"
click Zebra "http://www.github.com" "This is a link"
link Zebra "http://www.github.com" "This is a link"
</div>
<script>

View File

@ -167,22 +167,37 @@ export const setCssClass = function(ids, className) {
});
};
/**
* Called by parser when a tooltip is found, e.g. a clickable element.
* @param ids Comma separated list of ids
* @param tooltip Tooltip to add
*/
const setTooltip = function(ids, tooltip) {
const config = configApi.getConfig();
ids.split(',').forEach(function(id) {
if (typeof tooltip !== 'undefined') {
classes[id].tooltip = common.sanitizeText(tooltip, config);
}
});
};
/**
* Called by parser when a link is found. Adds the URL to the vertex data.
* @param ids Comma separated list of ids
* @param linkStr URL to create a link for
* @param tooltip Tooltip for the clickable element
* @param target Target of the link, _blank by default as originally defined in the svgDraw.js file
*/
export const setLink = function(ids, linkStr, tooltip) {
export const setLink = function(ids, linkStr, target) {
const config = configApi.getConfig();
ids.split(',').forEach(function(_id) {
let id = _id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (typeof classes[id] !== 'undefined') {
classes[id].link = utils.formatUrl(linkStr, config);
if (tooltip) {
classes[id].tooltip = common.sanitizeText(tooltip, config);
if (typeof target === 'string') {
classes[id].linkTarget = target;
} else {
classes[id].linkTarget = '_blank';
}
}
});
@ -193,17 +208,17 @@ export const setLink = function(ids, linkStr, tooltip) {
* Called by parser when a click definition is found. Registers an event handler.
* @param ids Comma separated list of ids
* @param functionName Function to be called on click
* @param tooltip Tooltip for the clickable element
* @param functionArgs Function args the function should be called with
*/
export const setClickEvent = function(ids, functionName, tooltip) {
export const setClickEvent = function(ids, functionName, functionArgs) {
ids.split(',').forEach(function(id) {
setClickFunc(id, functionName, tooltip);
setClickFunc(id, functionName, functionArgs);
classes[id].haveCallback = true;
});
setCssClass(ids, 'clickable');
};
const setClickFunc = function(domId, functionName, tooltip) {
const setClickFunc = function(domId, functionName, functionArgs) {
const config = configApi.getConfig();
let id = domId;
let elemId = lookUpDomId(id);
@ -215,8 +230,24 @@ const setClickFunc = function(domId, functionName, tooltip) {
return;
}
if (typeof classes[id] !== 'undefined') {
if (tooltip) {
classes[id].tooltip = common.sanitizeText(tooltip, config);
let argList = [];
if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
for (let i = 0; i < argList.length; i++) {
let item = argList[i].trim();
/* Removes all double quotes at the start and end of an argument */
/* This preserves all starting and ending whitespace inside */
if (item.charAt(0) === '"' && item.charAt(item.length - 1) === '"') {
item = item.substr(1, item.length - 2);
}
argList[i] = item;
}
}
/* if no arguments passed into callback, default to passing in id */
if (argList.length === 0) {
argList.push(elemId);
}
funs.push(function() {
@ -225,7 +256,7 @@ const setClickFunc = function(domId, functionName, tooltip) {
elem.addEventListener(
'click',
function() {
utils.runFunc(functionName, elemId);
utils.runFunc(functionName, ...argList);
},
false
);
@ -314,5 +345,6 @@ export default {
setClickEvent,
setCssClass,
setLink,
setTooltip,
lookUpDomId
};

View File

@ -343,6 +343,21 @@ foo()
parser.parse(str);
});
it('should handle click statement with click and href link', function () {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'%% Comment Class01 <|-- Class02\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}\n' +
'click Class01 href "google.com" ';
parser.parse(str);
});
it('should handle click statement with link and tooltip', function () {
const str =
'classDiagram\n' +
@ -358,6 +373,22 @@ foo()
parser.parse(str);
});
it('should handle click statement with click and href link and tooltip', function () {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'%% Comment Class01 <|-- Class02\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}\n' +
'click Class01 href "google.com" "A Tooltip" ';
parser.parse(str);
});
it('should handle click statement with callback', function () {
const str =
'classDiagram\n' +
@ -373,6 +404,21 @@ foo()
parser.parse(str);
});
it('should handle click statement with click and call callback', function () {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'%% Comment Class01 <|-- Class02\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}\n' +
'click Class01 call functionCall() ';
parser.parse(str);
});
it('should handle click statement with callback and tooltip', function () {
const str =
'classDiagram\n' +
@ -388,6 +434,21 @@ foo()
parser.parse(str);
});
it('should handle click statement with click and call callback and tooltip', function () {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'%% Comment Class01 <|-- Class02\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}\n' +
'click Class01 call functionCall() "A Tooltip" ';
parser.parse(str);
});
it('should handle dashed relation definition of different types and directions', function () {
const str =
'classDiagram\n' +
@ -637,7 +698,17 @@ foo()
});
it('should associate link and css appropriately', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com"';
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com"';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.link).toBe('about:blank');//('google.com'); security needs to be set to 'loose' for this to work right
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
});
it('should associate click and href link and css appropriately', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 href "google.com"';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
@ -657,20 +728,86 @@ foo()
expect(testClass.cssClasses[0]).toBe('clickable');
});
it('should associate click and href link with tooltip', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 href "google.com" "A tooltip"';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.link).toBe('about:blank');//('google.com'); security needs to be set to 'loose' for this to work right
expect(testClass.tooltip).toBe('A tooltip');
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
});
it('should associate click and href link with tooltip and target appropriately', function () {
spyOn(classDb, 'setLink');
spyOn(classDb, 'setTooltip');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 href "google.com" "A tooltip" _self';
parser.parse(str);
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
});
it('should associate click and href link appropriately', function () {
spyOn(classDb, 'setLink');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 href "google.com"';
parser.parse(str);
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com');
});
it('should associate click and href link with target appropriately', function () {
spyOn(classDb, 'setLink');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 href "google.com" _self';
parser.parse(str);
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
});
it('should associate link appropriately', function () {
spyOn(classDb, 'setLink');
spyOn(classDb, 'setTooltip');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com" "A tooltip" _self';
parser.parse(str);
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
});
it('should associate callback appropriately', function () {
spyOn(classDb, 'setClickEvent');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'callback Class1 "functionCall"';
parser.parse(str);
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall', undefined);
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
});
it('should associate click and call callback appropriately', function () {
spyOn(classDb, 'setClickEvent');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 call functionCall()';
parser.parse(str);
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
});
it('should associate callback appropriately with an arbitrary number of args', function () {
spyOn(classDb, 'setClickEvent');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 call functionCall("test0", test1, test2)';
parser.parse(str);
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall','"test0", test1, test2');
});
it('should associate callback with tooltip', function () {
spyOn(classDb, 'setClickEvent');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'callback Class1 "functionCall" "A tooltip"';
spyOn(classDb, 'setTooltip');
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'click Class1 call functionCall() "A tooltip"';
parser.parse(str);
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall', 'A tooltip');
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
});
});
});

View File

@ -6,7 +6,15 @@
/* lexical grammar */
%lex
%x string generic struct open_directive type_directive arg_directive
%x string
%x generic
%x struct
%x href
%x callback_name
%x callback_args
%x open_directive
%x type_directive
%x arg_directive
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
@ -31,6 +39,7 @@
"cssClass" return 'CSSCLASS';
"callback" return 'CALLBACK';
"link" return 'LINK';
"click" return 'CLICK';
"<<" return 'ANNOTATION_START';
">>" return 'ANNOTATION_END';
[~] this.begin("generic");
@ -40,6 +49,35 @@
<string>["] this.popState();
<string>[^"]* return "STR";
/*
---interactivity command---
'href' adds a link to the specified node. 'href' can only be specified when the
line was introduced with 'click'.
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
*/
"href"[\s]+["] this.begin("href");
<href>["] this.popState();
<href>[^"]* return 'HREF';
/*
---interactivity command---
'call' adds a callback to the specified node. 'call' can only be specified when
the line was introduced with 'click'.
'call <callback_name>(<callback_args>)' attaches the function 'callback_name' with the specified
arguments to the node that was specified by 'click'.
Function arguments are optional: 'call <callback_name>()' simply executes 'callback_name' without any arguments.
*/
"call"[\s]+ this.begin("callback_name");
<callback_name>\([\s]*\) this.popState();
<callback_name>\( this.popState(); this.begin("callback_args");
<callback_name>[^(]* return 'CALLBACK_NAME';
<callback_args>\) this.popState();
<callback_args>[^)]* return 'CALLBACK_ARGS';
"_self" return 'LINK_TARGET';
"_blank" return 'LINK_TARGET';
"_parent" return 'LINK_TARGET';
"_top" return 'LINK_TARGET';
\s*\<\| return 'EXTENSION';
\s*\|\> return 'EXTENSION';
@ -243,10 +281,20 @@ lineType
;
clickStatement
: CALLBACK className STR {$$ = $1;yy.setClickEvent($2, $3, undefined);}
| CALLBACK className STR STR {$$ = $1;yy.setClickEvent($2, $3, $4);}
| LINK className STR {$$ = $1;yy.setLink($2, $3, undefined);}
| LINK className STR STR {$$ = $1;yy.setLink($2, $3, $4);}
: CALLBACK className STR {$$ = $1;yy.setClickEvent($2, $3);}
| CALLBACK className STR STR {$$ = $1;yy.setClickEvent($2, $3);yy.setTooltip($2, $4);}
| LINK className STR {$$ = $1;yy.setLink($2, $3);}
| LINK className STR LINK_TARGET {$$ = $1;yy.setLink($2, $3,$4);}
| LINK className STR STR {$$ = $1;yy.setLink($2, $3);yy.setTooltip($2, $4);}
| LINK className STR STR LINK_TARGET {$$ = $1;yy.setLink($2, $3, $5);yy.setTooltip($2, $4);}
| CLICK className CALLBACK_NAME {$$ = $1;yy.setClickEvent($2, $3);}
| CLICK className CALLBACK_NAME STR {$$ = $1;yy.setClickEvent($2, $3);yy.setTooltip($2, $4);}
| CLICK className CALLBACK_NAME CALLBACK_ARGS {$$ = $1;yy.setClickEvent($2, $3, $4);}
| CLICK className CALLBACK_NAME CALLBACK_ARGS STR {$$ = $1;yy.setClickEvent($2, $3, $4);yy.setTooltip($2, $5);}
| CLICK className HREF {$$ = $1;yy.setLink($2, $3);}
| CLICK className HREF LINK_TARGET {$$ = $1;yy.setLink($2, $3, $4);}
| CLICK className HREF STR {$$ = $1;yy.setLink($2, $3);yy.setTooltip($2, $4);}
| CLICK className HREF STR LINK_TARGET {$$ = $1;yy.setLink($2, $3, $5);yy.setTooltip($2, $4);}
;
cssClassStatement

View File

@ -167,7 +167,7 @@ export const drawClass = function(elem, classDef, conf) {
title = g
.append('svg:a')
.attr('xlink:href', classDef.link)
.attr('target', '_blank')
.attr('target', classDef.linkTarget)
.append('text')
.attr('y', conf.textHeight + conf.padding)
.attr('x', 0);