Merge pull request #16 from mermaid-js/develop

Merge from parent
This commit is contained in:
Justin Greywolf
2020-01-30 10:20:14 -08:00
committed by GitHub
23 changed files with 718 additions and 118 deletions

7
.gitignore vendored
View File

@@ -12,3 +12,10 @@ yarn-error.log
token
package-lock.json
dist/classTest.html
dist/sequenceTest.html
.vscode/
cypress/platform/current.html

View File

@@ -289,4 +289,20 @@ describe('Class diagram', () => {
);
cy.get('svg');
});
it('12: should render a simple class diagram with generic types', () => {
imgSnapshotTest(
`
classDiagram
class Class10~T~ {
int[] id
List~int~ ids
test(List~int~ ids) List~bool~
testArray() bool[]
}
`,
{}
);
cy.get('svg');
});
});

View File

@@ -512,7 +512,7 @@ describe('Flowchart', () => {
);
});
it('24: Keep node label text (if already defined) when a style is applied', () => {
it('24.1: Keep node label text (if already defined) when a style is applied', () => {
imgSnapshotTest(
`graph LR
A(( )) -->|step 1| B(( ))
@@ -524,4 +524,55 @@ describe('Flowchart', () => {
{ flowchart: { htmlLabels: false } }
);
});
it('24.2: Handle link click events (link, anchor, mailto, other protocol, script)', () => {
imgSnapshotTest(
`graph TB
TITLE["Link Click Events<br>(click the nodes below)"]
A[link test]
B[anchor test]
C[mailto test]
D[other protocol test]
E[script test]
TITLE --> A & B & C & D & E
click A "https://mermaid-js.github.io/mermaid/#/" "link test"
click B "#link-clicked" "anchor test"
click C "mailto:user@user.user" "mailto test"
click D "notes://do-your-thing/id" "other protocol test"
click E "javascript:alert('test')" "script test"
`,
{ securityLevel: 'loose' }
);
});
it('25: Set node text color according to style when html labels are enabled', () => {
imgSnapshotTest(
`graph LR
A[red<br>text] --> B(blue<br>text)
C[/red<br/>text/] --> D{blue<br/>text}
style A color:red;
style B color:blue;
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
style D stroke:#0000ff,fill:#ccccff,color:#0000ff
click B "index.html#link-clicked" "link test"
click D testClick "click test"
`,
{ flowchart: { htmlLabels: true } }
);
});
it('26: Set node text color according to style when html labels are disabled', () => {
imgSnapshotTest(
`graph LR
A[red<br>text] --> B(blue<br>text)
C[/red<br/>text/] --> D{blue<br/>text}
style A color:red;
style B color:blue;
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
style D stroke:#0000ff,fill:#ccccff,color:#0000ff
click B "index.html#link-clicked" "link test"
click D testClick "click test"
`,
{ flowchart: { htmlLabels: false } }
);
});
});

View File

@@ -319,7 +319,7 @@ describe('State diagram', () => {
}
);
});
it('Simplest compone state', () => {
it('Simplest composit state', () => {
imgSnapshotTest(
`
stateDiagram
@@ -332,5 +332,17 @@ describe('State diagram', () => {
}
);
});
it('should handle multiple arrows from one node to another', () => {
imgSnapshotTest(
`
stateDiagram
a --> b: Start
a --> b: Stop
`,
{
logLevel: 0,
}
);
});
});

View File

@@ -5,33 +5,28 @@
rel="stylesheet"
/>
<style>
body {background: black}
body {background: white}
h1 { color: white;}
.arrowheadPath {fill: red;}
.edgePath .path {stroke: red;}
</style>
</head>
<body>
<h1>info below</h1>
<div style="display: flex;width: 100%; height: 100%">
<div class="mermaid" style="width: 100%; height: 100%">
graph TB
A --> B
A ==> C
A .-> D
A === E
A -.- F
D -- Hello --> a
D-- text including R TD space --xb
stateDiagram
NotFound --> NotFound: Status
NotFound --> NotFound: Stop
</div>
</div>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
theme: 'dark',
// theme: 'dark',
// arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 0,

File diff suppressed because one or more lines are too long

30
dist/index.html vendored
View File

@@ -353,6 +353,33 @@ graph TB
linkStyle 1 stroke:greenyellow,stroke-width:2px
style C fill:greenyellow,stroke:green,stroke-width:4px
</div>
<div class="mermaid">
graph TB
TITLE["Link Click Events<br>(click the nodes below)"]
A[link test]
B[anchor test]
C[mailto test]
D[other protocol test]
E[script test]
TITLE --> A & B & C & D & E
click A "https://mermaid-js.github.io/mermaid/#/" "link test"
click B "#link-clicked" "anchor test"
click C "mailto:user@user.user" "mailto test"
click D "notes://do-your-thing/id" "other protocol test"
click E "javascript:alert('test')" "script test"
</div>
<hr/>
<div class="mermaid">
graph LR
A[red<br>text] --> B(blue<br>text)
C[/red<br/>text/] --> D{blue<br/>text}
style A color:red;
style B color:blue;
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
style D stroke:#0000ff,fill:#ccccff,color:#0000ff
click B "index.html#link-clicked" "link test"
click D testClick "click test"
</div>
<hr/>
@@ -587,12 +614,15 @@ class Class10 {
end note
</div>
<h1 id="link-clicked">Anchor for "link-clicked" test</h1>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
theme: 'forest',
// themeCSS: '.node rect { fill: red; }',
logLevel: 3,
securityLevel: 'loose',
flowchart: { curve: 'basis' },
gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50 },

View File

@@ -105,7 +105,7 @@ Naming convention: a class name should be composed of alphanumeric (unicode allo
UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes. To indicate a return type for a method, enclose the type within **square brackets** `[]`
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes.
There are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are :
@@ -115,7 +115,7 @@ There are two ways to define the members of a class, and regardless of whichever
class BankAccount
BankAccount : +String owner
BankAccount : +BigDecimal balance
BankAccount : +deposit(amount) bool
BankAccount : +deposit(amount)
BankAccount : +withdrawal(amount)
```
@@ -124,7 +124,7 @@ There are two ways to define the members of a class, and regardless of whichever
class BankAccount
BankAccount : +String owner
BankAccount : +BigDecimal balance
BankAccount : +deposit(amount) : bool
BankAccount : +deposit(amount)
BankAccount : +withdrawl(amount)
```
@@ -142,11 +142,64 @@ class BankAccount{
class BankAccount{
+String owner
+BigDecimal balance
+deposit(amount) : bool
+withdrawl(amount)
+deposit(amount) bool
+withdrawl(amount) int
}
```
#### Return Type
Optionally you can end the method/function definition with the data type that will be returned (note: there must be a space between the final `)` of the method definition and return type
example:
```
class BankAccount{
+String owner
+BigDecimal balance
+deposit(amount) bool
+withdrawl(amount) int
}
```
```mermaid
classDiagram
class BankAccount{
+String owner
+BigDecimal balance
+deposit(amount) bool
+withdrawl(amount) int
}
```
#### Generic Types
Members can be defined using generic types, such as `List<int>`, for fields, parameters and return types by enclosing the type within `~` (**tilde**). Note: **nested** type declarations (such as `List<List<int>>`) are not currently supported
This can be done as part of either class definition method:
```
classDiagram
class Square~Shape~{
int id
List~int~ position
setPoints(List~int~ points)
getPoints() List~int~
}
Square : -List~string~ messages
Square : +setMessages(List~string~ messages)
Square : +getMessages() List~string~
```
```mermaid
classDiagram
class Square~Shape~{
int id
List~int~ position
setPoints(List~int~ points)
getPoints() List~int~
}
Square : -List~string~ messages
Square : +setMessages(List~string~ messages)
Square : +getMessages() List~string~
```
#### Return Type
Optionally you can end the method/function definition with the data type that will be returned
@@ -157,7 +210,7 @@ To specify the visibility of a class member (i.e. any attribute or method), thes
- `+` Public
- `-` Private
- `#` Protected
- `~` Package
- `~` Package/Internal
>_note_ you can also include additional _classifers_ to a method definition by adding the following notations to the end of the method, i.e.: after the `()`:
> - `*` Abstract e.g.: `someAbstractMethod()*`

View File

@@ -509,13 +509,13 @@ It is possible to apply specific styles such as a thicker border or a different
graph LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#ccf,stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5, 5
```
```mermaid
graph LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#ccf,stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5, 5
```

View File

@@ -275,11 +275,12 @@ mermaidAPI.initialize({
### Parameters
- `id` the id of the element to be rendered
- `txt` the graph definition
- `_txt`
- `cb` callback which is called after rendering is finished with the svg code as inparam.
- `container` selector to element in which a div with the graph temporarily will be inserted. In one is
provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
completed.
- `txt` the graph definition
##

View File

@@ -380,6 +380,7 @@ describe('class diagram, ', function () {
parser.parse(str);
});
it('should handle dashed relation definition of different types and directions', function () {
const str =
'classDiagram\n' +
@@ -390,6 +391,29 @@ describe('class diagram, ', function () {
'Class19 .. Class20';
parser.parse(str);
});
it('should handle generic types in members', function () {
const str =
'classDiagram\n' +
'class Car~T~\n' +
'Car : -List~Wheel~ wheels\n' +
'Car : +setWheels(List~Wheel~ wheels)\n' +
'Car : +getWheels() List~Wheel~';
parser.parse(str);
});
it('should handle generic types in members in class with brackets', function () {
const str =
'classDiagram\n' +
'class Car {\n' +
'List~Wheel~ wheels\n' +
'setWheels(List~Wheel~ wheels)\n' +
'+getWheels() List~Wheel~\n' +
'}';
parser.parse(str);
});
});
describe('when fetching data from a classDiagram graph it', function () {
@@ -614,6 +638,7 @@ describe('class diagram, ', function () {
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
});
it('should associate link with tooltip', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com" "A tooltip"';
parser.parse(str);

View File

@@ -0,0 +1,137 @@
export const addTspan = function(textEl, txt, isFirst, conf) {
let member = parseMember(txt);
const tSpan = textEl
.append('tspan')
.attr('x', conf.padding)
.text(member.displayText);
if (member.cssStyle !== '') {
tSpan.attr('style', member.cssStyle);
}
if (!isFirst) {
tSpan.attr('dy', conf.textHeight);
}
};
export const buildFieldDisplay = function(parsedText) {
let visibility = parsedText[1] ? parsedText[1].trim() : '';
let fieldType = parsedText[2] ? parsedText[2].trim() : '';
let genericType = parsedText[3] ? parseGenericTypes(parsedText[3]) : '';
let fieldName = parsedText[4] ? parsedText[4].trim() : '';
return {
displayText: visibility + fieldType + genericType + ' ' + fieldName,
cssStyle: ''
};
};
export const buildMethodDisplay = function(parsedText) {
let cssStyle = '';
let displayText = parsedText;
let visibility = parsedText[1] ? parsedText[1].trim() : '';
let methodName = parsedText[2] ? parsedText[2].trim() : '';
let parameters = parsedText[3] ? parseGenericTypes(parsedText[3]) : '';
let classifier = parsedText[6] ? parsedText[6].trim() : '';
let returnType = parsedText[7] ? ' : ' + parseGenericTypes(parsedText[7]).trim() : '';
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
cssStyle = parseClassifier(classifier);
let member = {
displayText: displayText,
cssStyle: cssStyle
};
return member;
};
export const buildLegacyDisplay = function(text) {
// if for some reason we dont have any match, use old format to parse text
let memberText = '';
let cssStyle = '';
let returnType = '';
let methodStart = text.indexOf('(');
let methodEnd = text.indexOf(')');
if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) {
let parsedText = text.match(/(\+|-|~|#)?(\w+)/);
let visibility = parsedText[1] ? parsedText[1].trim() : '';
let methodName = parsedText[2];
let parameters = text.substring(methodStart + 1, methodEnd);
let classifier = text.substring(methodEnd, methodEnd + 1);
cssStyle = parseClassifier(classifier);
memberText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
if (methodEnd < memberText.length) {
returnType = text.substring(methodEnd + 2).trim();
if (returnType !== '') {
returnType = ' : ' + parseGenericTypes(returnType);
}
}
} else {
// finally - if all else fails, just send the text back as written (other than parsing for generic types)
memberText = parseGenericTypes(text);
}
let member = {
displayText: memberText + returnType,
cssStyle: cssStyle
};
return member;
};
export const parseGenericTypes = function(text) {
let cleanedText = text;
if (text.indexOf('~') != -1) {
cleanedText = cleanedText.replace('~', '<');
cleanedText = cleanedText.replace('~', '>');
return parseGenericTypes(cleanedText);
} else {
return cleanedText;
}
};
export const parseMember = function(text) {
const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+)$/;
const methodRegEx = /^(\+|-|~|#)?(\w+)\s?\(\s*(\w+(~\w+~|\[\])?\s*(\w+)?)?\s*\)\s?([*|$])?\s?(\w+(~\w+~|\[\])?)?\s*$/;
//const methodRegEx = /(\+|-|~|#)?(\w+)\s?\(\s*(\w+(~\w+~|\[\])?\s*(\w+)?)?\s*\)\s?([*|$])?\s?(\w+(~\w+~|\[\])?)?/;
let fieldMatch = text.match(fieldRegEx);
let methodMatch = text.match(methodRegEx);
if (fieldMatch) {
return buildFieldDisplay(fieldMatch);
} else if (methodMatch) {
return buildMethodDisplay(methodMatch);
} else {
return buildLegacyDisplay(text);
}
};
const parseClassifier = function(classifier) {
switch (classifier) {
case '*':
return 'font-style:italic;';
case '$':
return 'text-decoration:underline;';
default:
return '';
}
};
export default {
addTspan,
buildFieldDisplay,
buildLegacyDisplay,
buildMethodDisplay,
parseGenericTypes,
parseMember
};

View File

@@ -0,0 +1,168 @@
/* eslint-env jasmine */
import memberRenderer from './classMemberRenderer';
describe('class member Renderer, ', function () {
describe('when parsing text to build method display string', function () {
it('should handle simple method declaration', function () {
const str = 'foo()';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle public visibility', function () {
const str = '+foo()';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('+foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle private visibility', function () {
const str = '-foo()';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('-foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle protected visibility', function () {
const str = '#foo()';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('#foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle package/internal visibility', function () {
const str = '~foo()';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('~foo()');
expect(actual.cssStyle).toBe('');
});
it('should ignore unknown character for visibility', function () {
const str = '!foo()';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle abstract classifier', function () {
const str = 'foo()*';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle static classifier', function () {
const str = 'foo()$';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should ignore unknown character for classifier', function () {
const str = 'foo()!';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with parameters', function () {
const str = 'foo(int id)';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo(int id)');
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with single item in parameters', function () {
const str = 'foo(id)';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo(id)');
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with single item in parameters with extra spaces', function () {
const str = ' foo ( id) ';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo(id)');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with return value', function () {
const str = 'foo(id) int';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo(id) : int');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with generic return value', function () {
const str = 'foo(id) List~int~';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo(id) : List<int>');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with generic parameter', function () {
const str = 'foo(List~int~)';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('foo(List<int>)');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with all possible markup', function () {
const str = '+foo ( List~int~ ids )* List~Item~';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('+foo(List<int> ids) : List<Item>');
expect(actual.cssStyle).toBe('font-style:italic;');
});
});
describe('when parsing text for generic types', function () {
it('should handle open and close brackets in correct order', function () {
const str = 'foo(List~Item~)';
let actual = memberRenderer.parseGenericTypes(str);
expect(actual).toBe('foo(List<Item>)');
});
it('should handle open and close brackets in correct order with multiple usages', function () {
const str = 'foo(List~Item~) List~Item~';
let actual = memberRenderer.parseGenericTypes(str);
expect(actual).toBe('foo(List<Item>) List<Item>');
});
});
describe('when parsing text to build field display string', function () {
it('should handle simple field declaration', function () {
const str = 'int[] ids';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('int[] ids');
expect(actual.cssStyle).toBe('');
});
it('should handle field declaration with generic type', function () {
const str = 'List~int~ ids';
let actual = memberRenderer.parseMember(str);
expect(actual.displayText).toBe('List<int> ids');
expect(actual.cssStyle).toBe('');
});
});
});

View File

@@ -5,6 +5,7 @@ import { logger } from '../../logger';
import classDb, { lookUpDomId } from './classDb';
import utils from '../../utils';
import { parser } from './parser/classDiagram';
import memberRenderer from './classMemberRenderer';
parser.yy = classDb;
@@ -287,75 +288,6 @@ const drawClass = function(elem, classDef) {
cssClassStr = cssClassStr + classDef.cssClasses.join(' ');
}
const addTspan = function(textEl, txt, isFirst) {
let isMethod = txt.indexOf(')') > 1;
let displayText = txt;
let cssStyle = '';
if (isMethod) {
let method = buildDisplayTextForMethod(txt);
displayText = method.displayText;
cssStyle = method.cssStyle;
}
const tSpan = textEl
.append('tspan')
.attr('x', conf.padding)
.text(displayText);
if (cssStyle !== '') {
tSpan.attr('style', cssStyle);
}
if (!isFirst) {
tSpan.attr('dy', conf.textHeight);
}
};
const buildDisplayTextForMethod = function(txt) {
let regEx = /(\+|-|~|#)?(\w+)\s?\((\w+(<\w+>|\[\])?\s?(\w+)?)?\)\s?([*|$])?\s?(\w+(<\w+>|\[\])?)?/;
let cssStyle = '';
let displayText = txt;
let methodName = txt;
let classifier = '';
let parsedText = txt.match(regEx);
if (parsedText) {
let visibility = parsedText[1] ? parsedText[1].trim() : '';
methodName = parsedText[2] ? parsedText[2].trim() : '';
let parameters = parsedText[3] ? parsedText[3].trim() : '';
classifier = parsedText[6] ? parsedText[6].trim() : '';
let returnType = parsedText[7] ? ' : ' + parsedText[7].trim() : '';
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
} else {
let methodEnd = displayText.indexOf(')') + 1;
classifier = displayText.substring(methodEnd, methodEnd + 1);
if (classifier !== '' && classifier !== ' ') {
displayText = displayText.replace(classifier, '');
}
}
switch (classifier) {
case '*':
cssStyle = 'font-style:italic;';
break;
case '$':
cssStyle = 'text-decoration:underline;';
break;
}
let method = {
methodname: methodName,
displayText: displayText,
cssStyle: cssStyle
};
return method;
};
const id = classDef.id;
const classInfo = {
id: id,
@@ -426,7 +358,7 @@ const drawClass = function(elem, classDef) {
isFirst = true;
classDef.members.forEach(function(member) {
addTspan(members, member, isFirst);
memberRenderer.addTspan(members, member, isFirst, conf);
isFirst = false;
});
@@ -448,7 +380,7 @@ const drawClass = function(elem, classDef) {
isFirst = true;
classDef.methods.forEach(function(method) {
addTspan(methods, method, isFirst);
memberRenderer.addTspan(methods, method, isFirst, conf);
isFirst = false;
});
@@ -570,8 +502,8 @@ export const draw = function(text, id) {
}
});
diagram.attr('height', '100%');
diagram.attr('width', `${g.graph().width * 1.5 + 20}`);
diagram.attr('height', g.graph().height + 40);
diagram.attr('width', g.graph().width * 1.5 + 20);
diagram.attr('viewBox', '-10 -10 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20));
};

View File

@@ -87,6 +87,7 @@ export const addVertices = function(vert, g, svgId) {
vertexNode.parentNode.removeChild(vertexNode);
} else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', labelStyle.replace('color:', 'fill:'));
const rows = vertexText.split(/<br\s*\/?>/gi);

File diff suppressed because one or more lines are too long

View File

@@ -48,7 +48,7 @@
<STATE>.*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>["] this.begin("STATE_STRING");
<STATE>"as"\s* {this.popState();this.pushState('STATE_ID');return "AS";}
<STATE>\s*"as"\s+ {this.popState();this.pushState('STATE_ID');return "AS";}
<STATE_ID>[^\n\{]* {this.popState();/* console.log('STATE_ID', yytext);*/return "ID";}
<STATE_STRING>["] this.popState();
<STATE_STRING>[^"]* { /*console.log('Long description:', yytext);*/return "STATE_DESCR";}

View File

@@ -53,6 +53,39 @@ describe('state diagram, ', function() {
parser.parse(str);
});
it('handle "as" in state names', function() {
const str = `stateDiagram
assemble
state assemble
`;
parser.parse(str);
});
it('handle "as" in state names 1', function() {
const str = `stateDiagram
assemble
state assemble
`;
parser.parse(str);
});
it('handle "as" in state names 2', function() {
const str = `stateDiagram
assembleas
state assembleas
`;
parser.parse(str);
});
it('handle "as" in state names 3', function() {
const str = `stateDiagram
state "as" as as
`;
parser.parse(str);
});
it('scale', function() {
const str = `stateDiagram\n
scale 350 width

View File

@@ -53,7 +53,7 @@ export const draw = function(text, id) {
// Layout graph, Create a new directed graph
const graph = new graphlib.Graph({
multigraph: false,
multigraph: true,
compound: true,
// acyclicer: 'greedy',
rankdir: 'RL'
@@ -110,7 +110,8 @@ const getRows = s => {
const renderDoc = (doc, diagram, parentId, altBkg) => {
// // Layout graph, Create a new directed graph
const graph = new graphlib.Graph({
compound: true
compound: true,
multigraph: true
});
let i;
@@ -126,28 +127,29 @@ const renderDoc = (doc, diagram, parentId, altBkg) => {
if (parentId)
graph.setGraph({
rankdir: 'LR',
// multigraph: false,
multigraph: true,
compound: true,
// acyclicer: 'greedy',
ranker: 'tight-tree',
ranksep: edgeFreeDoc ? 1 : conf.edgeLengthFactor,
nodeSep: edgeFreeDoc ? 1 : 50
// isMultiGraph: false
nodeSep: edgeFreeDoc ? 1 : 50,
isMultiGraph: true
// ranksep: 5,
// nodesep: 1
});
else {
graph.setGraph({
rankdir: 'TB',
multigraph: true,
compound: true,
// isCompound: true,
// acyclicer: 'greedy',
// ranker: 'longest-path'
ranksep: edgeFreeDoc ? 1 : conf.edgeLengthFactor,
nodeSep: edgeFreeDoc ? 1 : 50,
ranker: 'tight-tree'
ranker: 'tight-tree',
// ranker: 'network-simplex'
// isMultiGraph: false
isMultiGraph: true
});
}
@@ -226,14 +228,22 @@ const renderDoc = (doc, diagram, parentId, altBkg) => {
}
}
logger.info('Count=', graph.nodeCount());
logger.debug('Count=', graph.nodeCount(), graph);
let cnt = 0;
relations.forEach(function(relation) {
graph.setEdge(relation.id1, relation.id2, {
relation: relation,
width: getLabelWidth(relation.title),
height: conf.labelHeight * getRows(relation.title).length,
labelpos: 'c'
});
cnt++;
logger.debug('Setting edge', relation);
graph.setEdge(
relation.id1,
relation.id2,
{
relation: relation,
width: getLabelWidth(relation.title),
height: conf.labelHeight * getRows(relation.title).length,
labelpos: 'c'
},
'id' + cnt
);
});
dagre.layout(graph);
@@ -299,7 +309,7 @@ const renderDoc = (doc, diagram, parentId, altBkg) => {
stateInfo.width = stateBox.width + 2 * conf.padding;
stateInfo.height = stateBox.height + 2 * conf.padding;
logger.info('Doc rendered', stateInfo, graph);
logger.debug('Doc rendered', stateInfo, graph);
return stateInfo;
};

View File

@@ -95,6 +95,8 @@ const config = {
*/
theme: 'default',
themeCSS: undefined,
/* **maxTextSize** - The maximum allowed size of the users text diamgram */
maxTextSize: 50000,
/**
* **fontFamily** The font to be used for the rendered diagrams. Default value is \"trebuchet ms\", verdana, arial;
@@ -460,7 +462,13 @@ export const decodeEntities = function(text) {
* provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
* completed.
*/
const render = function(id, txt, cb, container) {
const render = function(id, _txt, cb, container) {
// Check the maximum allowed text size
let txt = _txt;
if (_txt.length > config.maxTextSize) {
txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
}
if (typeof container !== 'undefined') {
container.innerHTML = '';

View File

@@ -99,10 +99,6 @@ export const formatUrl = (linkStr, config) => {
if (url) {
if (config.securityLevel !== 'loose') {
return sanitizeUrl(url);
} else {
if (!/^(https?:)?\/\//i.test(url)) {
url = 'http://' + url;
}
}
return url;

View File

@@ -37,3 +37,61 @@ describe('when finding substring in array ', function() {
expect(result).toEqual(-1);
});
});
describe('when formatting urls', function() {
it('should handle links', function() {
const url = 'https://mermaid-js.github.io/mermaid/#/';
let config = { securityLevel: 'loose' };
let result = utils.formatUrl(url, config);
expect(result).toEqual(url);
config.securityLevel = 'strict';
result = utils.formatUrl(url, config);
expect(result).toEqual(url);
});
it('should handle anchors', function() {
const url = '#interaction';
let config = { securityLevel: 'loose' };
let result = utils.formatUrl(url, config);
expect(result).toEqual(url);
config.securityLevel = 'strict';
result = utils.formatUrl(url, config);
expect(result).toEqual('about:blank');
});
it('should handle mailto', function() {
const url = 'mailto:user@user.user';
let config = { securityLevel: 'loose' };
let result = utils.formatUrl(url, config);
expect(result).toEqual(url);
config.securityLevel = 'strict';
result = utils.formatUrl(url, config);
expect(result).toEqual(url);
});
it('should handle other protocols', function() {
const url = 'notes://do-your-thing/id';
let config = { securityLevel: 'loose' };
let result = utils.formatUrl(url, config);
expect(result).toEqual(url);
config.securityLevel = 'strict';
result = utils.formatUrl(url, config);
expect(result).toEqual(url);
});
it('should handle scripts', function() {
const url = 'javascript:alert("test")';
let config = { securityLevel: 'loose' };
let result = utils.formatUrl(url, config);
expect(result).toEqual(url);
config.securityLevel = 'strict';
result = utils.formatUrl(url, config);
expect(result).toEqual('about:blank');
});
});

View File

@@ -1,4 +1,3 @@
- Get familar with jison
- git graph requires a blank line at the end. why?
- Create a desktop client
- Do the rendering in an iframe to avoid global CSS to affect rendering.