Fixed test cases for sequence diagrams

Updated config to match a conversation knut and i had about the relationship between global, site, and integrator configuration
(Will update docs)
Renamed wrapEnabled to wrap
Poor man's caching for calculateTextDimensions, wrapLabel, and breakString (actually makes a huge difference)
This commit is contained in:
chris moran
2020-06-26 09:26:56 -04:00
parent 7d9bf83f66
commit 217bd1f4bf
13 changed files with 1156 additions and 1370 deletions

View File

@@ -144,7 +144,7 @@ context('Sequence diagram', () => {
it('should wrap (directive) long actor descriptions', () => { it('should wrap (directive) long actor descriptions', () => {
imgSnapshotTest( imgSnapshotTest(
` `
%%{init: {'config': {'wrapEnabled': true }}}%% %%{init: {'config': {'wrap': true }}}%%
sequenceDiagram sequenceDiagram
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
A->>Bob: Hola A->>Bob: Hola
@@ -326,7 +326,7 @@ context('Sequence diagram', () => {
it('should render a single and nested opt with long test wrapping', () => { it('should render a single and nested opt with long test wrapping', () => {
imgSnapshotTest( imgSnapshotTest(
` `
%%{init: { 'config': { 'wrapEnabled': true } } }%% %%{init: { 'config': { 'wrap': true } } }%%
sequenceDiagram sequenceDiagram
participant A participant A
participant B participant B
@@ -482,7 +482,7 @@ context('Sequence diagram', () => {
it('should render with wrapping enabled', () => { it('should render with wrapping enabled', () => {
imgSnapshotTest( imgSnapshotTest(
` `
%%{init: { 'config': { 'wrapEnabled': true }}}%% %%{init: { 'config': { 'wrap': true }}}%%
sequenceDiagram sequenceDiagram
participant A as Alice, the talkative one participant A as Alice, the talkative one
A->>John: Hello John, how are you today? I'm feeling quite verbose today. A->>John: Hello John, how are you today? I'm feeling quite verbose today.
@@ -495,7 +495,7 @@ context('Sequence diagram', () => {
}); });
it('should render with an init directive', () => { it('should render with an init directive', () => {
imgSnapshotTest( imgSnapshotTest(
`%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrapEnabled": true }}}%% `%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrap": true }}}%%
sequenceDiagram sequenceDiagram
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can! Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
Note left of Alice: Bob thinks Note left of Alice: Bob thinks
@@ -520,11 +520,10 @@ context('Sequence diagram', () => {
it('should overide config with directive settings', () => { it('should overide config with directive settings', () => {
imgSnapshotTest( imgSnapshotTest(
` `
%%{init: { "config": { "mirrorActors": false }}}%% %%{init: { "config": { "mirrorActors": false, "wrap": true }}}%%
sequenceDiagram sequenceDiagram
%%{config: { "mirrorActors": false} }%%
Alice->>Bob: I'm short Alice->>Bob: I'm short
note left of Alice: config set to mirrorActors: true<br/>directive set to mirrorActors: false note left of Alice: config: mirrorActors=true<br/>directive: mirrorActors=false
Bob->>Alice: Short as well Bob->>Alice: Short as well
`, `,
{ logLevel:0, sequence: { mirrorActors: true, noteFontSize: 18, noteFontFamily: 'Arial' } } { logLevel:0, sequence: { mirrorActors: true, noteFontSize: 18, noteFontFamily: 'Arial' } }

View File

@@ -11,429 +11,6 @@ It is is then up to the user of the API to make use of the svg, either insert it
In addition to the render function, a number of behavioral configuration options are available. In addition to the render function, a number of behavioral configuration options are available.
## Configuration
These are the default options which can be overridden with the initialization call like so:
**Example 1:**
<pre>
mermaid.initialize({
flowchart:{
htmlLabels: false
}
});
</pre>
**Example 2:**
<pre>
&lt;script>
var config = {
startOnLoad:true,
flowchart:{
useMaxWidth:true,
htmlLabels:true,
curve:'cardinal',
},
securityLevel:'loose',
};
mermaid.initialize(config);
&lt;/script>
</pre>
A summary of all options and their defaults is found [here][2]. A description of each option follows below.
## theme
theme , the CSS style sheet
**theme** - Choose one of the built-in themes:
- default
- forest
- dark
- neutral.
To disable any pre-defined mermaid theme, use "null".
**themeCSS** - Use your own CSS. This overrides **theme**.
<pre>
"theme": "forest",
"themeCSS": ".node rect { fill: red; }"
</pre>
## fontFamily
**fontFamily** The font to be used for the rendered diagrams. Default value is \\"trebuchet ms\\", verdana, arial;
## logLevel
This option decides the amount of logging to be used.
- debug: 1
- info: 2
- warn: 3
- error: 4
- fatal: (**default**) 5
## securityLevel
Sets the level of trust to be used on the parsed diagrams.
- **strict**: (**default**) tags in text are encoded, click functionality is disabeled
- **loose**: tags in text are allowed, click functionality is enabled
## startOnLoad
This options controls whether or mermaid starts when the page loads
**Default value true**.
## arrowMarkerAbsolute
This options controls whether or arrow markers in html code will be absolute paths or
an anchor, #. This matters if you are using base tag settings.
**Default value false**.
## flowchart
The object containing configurations specific for flowcharts
### htmlLabels
Flag for setting whether or not a html tag should be used for rendering labels
on the edges.
**Default value true**.
### nodeSpacing
Defines the spacing between nodes on the same level (meaning horizontal spacing for
TB or BT graphs, and the vertical spacing for LR as well as RL graphs).
**Default value 50**.
### rankSpacing
Defines the spacing between nodes on different levels (meaning vertical spacing for
TB or BT graphs, and the horizontal spacing for LR as well as RL graphs).
**Default value 50**.
### curve
How mermaid renders curves for flowcharts. Possible values are
- basis
- linear **default**
- cardinal
## sequence
The object containing configurations specific for sequence diagrams
### diagramMarginX
margin to the right and left of the sequence diagram.
**Default value 50**.
### diagramMarginY
margin to the over and under the sequence diagram.
**Default value 10**.
### actorMargin
Margin between actors.
**Default value 50**.
### width
Width of actor boxes
**Default value 150**.
### height
Height of actor boxes
**Default value 65**.
### boxMargin
Margin around loop boxes
**Default value 10**.
### boxTextMargin
margin around the text in loop/alt/opt boxes
**Default value 5**.
### noteMargin
margin around notes.
**Default value 10**.
### messageMargin
Space between messages.
**Default value 35**.
### messageAlign
Multiline message alignment. Possible values are:
- left
- center **default**
- right
### mirrorActors
mirror actors under diagram.
**Default value true**.
### bottomMarginAdj
Depending on css styling this might need adjustment.
Prolongs the edge of the diagram downwards.
**Default value 1**.
### useMaxWidth
when this flag is set the height and width is set to 100% and is then scaling with the
available space if not the absolute space required is used.
**Default value true**.
### rightAngles
This will display arrows that start and begin at the same node as right angles, rather than a curve
**Default value false**.
### showSequenceNumbers
This will show the node numbers
**Default value false**.
### actorFontSize
This sets the font size of the actor's description
**Default value 14**.
### actorFontFamily
This sets the font family of the actor's description
**Default value "Open-Sans", "sans-serif"**.
### actorFontWeight
This sets the font weight of the actor's description
\*\*Default value 400.
### noteFontSize
This sets the font size of actor-attached notes.
**Default value 14**.
### noteFontFamily
This sets the font family of actor-attached notes.
**Default value "trebuchet ms", verdana, arial**.
### noteFontWeight
This sets the font weight of the note's description
\*\*Default value 400.
### noteAlign
This sets the text alignment of actor-attached notes.
**Default value center**.
### messageFontSize
This sets the font size of actor messages.
**Default value 16**.
### messageFontFamily
This sets the font family of actor messages.
**Default value "trebuchet ms", verdana, arial**.
### messageFontWeight
This sets the font weight of the message's description
\*\*Default value 400.
### wrapEnabled
This sets the auto-wrap state for the diagram
\*\*Default value false.
### wrapPadding
This sets the auto-wrap padding for the diagram (sides only)
\*\*Default value 15.
## gantt
The object containing configurations specific for gantt diagrams\*
### titleTopMargin
Margin top for the text over the gantt diagram
**Default value 25**.
### barHeight
The height of the bars in the graph
**Default value 20**.
### barGap
The margin between the different activities in the gantt diagram.
**Default value 4**.
### topPadding
Margin between title and gantt diagram and between axis and gantt diagram.
**Default value 50**.
### leftPadding
The space allocated for the section name to the left of the activities.
**Default value 75**.
### gridLineStartPadding
Vertical starting position of the grid lines.
**Default value 35**.
### fontSize
Font size ...
**Default value 11**.
### fontFamily
font family ...
**Default value '"Open-Sans", "sans-serif"'**.
### numberSectionStyles
The number of alternating section styles.
**Default value 4**.
### axisFormat
Datetime format of the axis. This might need adjustment to match your locale and preferences
**Default value '%Y-%m-%d'**.
## journey
The object containing configurations specific for sequence diagrams
### diagramMarginX
margin to the right and left of the sequence diagram.
**Default value 50**.
### diagramMarginY
margin to the over and under the sequence diagram.
**Default value 10**.
### actorMargin
Margin between actors.
**Default value 50**.
### width
Width of actor boxes
**Default value 150**.
### height
Height of actor boxes
**Default value 65**.
### boxMargin
Margin around loop boxes
**Default value 10**.
### boxTextMargin
margin around the text in loop/alt/opt boxes
**Default value 5**.
### noteMargin
margin around notes.
**Default value 10**.
### messageMargin
Space between messages.
**Default value 35**.
### messageAlign
Multiline message alignment. Possible values are:
- left
- center **default**
- right
### bottomMarginAdj
Depending on css styling this might need adjustment.
Prolongs the edge of the diagram downwards.
**Default value 1**.
### useMaxWidth
when this flag is set the height and width is set to 100% and is then scaling with the
available space if not the absolute space required is used.
**Default value true**.
### rightAngles
This will display arrows that start and begin at the same node as right angles, rather than a curve
**Default value false**.
## er
The object containing configurations specific for entity relationship diagrams
### diagramPadding
The amount of padding around the diagram as a whole so that embedded diagrams have margins, expressed in pixels
### layoutDirection
Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL',
where T = top, B = bottom, L = left, and R = right.
### minEntityWidth
The mimimum width of an entity box, expressed in pixels
### minEntityHeight
The minimum height of an entity box, expressed in pixels
### entityPadding
The minimum internal padding between the text in an entity box and the enclosing box borders, expressed in pixels
### stroke
Stroke color of box edges and lines
### fill
Fill color of entity boxes
### fontSize
Font size (expressed as an integer representing a number of pixels)
## render ## render
Function that renders an svg with a graph from a chart definition. Usage example below. Function that renders an svg with a graph from a chart definition. Usage example below.
@@ -513,5 +90,3 @@ mermaidAPI.initialize({
</pre> </pre>
[1]: https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#render [1]: https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#render
[2]: https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#mermaidapi-configuration-defaults

View File

@@ -1,19 +1,632 @@
import { assignWithDepth } from './utils'; import { assignWithDepth } from './utils';
const config = {}; import { logger } from './logger';
/**
* These are the default options which can be overridden with the initialization call like so:
* **Example 1:**
* <pre>
* mermaid.initialize({
* flowchart:{
* htmlLabels: false
* }
* });
* </pre>
*
* **Example 2:**
* <pre>
* &lt;script>
* var currentConfig = {
* startOnLoad:true,
* flowchart:{
* useMaxWidth:true,
* htmlLabels:true,
* curve:'cardinal',
* },
*
* securityLevel:'loose',
* };
* mermaid.initialize(currentConfig);
* &lt;/script>
* </pre>
* A summary of all options and their defaults is found [here](https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#mermaidapi-configuration-defaults). A description of each option follows below.
*
* @name Configuration
*/
const config = {
/** theme , the CSS style sheet
*
* **theme** - Choose one of the built-in themes:
* * default
* * forest
* * dark
* * neutral.
* To disable any pre-defined mermaid theme, use "null".
*
* **themeCSS** - Use your own CSS. This overrides **theme**.
* <pre>
* "theme": "forest",
* "themeCSS": ".node rect { fill: red; }"
* </pre>
*/
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;
*/
fontFamily: '"trebuchet ms", verdana, arial;',
/**
* This option decides the amount of logging to be used.
* * debug: 1
* * info: 2
* * warn: 3
* * error: 4
* * fatal: (**default**) 5
*/
logLevel: 5,
/**
* Sets the level of trust to be used on the parsed diagrams.
* * **strict**: (**default**) tags in text are encoded, click functionality is disabeled
* * **loose**: tags in text are allowed, click functionality is enabled
*/
securityLevel: 'strict',
/**
* This options controls whether or mermaid starts when the page loads
* **Default value true**.
*/
startOnLoad: true,
/**
* This options controls whether or arrow markers in html code will be absolute paths or
* an anchor, #. This matters if you are using base tag settings.
* **Default value false**.
*/
arrowMarkerAbsolute: false,
/**
* This option controls which currentConfig keys are considered _secure_ and can only be changed via
* call to mermaidAPI.initialize. Calls to mermaidAPI.reinitialize cannot make changes to
* the `secure` keys in the current currentConfig. This prevents malicious graph directives from
* overriding a site's default security.
*/
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'],
/**
* The object containing configurations specific for flowcharts
*/
flowchart: {
/**
* Flag for setting whether or not a html tag should be used for rendering labels
* on the edges.
* **Default value true**.
*/
htmlLabels: true,
/**
* Defines the spacing between nodes on the same level (meaning horizontal spacing for
* TB or BT graphs, and the vertical spacing for LR as well as RL graphs).
* **Default value 50**.
*/
nodeSpacing: 50,
/**
* Defines the spacing between nodes on different levels (meaning vertical spacing for
* TB or BT graphs, and the horizontal spacing for LR as well as RL graphs).
* **Default value 50**.
*/
rankSpacing: 50,
/**
* How mermaid renders curves for flowcharts. Possible values are
* * basis
* * linear **default**
* * cardinal
*/
curve: 'linear',
// Only used in new experimental rendering
// repreesents the padding between the labels and the shape
padding: 15
},
/**
* The object containing configurations specific for sequence diagrams
*/
sequence: {
/**
* margin to the right and left of the sequence diagram.
* **Default value 50**.
*/
diagramMarginX: 50,
/**
* margin to the over and under the sequence diagram.
* **Default value 10**.
*/
diagramMarginY: 10,
/**
* Margin between actors.
* **Default value 50**.
*/
actorMargin: 50,
/**
* Width of actor boxes
* **Default value 150**.
*/
width: 150,
/**
* Height of actor boxes
* **Default value 65**.
*/
height: 65,
/**
* Margin around loop boxes
* **Default value 10**.
*/
boxMargin: 10,
/**
* margin around the text in loop/alt/opt boxes
* **Default value 5**.
*/
boxTextMargin: 5,
/**
* margin around notes.
* **Default value 10**.
*/
noteMargin: 10,
/**
* Space between messages.
* **Default value 35**.
*/
messageMargin: 35,
/**
* Multiline message alignment. Possible values are:
* * left
* * center **default**
* * right
*/
messageAlign: 'center',
/**
* mirror actors under diagram.
* **Default value true**.
*/
mirrorActors: true,
/**
* Depending on css styling this might need adjustment.
* Prolongs the edge of the diagram downwards.
* **Default value 1**.
*/
bottomMarginAdj: 1,
/**
* when this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used.
* **Default value true**.
*/
useMaxWidth: true,
/**
* This will display arrows that start and begin at the same node as right angles, rather than a curve
* **Default value false**.
*/
rightAngles: false,
/**
* This will show the node numbers
* **Default value false**.
*/
showSequenceNumbers: false,
/**
* This sets the font size of the actor's description
* **Default value 14**.
*/
actorFontSize: 14,
/**
* This sets the font family of the actor's description
* **Default value "Open-Sans", "sans-serif"**.
*/
actorFontFamily: '"Open-Sans", "sans-serif"',
/**
* This sets the font weight of the actor's description
* **Default value 400.
*/
actorFontWeight: 400,
/**
* This sets the font size of actor-attached notes.
* **Default value 14**.
*/
noteFontSize: 14,
/**
* This sets the font family of actor-attached notes.
* **Default value "trebuchet ms", verdana, arial**.
*/
noteFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the note's description
* **Default value 400.
*/
noteFontWeight: 400,
/**
* This sets the text alignment of actor-attached notes.
* **Default value center**.
*/
noteAlign: 'center',
/**
* This sets the font size of actor messages.
* **Default value 16**.
*/
messageFontSize: 16,
/**
* This sets the font family of actor messages.
* **Default value "trebuchet ms", verdana, arial**.
*/
messageFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the message's description
* **Default value 400.
*/
messageFontWeight: 400,
/**
* This sets the auto-wrap state for the diagram
* **Default value false.
*/
wrap: false,
/**
* This sets the auto-wrap padding for the diagram (sides only)
* **Default value 15.
*/
wrapPadding: 15,
/**
* This sets the width of the loop-box (loop, alt, opt, par)
* **Default value 50.
*/
labelBoxWidth: 50,
/**
* This sets the height of the loop-box (loop, alt, opt, par)
* **Default value 20.
*/
labelBoxHeight: 20,
messageFont: () => {
return {
fontFamily: config.messageFontFamily,
fontSize: config.messageFontSize,
fontWeight: config.messageFontWeight
};
},
noteFont: () => {
return {
fontFamily: config.noteFontFamily,
fontSize: config.noteFontSize,
fontWeight: config.noteFontWeight
};
},
actorFont: () => {
return {
fontFamily: config.actorFontFamily,
fontSize: config.actorFontSize,
fontWeight: config.actorFontWeight
};
}
},
/**
* The object containing configurations specific for gantt diagrams*
*/
gantt: {
/**
* Margin top for the text over the gantt diagram
* **Default value 25**.
*/
titleTopMargin: 25,
/**
* The height of the bars in the graph
* **Default value 20**.
*/
barHeight: 20,
/**
* The margin between the different activities in the gantt diagram.
* **Default value 4**.
*/
barGap: 4,
/**
* Margin between title and gantt diagram and between axis and gantt diagram.
* **Default value 50**.
*/
topPadding: 50,
/**
* The space allocated for the section name to the left of the activities.
* **Default value 75**.
*/
leftPadding: 75,
/**
* Vertical starting position of the grid lines.
* **Default value 35**.
*/
gridLineStartPadding: 35,
/**
* Font size ...
* **Default value 11**.
*/
fontSize: 11,
/**
* font family ...
* **Default value '"Open-Sans", "sans-serif"'**.
*/
fontFamily: '"Open-Sans", "sans-serif"',
/**
* The number of alternating section styles.
* **Default value 4**.
*/
numberSectionStyles: 4,
/**
* Datetime format of the axis. This might need adjustment to match your locale and preferences
* **Default value '%Y-%m-%d'**.
*/
axisFormat: '%Y-%m-%d'
},
/**
* The object containing configurations specific for sequence diagrams
*/
journey: {
/**
* margin to the right and left of the sequence diagram.
* **Default value 50**.
*/
diagramMarginX: 50,
/**
* margin to the over and under the sequence diagram.
* **Default value 10**.
*/
diagramMarginY: 10,
/**
* Margin between actors.
* **Default value 50**.
*/
actorMargin: 50,
/**
* Width of actor boxes
* **Default value 150**.
*/
width: 150,
/**
* Height of actor boxes
* **Default value 65**.
*/
height: 65,
/**
* Margin around loop boxes
* **Default value 10**.
*/
boxMargin: 10,
/**
* margin around the text in loop/alt/opt boxes
* **Default value 5**.
*/
boxTextMargin: 5,
/**
* margin around notes.
* **Default value 10**.
*/
noteMargin: 10,
/**
* Space between messages.
* **Default value 35**.
*/
messageMargin: 35,
/**
* Multiline message alignment. Possible values are:
* * left
* * center **default**
* * right
*/
messageAlign: 'center',
/**
* Depending on css styling this might need adjustment.
* Prolongs the edge of the diagram downwards.
* **Default value 1**.
*/
bottomMarginAdj: 1,
/**
* when this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used.
* **Default value true**.
*/
useMaxWidth: true,
/**
* This will display arrows that start and begin at the same node as right angles, rather than a curve
* **Default value false**.
*/
rightAngles: false
},
class: {
arrowMarkerAbsolute: false
},
git: {
arrowMarkerAbsolute: false
},
state: {
dividerMargin: 10,
sizeUnit: 5,
padding: 8,
textHeight: 10,
titleShift: -15,
noteMargin: 10,
forkWidth: 70,
forkHeight: 7,
// Used
miniPadding: 2,
// Font size factor, this is used to guess the width of the edges labels before rendering by dagre
// layout. This might need updating if/when switching font
fontSizeFactor: 5.02,
fontSize: 24,
labelHeight: 16,
edgeLengthFactor: '20',
compositTitleSize: 35,
radius: 5
},
/**
* The object containing configurations specific for entity relationship diagrams
*/
er: {
/**
* The amount of padding around the diagram as a whole so that embedded diagrams have margins, expressed in pixels
*/
diagramPadding: 20,
/**
* Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL',
* where T = top, B = bottom, L = left, and R = right.
*/
layoutDirection: 'TB',
/**
* The mimimum width of an entity box, expressed in pixels
*/
minEntityWidth: 100,
/**
* The minimum height of an entity box, expressed in pixels
*/
minEntityHeight: 75,
/**
* The minimum internal padding between the text in an entity box and the enclosing box borders, expressed in pixels
*/
entityPadding: 15,
/**
* Stroke color of box edges and lines
*/
stroke: 'gray',
/**
* Fill color of entity boxes
*/
fill: 'honeydew',
/**
* Font size (expressed as an integer representing a number of pixels)
*/
fontSize: 12
}
};
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
config.git.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
export const defaultConfig = Object.freeze(config);
const siteConfig = assignWithDepth({}, defaultConfig);
const currentConfig = assignWithDepth({}, defaultConfig);
/**
* Sets the siteConfig. The siteConfig is a protected configuration for repeat use. Calls to reset() will reset
* the currentConfig to siteConfig. Calls to reset(configApi.defaultConfig) will reset siteConfig and currentConfig
* to the defaultConfig
* Note: currentConfig is set in this function
* @param conf - the base currentConfig to use as siteConfig
* @returns {*} - the siteConfig
*/
export const setSiteConfig = conf => {
assignWithDepth(currentConfig, conf, { clobber: true });
assignWithDepth(siteConfig, conf);
return getSiteConfig();
};
/**
* Obtains the current siteConfig base configuration
* @returns {*}
*/
export const getSiteConfig = () => {
return assignWithDepth({}, siteConfig);
};
/**
* Sets the currentConfig. The param conf is sanitized based on the siteConfig.secure keys. Any
* values found in conf with key found in siteConfig.secure will be replaced with the corresponding
* siteConfig value.
* @param conf - the potential currentConfig
* @returns {*} - the currentConfig merged with the sanitized conf
*/
export const setConfig = conf => { export const setConfig = conf => {
assignWithDepth(config, conf); sanitize(conf);
assignWithDepth(currentConfig, conf);
return getConfig();
}; };
export const getConfig = () => config; /**
* Obtains the currentConfig
export const reset = conf => { * @returns {*} - the currentConfig
Object.keys(config).forEach(key => delete config[key]); */
assignWithDepth(config, conf, { clobber: true }); export const getConfig = () => {
return assignWithDepth({}, currentConfig);
};
/**
* Ensures options parameter does not attempt to override siteConfig secure keys
* Note: modifies options in-place
* @param options - the potential setConfig parameter
*/
export const sanitize = options => {
Object.keys(siteConfig.secure).forEach(key => {
if (typeof options[siteConfig.secure[key]] !== 'undefined') {
// DO NOT attempt to print options[siteConfig.secure[key]] within `${}` as a malicious script
// can exploit the logger's attempt to stringify the value and execute arbitrary code
logger.warn(
`Denied attempt to modify a secure key ${siteConfig.secure[key]}`,
options[siteConfig.secure[key]]
);
delete options[siteConfig.secure[key]];
}
});
};
/**
* Resets this currentConfig to conf
* @param conf - the base currentConfig to reset to (default: current siteConfig )
*/
export const reset = (conf = getSiteConfig()) => {
Object.keys(siteConfig).forEach(key => delete siteConfig[key]);
Object.keys(currentConfig).forEach(key => delete currentConfig[key]);
assignWithDepth(siteConfig, conf, { clobber: true });
assignWithDepth(currentConfig, conf, { clobber: true });
}; };
const configApi = { const configApi = Object.freeze({
sanitize,
setSiteConfig,
getSiteConfig,
setConfig, setConfig,
getConfig, getConfig,
reset reset,
}; defaultConfig
});
export default configApi; export default configApi;

52
src/config.spec.js Normal file
View File

@@ -0,0 +1,52 @@
/* eslint-env jasmine */
import configApi from './config';
describe('when working with site config', function() {
beforeEach(() => {
configApi.reset(configApi.defaultConfig);
});
it('should set site config and config properly', function() {
let config_0 = { foo: 'bar', bar: 0 };
configApi.setSiteConfig(config_0);
let config_1 = configApi.getSiteConfig();
let config_2 = configApi.getConfig();
expect(config_1.foo).toEqual(config_0.foo);
expect(config_1.bar).toEqual(config_0.bar);
expect(config_1).toEqual(config_2);
});
it('should set config and respect secure keys', function() {
let config_0 = { foo: 'bar', bar: 0, secure: [...configApi.defaultConfig.secure, 'bar'] };
configApi.setSiteConfig(config_0);
let config_1 = { foo: 'baf', bar: 'foo'};
configApi.setConfig(config_1);
let config_2 = configApi.getConfig();
expect(config_2.foo).toEqual(config_1.foo);
expect(config_2.bar).toEqual(0); // Should be siteConfig.bar
});
it('should set reset config properly', function() {
let config_0 = { foo: 'bar', bar: 0};
configApi.setSiteConfig(config_0);
let config_1 = { foo: 'baf'};
configApi.setConfig(config_1);
let config_2 = configApi.getConfig();
expect(config_2.foo).toEqual(config_1.foo);
configApi.reset();
let config_3 = configApi.getConfig();
expect(config_3.foo).toEqual(config_0.foo);
let config_4 = configApi.getSiteConfig();
expect(config_4.foo).toEqual(config_0.foo);
});
it('should set global reset config properly', function() {
let config_0 = { foo: 'bar', bar: 0};
configApi.setSiteConfig(config_0);
let config_1 = configApi.getSiteConfig();
expect(config_1.foo).toEqual(config_0.foo);
let config_2 = configApi.getConfig();
expect(config_2.foo).toEqual(config_0.foo);
configApi.reset(configApi.defaultConfig);
let config_3 = configApi.getSiteConfig();
expect(config_3.foo).toBeUndefined();
let config_4 = configApi.getConfig();
expect(config_4.foo).toBeUndefined();
});
});

View File

@@ -230,7 +230,7 @@ argDirective
; ;
closeDirective closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive'); } : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); }
; ;
%% %%

View File

@@ -1,5 +1,5 @@
import { logger } from '../../logger';
import mermaidAPI from '../../mermaidAPI'; import mermaidAPI from '../../mermaidAPI';
import configApi from '../../config';
let prevActor = undefined; let prevActor = undefined;
let actors = {}; let actors = {};
@@ -9,63 +9,9 @@ let title = '';
let titleWrapped = false; let titleWrapped = false;
let sequenceNumbersEnabled = false; let sequenceNumbersEnabled = false;
let wrapEnabled = false; let wrapEnabled = false;
let currentDirective = {};
export const parseDirective = function(statement, context) { export const parseDirective = function(statement, context, type) {
try { mermaidAPI.parseDirective(statement, context, type);
if (statement !== undefined) {
statement = statement.trim();
switch (context) {
case 'open_directive':
currentDirective = {};
break;
case 'type_directive':
currentDirective.type = statement.toLowerCase();
break;
case 'arg_directive':
currentDirective.args = JSON.parse(statement);
break;
case 'close_directive':
handleDirective(currentDirective);
currentDirective = null;
break;
}
}
} catch (error) {
logger.error(
`Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}`
);
logger.error(error.message);
}
};
const handleDirective = function(directive) {
logger.debug(`Directive type=${directive.type} with args:`, directive.args);
switch (directive.type) {
case 'init':
case 'initialize':
['config'].forEach(prop => {
if (typeof directive.args[prop] !== 'undefined') {
directive.args.sequence = directive.args[prop];
delete directive.args[prop];
}
});
mermaidAPI.initialize(directive.args);
break;
case 'wrap':
case 'nowrap':
wrapEnabled = directive.type === 'wrap';
break;
default:
logger.warn(
`Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify(
directive.args ? directive.args : {}
)}}%%`,
directive
);
break;
}
}; };
export const addActor = function(id, name, description) { export const addActor = function(id, name, description) {
@@ -126,22 +72,8 @@ export const addSignal = function(
message = { text: undefined, wrap: undefined }, message = { text: undefined, wrap: undefined },
messageType messageType
) { ) {
logger.debug(
'Adding message from=' +
idFrom +
' to=' +
idTo +
' message=' +
message.text +
' wrap=' +
message.wrap +
' type=' +
messageType
);
if (messageType === LINETYPE.ACTIVE_END) { if (messageType === LINETYPE.ACTIVE_END) {
const cnt = activationCount(idFrom.actor); const cnt = activationCount(idFrom.actor);
logger.debug('Adding message from=', messages, cnt);
if (cnt < 1) { if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant // Bail out as there is an activation signal from an inactive participant
let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')'); let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
@@ -356,6 +288,7 @@ export default {
getActorKeys, getActorKeys,
getTitle, getTitle,
parseDirective, parseDirective,
getConfig: () => configApi.getConfig().sequence,
getTitleWrapped, getTitleWrapped,
clear, clear,
parseMessage, parseMessage,

View File

@@ -23,7 +23,7 @@ Alice->Bob:Hello Bob, how are you?
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -41,7 +41,7 @@ Alice->Bob:Hello Bob, how are you?
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
expect(parser.yy.showSequenceNumbers()).toBe(false); expect(parser.yy.showSequenceNumbers()).toBe(false);
}); });
it('it should show sequence numbers when autonumber is enabled', function() { it('it should show sequence numbers when autonumber is enabled', function() {
@@ -52,7 +52,7 @@ Alice->Bob:Hello Bob, how are you?
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
expect(parser.yy.showSequenceNumbers()).toBe(true); expect(parser.yy.showSequenceNumbers()).toBe(true);
}); });
it('it should handle a sequenceDiagram definition with a title', function() { it('it should handle a sequenceDiagram definition with a title', function() {
@@ -63,7 +63,7 @@ Alice->Bob:Hello Bob, how are you?
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -82,7 +82,7 @@ sequenceDiagram
Alice->Bob:Hello Bob, how are - you? Alice->Bob:Hello Bob, how are - you?
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -101,7 +101,7 @@ participant B as Bob
A->B:Hello Bob, how are you? A->B:Hello Bob, how are you?
B-->A: I am good thanks!`; B-->A: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['A', 'B']); expect(Object.keys(actors)).toEqual(['A', 'B']);
@@ -118,7 +118,7 @@ B-->A: I am good thanks!`;
sequenceDiagram sequenceDiagram
Alice-xBob:Hello Bob, how are you?`; Alice-xBob:Hello Bob, how are you?`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -133,7 +133,7 @@ Alice-xBob:Hello Bob, how are you?`;
sequenceDiagram sequenceDiagram
Alice--xBob:Hello Bob, how are you?`; Alice--xBob:Hello Bob, how are you?`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -148,7 +148,7 @@ Alice--xBob:Hello Bob, how are you?`;
sequenceDiagram sequenceDiagram
Alice->>Bob:Hello Bob, how are you?`; Alice->>Bob:Hello Bob, how are you?`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -161,7 +161,7 @@ Alice->>Bob:Hello Bob, how are you?`;
it('it should handle in arrow messages', function() { it('it should handle in arrow messages', function() {
const str = 'sequenceDiagram\n' + 'Alice-->>Bob:Hello Bob, how are you?'; const str = 'sequenceDiagram\n' + 'Alice-->>Bob:Hello Bob, how are you?';
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -179,7 +179,7 @@ activate Bob
Bob-->>Alice:Hello Alice, I'm fine and you? Bob-->>Alice:Hello Alice, I'm fine and you?
deactivate Bob`; deactivate Bob`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -200,7 +200,7 @@ deactivate Bob`;
Alice-->>+Bob:Hello Bob, how are you? Alice-->>+Bob:Hello Bob, how are you?
Bob-->>- Alice:Hello Alice, I'm fine and you?`; Bob-->>- Alice:Hello Alice, I'm fine and you?`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -223,7 +223,7 @@ deactivate Bob`;
Bob-->>- Alice:Hello Alice, please meet Carol? Bob-->>- Alice:Hello Alice, please meet Carol?
Carol->>- Bob:Oh Bob, I'm so happy to be here!`; Carol->>- Bob:Oh Bob, I'm so happy to be here!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob'); expect(actors.Bob.description).toBe('Bob');
@@ -261,7 +261,7 @@ deactivate Bob`;
let error = false; let error = false;
try { try {
parser.parse(str); mermaidAPI.parse(str);
} catch (e) { } catch (e) {
console.log(e.hash); console.log(e.hash);
error = true; error = true;
@@ -277,7 +277,7 @@ deactivate Bob`;
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -298,7 +298,7 @@ deactivate Bob`;
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
`; `;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -313,7 +313,7 @@ deactivate Bob`;
const str = ` const str = `
sequenceDiagram;Alice->Bob: Hello Bob, how are you?;Note right of Bob: Bob thinks;Bob-->Alice: I am good thanks!;`; sequenceDiagram;Alice->Bob: Hello Bob, how are you?;Note right of Bob: Bob thinks;Bob-->Alice: I am good thanks!;`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -333,7 +333,7 @@ sequenceDiagram
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -353,7 +353,7 @@ sequenceDiagram
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`; Bob-->Alice: I am good thanks!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -378,7 +378,7 @@ Note right of John: Rational thoughts<br/>prevail...
John->Bob: How about you? John->Bob: How about you?
Bob-->John: Jolly good!`; Bob-->John: Jolly good!`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -406,7 +406,7 @@ note right of 4: multiline<br />text
note right of 1: multiline<br \t/>text note right of 1: multiline<br \t/>text
`; `;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors['1'].description).toBe('multiline<br>text'); expect(actors['1'].description).toBe('multiline<br>text');
@@ -431,7 +431,7 @@ Alice->Bob: Hello Bob, how are you?
Note over Bob: Bob thinks Note over Bob: Bob thinks
`; `;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].from).toBe('Bob'); expect(messages[1].from).toBe('Bob');
@@ -445,7 +445,7 @@ Note over Alice,Bob: confusion
Note over Bob,Alice: resolution Note over Bob,Alice: resolution
`; `;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].from).toBe('Alice'); expect(messages[1].from).toBe('Alice');
@@ -465,7 +465,7 @@ loop Multiple happy responses
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -487,7 +487,7 @@ end`;
end end
`; `;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -512,7 +512,7 @@ end`;
Bob-->Alice: I am good thanks Bob-->Alice: I am good thanks
end end
`; `;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -539,7 +539,7 @@ opt Perhaps a happy response
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob'; actors.Bob.description = 'Bob';
@@ -564,7 +564,7 @@ else isSick
Bob-->Alice: Feel sick... Bob-->Alice: Feel sick...
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
@@ -591,7 +591,7 @@ Bob-->Alice: Feel sick...
else default else default
Bob-->Alice: :-) Bob-->Alice: :-)
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages.length).toBe(9); expect(messages.length).toBe(9);
expect(messages[1].from).toBe('Bob'); expect(messages[1].from).toBe('Bob');
@@ -617,7 +617,7 @@ Alice->>Bob: What do you think about it?
Bob-->>Alice: It's good! Bob-->>Alice: It's good!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(actors.Alice.description).toBe('Alice'); expect(actors.Alice.description).toBe('Alice');
@@ -633,7 +633,7 @@ end`;
it('it should handle special characters in signals', function() { it('it should handle special characters in signals', function() {
const str = 'sequenceDiagram\n' + 'Alice->Bob: -:<>,;# comment'; const str = 'sequenceDiagram\n' + 'Alice->Bob: -:<>,;# comment';
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[0].message).toBe('-:<>,'); expect(messages[0].message).toBe('-:<>,');
@@ -644,7 +644,7 @@ sequenceDiagram
Alice->Bob: Hello Bob, how are you? Alice->Bob: Hello Bob, how are you?
Note right of Bob: -:<>,;# comment`; Note right of Bob: -:<>,;# comment`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe('-:<>,'); expect(messages[1].message).toBe('-:<>,');
@@ -657,7 +657,7 @@ loop -:<>,;# comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe('-:<>,'); expect(messages[1].message).toBe('-:<>,');
@@ -670,7 +670,7 @@ opt -:<>,;# comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe('-:<>,'); expect(messages[1].message).toBe('-:<>,');
@@ -685,7 +685,7 @@ else ,<>:-#; comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe('-:<>,'); expect(messages[1].message).toBe('-:<>,');
@@ -701,7 +701,7 @@ and ,<>:-#; comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe('-:<>,'); expect(messages[1].message).toBe('-:<>,');
@@ -715,7 +715,7 @@ loop
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe(''); expect(messages[1].message).toBe('');
@@ -729,7 +729,7 @@ opt # comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe(''); expect(messages[1].message).toBe('');
@@ -744,7 +744,7 @@ else # comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe(''); expect(messages[1].message).toBe('');
@@ -761,7 +761,7 @@ and # comment
Bob-->Alice: I am good thanks! Bob-->Alice: I am good thanks!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
expect(messages[1].message).toBe(''); expect(messages[1].message).toBe('');
@@ -772,12 +772,8 @@ end`;
}); });
describe('when checking the bounds in a sequenceDiagram', function() { describe('when checking the bounds in a sequenceDiagram', function() {
let conf; beforeAll(() => {
beforeEach(function() { let conf = {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = {
diagramMarginX: 50, diagramMarginX: 50,
diagramMarginY: 10, diagramMarginY: 10,
actorMargin: 50, actorMargin: 50,
@@ -791,13 +787,21 @@ describe('when checking the bounds in a sequenceDiagram', function() {
}; };
mermaidAPI.initialize({ sequence: conf }); mermaidAPI.initialize({ sequence: conf });
});
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
renderer.bounds.init(); renderer.bounds.init();
conf = parser.yy.getConfig();
}); });
it('it should handle a simple bound call', function() { it('it should handle a simple bound call', function() {
renderer.bounds.insert(100, 100, 200, 200); renderer.bounds.insert(100, 100, 200, 200);
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(100); expect(bounds.startx).toBe(100);
expect(bounds.starty).toBe(100); expect(bounds.starty).toBe(100);
expect(bounds.stopx).toBe(200); expect(bounds.stopx).toBe(200);
@@ -808,7 +812,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
renderer.bounds.insert(100, 100, 200, 200); renderer.bounds.insert(100, 100, 200, 200);
renderer.bounds.insert(25, 50, 300, 400); renderer.bounds.insert(25, 50, 300, 400);
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(25); expect(bounds.startx).toBe(25);
expect(bounds.starty).toBe(50); expect(bounds.starty).toBe(50);
expect(bounds.stopx).toBe(300); expect(bounds.stopx).toBe(300);
@@ -820,7 +824,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
renderer.bounds.insert(25, 50, 300, 400); renderer.bounds.insert(25, 50, 300, 400);
renderer.bounds.insert(125, 150, 150, 200); renderer.bounds.insert(125, 150, 150, 200);
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(25); expect(bounds.startx).toBe(25);
expect(bounds.starty).toBe(50); expect(bounds.starty).toBe(50);
expect(bounds.stopx).toBe(300); expect(bounds.stopx).toBe(300);
@@ -841,7 +845,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(loop.stopy).toBe(200 + conf.boxMargin); expect(loop.stopy).toBe(200 + conf.boxMargin);
// Check bounds of first loop // Check bounds of first loop
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(25); expect(bounds.startx).toBe(25);
expect(bounds.starty).toBe(50); expect(bounds.starty).toBe(50);
@@ -873,7 +877,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(loop.stopy).toBe(300 + 2 * conf.boxMargin); expect(loop.stopy).toBe(300 + 2 * conf.boxMargin);
// Check bounds of first loop // Check bounds of first loop
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(100); expect(bounds.startx).toBe(100);
expect(bounds.starty).toBe(100); expect(bounds.starty).toBe(100);
@@ -895,7 +899,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
expect(loop.stopy).toBe(300 + conf.boxMargin); expect(loop.stopy).toBe(300 + conf.boxMargin);
// Check bounds after the loop // Check bounds after the loop
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(loop.startx); expect(bounds.startx).toBe(loop.startx);
expect(bounds.starty).toBe(loop.starty); expect(bounds.starty).toBe(loop.starty);
@@ -905,13 +909,8 @@ describe('when checking the bounds in a sequenceDiagram', function() {
}); });
describe('when rendering a sequenceDiagram', function() { describe('when rendering a sequenceDiagram', function() {
let conf; beforeAll(() => {
beforeEach(function() { let conf = {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = {
diagramMarginX: 50, diagramMarginX: 50,
diagramMarginY: 10, diagramMarginY: 10,
actorMargin: 50, actorMargin: 50,
@@ -922,10 +921,17 @@ describe('when rendering a sequenceDiagram', function() {
messageMargin: 40, messageMargin: 40,
boxTextMargin: 15, boxTextMargin: 15,
noteMargin: 25, noteMargin: 25,
wrapEnabled: false, wrap: false,
mirrorActors: false mirrorActors: false
}; };
mermaidAPI.initialize({ sequence: conf }); mermaidAPI.initialize({ sequence: conf });
});
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = parser.yy.getConfig();
renderer.bounds.init(); renderer.bounds.init();
}); });
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) { ['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
@@ -935,12 +941,12 @@ it should handle one actor, when textPlacement is ${textPlacement}`, function()
sequenceDiagram sequenceDiagram
participant Alice`; participant Alice`;
mermaidAPI.initialize(addConf(conf, 'textPlacement', textPlacement)); mermaidAPI.reinitialize({sequence: { textPlacement: textPlacement}});
renderer.bounds.init(); mermaidAPI.parse(str);
parser.parse(str); // renderer.setConf(mermaidAPI.getConfig().sequence);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width); expect(bounds.stopx).toBe(conf.width);
@@ -955,7 +961,7 @@ participant Alice
participant Alice participant Alice
`; `;
parser.parse(str); mermaidAPI.parse(str);
const actors = parser.yy.getActors(); const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['Alice']); expect(Object.keys(actors)).toEqual(['Alice']);
@@ -967,15 +973,16 @@ participant Alice
Note over Alice: Alice thinks Note over Alice: Alice thinks
`; `;
parser.parse(str); expect(mermaidAPI.getConfig().sequence.mirrorActors).toBeFalsy();
mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width); expect(bounds.stopx).toBe(conf.width);
// 10 comes from mock of text height // 10 comes from mock of text height
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10); expect(bounds.stopy).toBe(models.lastNote().stopy);
}); });
it('it should handle one actor and a note to the left', function() { it('it should handle one actor and a note to the left', function() {
const str = ` const str = `
@@ -983,15 +990,15 @@ sequenceDiagram
participant Alice participant Alice
Note left of Alice: Alice thinks`; Note left of Alice: Alice thinks`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2); expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width); expect(bounds.stopx).toBe(conf.width);
// 10 comes from mock of text height // 10 comes from mock of text height
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10); expect(bounds.stopy).toBe(models.lastNote().stopy);
}); });
it('it should handle one actor and a note to the right', function() { it('it should handle one actor and a note to the right', function() {
const str = ` const str = `
@@ -999,29 +1006,29 @@ sequenceDiagram
participant Alice participant Alice
Note right of Alice: Alice thinks`; Note right of Alice: Alice thinks`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width / 2 + conf.actorMargin / 2 + conf.width); expect(bounds.stopx).toBe(conf.width / 2 + conf.actorMargin / 2 + conf.width);
// 10 comes from mock of text height // 10 comes from mock of text height
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10); expect(bounds.stopy).toBe(models.lastNote().stopy);
}); });
it('it should handle two actors', function() { it('it should handle two actors', function() {
const str = ` const str = `
sequenceDiagram sequenceDiagram
Alice->Bob: Hello Bob, how are you?`; Alice->Bob: Hello Bob, how are you?`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(conf.messageMargin + conf.height); expect(bounds.stopy).toBe(models.lastMessage().stopy);
}); });
it('it should handle two actors with init directive', function() { it('it should handle two actors with init directive', function() {
const str = ` const str = `
@@ -1029,16 +1036,16 @@ Alice->Bob: Hello Bob, how are you?`;
sequenceDiagram sequenceDiagram
Alice->Bob: Hello Bob, how are you?`; Alice->Bob: Hello Bob, how are you?`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(mermaid.logLevel).toBe(0); expect(mermaid.logLevel).toBe(0);
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(conf.height + conf.messageMargin + (conf.mirrorActors ? 2 * conf.boxMargin + conf.height : 0)); expect(bounds.stopy).toBe(models.lastMessage().stopy);
}); });
it('it should handle two actors with init directive with multiline directive', function() { it('it should handle two actors with init directive with multiline directive', function() {
const str = ` const str = `
@@ -1049,17 +1056,17 @@ wrap
}%% }%%
Alice->Bob: Hello Bob, how are you?`; Alice->Bob: Hello Bob, how are you?`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const msgs = parser.yy.getMessages(); const msgs = parser.yy.getMessages();
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(mermaid.logLevel).toBe(0); expect(mermaid.logLevel).toBe(0);
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(conf.messageMargin + conf.height); expect(bounds.stopy).toBe(models.lastMessage().stopy);
expect(msgs.every(v => v.wrap)).toBe(true); expect(msgs.every(v => v.wrap)).toBe(true);
}); });
@@ -1070,17 +1077,15 @@ Alice->Bob: Hello Bob, how are you?
Note over Alice,Bob: Looks Note over Alice,Bob: Looks
Note over Bob,Alice: Looks back Note over Bob,Alice: Looks back
`; `;
mermaidAPI.initialize({logLevel:0})
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastNote().stopy);
conf.height + conf.messageMargin + 2 * (conf.boxMargin + 2 * conf.noteMargin + 10)
);
}); });
it('it should draw two actors and two messages', function() { it('it should draw two actors and two messages', function() {
const str = ` const str = `
@@ -1088,14 +1093,14 @@ sequenceDiagram
Alice->Bob: Hello Bob, how are you? Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine!`; Bob->Alice: Fine!`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(0 + conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(0 + 2 * conf.messageMargin + conf.height); expect(bounds.stopy).toBe(models.lastMessage().stopy);
}); });
it('it should draw two actors notes to the right', function() { it('it should draw two actors notes to the right', function() {
const str = ` const str = `
@@ -1104,19 +1109,17 @@ Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks Note right of Bob: Bob thinks
Bob->Alice: Fine!`; Bob->Alice: Fine!`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
const expStopX = conf.actorMargin + conf.width + conf.width / 2 + conf.noteMargin + conf.width; const expStopX = conf.actorMargin + conf.width + conf.width / 2 + conf.noteMargin + conf.width;
expect(bounds.stopx).toBe(expStopX); expect(bounds.stopx).toBe(expStopX);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastMessage().stopy);
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
}); });
it('it should draw two actors notes to the left', function() { it('it should draw two actors notes to the left', function() {
const str = ` const str = `
@@ -1125,17 +1128,15 @@ Alice->Bob: Hello Bob, how are you?
Note left of Alice: Bob thinks Note left of Alice: Bob thinks
Bob->Alice: Fine!`; Bob->Alice: Fine!`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2); expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastMessage().stopy);
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
}); });
it('it should draw two actors notes to the left with text wrapped (inline)', function() { it('it should draw two actors notes to the left with text wrapped (inline)', function() {
const str = ` const str = `
@@ -1144,19 +1145,17 @@ Alice->>Bob:wrap: Hello Bob, how are you? If you are not available right now, I
Note left of Alice: Bob thinks Note left of Alice: Bob thinks
Bob->>Alice: Fine!`; Bob->>Alice: Fine!`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const msgs = parser.yy.getMessages(); const msgs = parser.yy.getMessages();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2); expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(msgs[0].wrap).toBe(true); expect(msgs[0].wrap).toBe(true);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastMessage().stopy);
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
}); });
it('it should draw two actors notes to the left with text wrapped (directive)', function() { it('it should draw two actors notes to the left with text wrapped (directive)', function() {
const str = ` const str = `
@@ -1167,10 +1166,10 @@ Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can l
Note left of Alice: Bob thinks Note left of Alice: Bob thinks
Bob->>Alice: Fine!`; Bob->>Alice: Fine!`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const msgs = parser.yy.getMessages(); const msgs = parser.yy.getMessages();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2); expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
@@ -1179,9 +1178,7 @@ Bob->>Alice: Fine!`;
expect(msgs.every(v => v.wrap)).toBe(true); expect(msgs.every(v => v.wrap)).toBe(true);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastMessage().stopy);
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
}); });
it('it should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark', function() { it('it should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark', function() {
const str = ` const str = `
@@ -1191,10 +1188,11 @@ sequenceDiagram
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can! Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
Note left of Alice: Bob thinks Note left of Alice: Bob thinks
Bob->>Alice: Fine!`; Bob->>Alice: Fine!`;
parser.parse(str);
mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const msgs = parser.yy.getMessages(); const msgs = parser.yy.getMessages();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2); expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
@@ -1203,22 +1201,19 @@ Bob->>Alice: Fine!`;
expect(msgs.every(v => v.wrap)).toBe(true); expect(msgs.every(v => v.wrap)).toBe(true);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastMessage().stopy);
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
}); });
it('it should draw two actors, notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function() { it('it should draw two actors, notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function() {
const str = ` const str = `
%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrapEnabled": true }}}%% %%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrap": true }}}%%
sequenceDiagram sequenceDiagram
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can! Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
Note left of Alice: Bob thinks Note left of Alice: Bob thinks
Bob->>Alice: Fine!`; Bob->>Alice: Fine!`;
parser.parse(str); mermaidAPI.parse(str);
// renderer.setConf(mermaidAPI.getConfig().sequence);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const msgs = parser.yy.getMessages(); const msgs = parser.yy.getMessages();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2); expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
@@ -1230,9 +1225,7 @@ Bob->>Alice: Fine!`;
expect(msgs.every(v => v.wrap)).toBe(true); expect(msgs.every(v => v.wrap)).toBe(true);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastMessage().stopy);
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
);
}); });
it('it should draw two loops', function() { it('it should draw two loops', function() {
const str = ` const str = `
@@ -1241,17 +1234,15 @@ Alice->Bob: Hello Bob, how are you?
loop Cheers loop Cheers
Bob->Alice: Fine! Bob->Alice: Fine!
end`; end`;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe( expect(bounds.stopy).toBe(models.lastLoop().stopy);
2 * conf.messageMargin + conf.height + 3 * conf.boxMargin + conf.boxTextMargin
);
}); });
it('it should draw background rect', function() { it('it should draw background rect', function() {
const str = ` const str = `
@@ -1261,26 +1252,21 @@ end`;
Bob->Alice: I feel surrounded by darkness Bob->Alice: I feel surrounded by darkness
end end
`; `;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(0 + conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(0 + 2 * conf.messageMargin + conf.height + 3 * conf.boxMargin); expect(bounds.stopy).toBe(models.lastLoop().stopy);
}); });
}); });
describe('when rendering a sequenceDiagram with actor mirror activated', function() { describe('when rendering a sequenceDiagram with actor mirror activated', function() {
let conf; beforeAll(() => {
beforeEach(function() { let conf = {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = {
diagramMarginX: 50, diagramMarginX: 50,
diagramMarginY: 10, diagramMarginY: 10,
actorMargin: 50, actorMargin: 50,
@@ -1296,7 +1282,16 @@ describe('when rendering a sequenceDiagram with actor mirror activated', functio
// Prolongs the edge of the diagram downwards // Prolongs the edge of the diagram downwards
bottomMarginAdj: 1 bottomMarginAdj: 1
}; };
mermaidAPI.initialize({ sequence: conf }); mermaidAPI.initialize({ sequence: conf });
});
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = parser.yy.getConfig();
renderer.bounds.init(); renderer.bounds.init();
}); });
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) { ['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
@@ -1306,31 +1301,26 @@ describe('when rendering a sequenceDiagram with actor mirror activated', functio
const str = ` const str = `
sequenceDiagram sequenceDiagram
participant Alice`; participant Alice`;
renderer.bounds.init();
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width); expect(bounds.stopx).toBe(conf.width);
expect(bounds.stopy).toBe(2 * conf.height + 2 * conf.boxMargin); expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
}); });
}); });
}); });
describe('when rendering a sequenceDiagram with directives', function() { describe('when rendering a sequenceDiagram with directives', function() {
let conf; beforeAll(function() {
beforeEach(function() { let conf = {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = {
diagramMarginX: 50, diagramMarginX: 50,
diagramMarginY: 10, diagramMarginY: 10,
actorMargin: 50, actorMargin: 50,
width: 150, width: 150,
// Height of actor boxes
height: 65, height: 65,
boxMargin: 10, boxMargin: 10,
messageMargin: 40, messageMargin: 40,
@@ -1338,6 +1328,14 @@ describe('when rendering a sequenceDiagram with directives', function() {
noteMargin: 25 noteMargin: 25
}; };
mermaidAPI.initialize({ sequence: conf }); mermaidAPI.initialize({ sequence: conf });
});
let conf;
beforeEach(function() {
mermaidAPI.reset();
parser.yy = sequenceDb;
parser.yy.clear();
conf = parser.yy.getConfig();
renderer.bounds.init(); renderer.bounds.init();
}); });
@@ -1349,17 +1347,18 @@ sequenceDiagram
participant Alice participant Alice
`; `;
parser.parse(str); renderer.bounds.init();
mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(mermaid.theme).toBe('dark'); expect(mermaid.theme).toBe('dark');
expect(mermaid.logLevel).toBe(1); expect(mermaid.logLevel).toBe(1);
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(2 * conf.height + 2 * conf.boxMargin); expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
}); });
it('it should handle one actor, when logLevel is 3', function() { it('it should handle one actor, when logLevel is 3', function() {
const str = ` const str = `
@@ -1368,15 +1367,15 @@ sequenceDiagram
participant Alice participant Alice
`; `;
parser.parse(str); mermaidAPI.parse(str);
renderer.draw(str, 'tst'); renderer.draw(str, 'tst');
const bounds = renderer.bounds.getBounds(); const { bounds, models } = renderer.bounds.getBounds();
const mermaid = mermaidAPI.getConfig(); const mermaid = mermaidAPI.getConfig();
expect(mermaid.logLevel).toBe(3); expect(mermaid.logLevel).toBe(3);
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(2 * conf.height + 2 * conf.boxMargin); expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
}); });
}); });

View File

@@ -8,79 +8,7 @@ import utils, { assignWithDepth } from '../../utils';
parser.yy = sequenceDb; parser.yy = sequenceDb;
const conf = { const conf = {};
diagramMarginX: 50,
diagramMarginY: 30,
// Margin between actors
actorMargin: 50,
// Width of actor boxes
width: 150,
// Height of actor boxes
height: 65,
actorFontSize: 14,
actorFontFamily: '"Open-Sans", "sans-serif"',
// 400 = normal
actorFontWeight: 400,
// Note font settings
noteFontSize: 14,
noteFontFamily: '"trebuchet ms", verdana, arial',
noteFontWeight: 400,
noteAlign: 'center',
// Message font settings
messageFontSize: 16,
messageFontFamily: '"trebuchet ms", verdana, arial',
messageFontWeight: 400,
// Margin around loop boxes
boxMargin: 10,
boxTextMargin: 5,
noteMargin: 10,
// Space between messages
messageMargin: 35,
// Multiline message alignment
messageAlign: 'center',
// mirror actors under diagram
mirrorActors: false,
// Depending on css styling this might need adjustment
// Prolongs the edge of the diagram downwards
bottomMarginAdj: 1,
// width of activation box
activationWidth: 10,
labelBoxWidth: 50,
labelBoxHeight: 20,
// text placement as: tspan | fo | old only text as before
textPlacement: 'tspan',
showSequenceNumbers: false,
// wrap text
wrapEnabled: false,
// padding for wrapped text
wrapPadding: 15,
messageFont: () => {
return {
fontFamily: conf.messageFontFamily,
fontSize: conf.messageFontSize,
fontWeight: conf.messageFontWeight
};
},
noteFont: () => {
return {
fontFamily: conf.noteFontFamily,
fontSize: conf.noteFontSize,
fontWeight: conf.noteFontWeight
};
},
actorFont: () => {
return {
fontFamily: conf.actorFontFamily,
fontSize: conf.actorFontSize,
fontWeight: conf.actorFontWeight
};
}
};
export const bounds = { export const bounds = {
data: { data: {
@@ -90,10 +18,57 @@ export const bounds = {
stopy: undefined stopy: undefined
}, },
verticalPos: 0, verticalPos: 0,
sequenceItems: [], sequenceItems: [],
activations: [], activations: [],
models: { models: {
getHeight: function() {
return (
Math.max.apply(
null,
this.actors.length === 0 ? [0] : this.actors.map(actor => actor.height || 0)
) +
(this.loops.length === 0
? 0
: this.loops.map(it => it.height || 0).reduce((acc, h) => acc + h)) +
(this.messages.length === 0
? 0
: this.messages.map(it => it.height || 0).reduce((acc, h) => acc + h)) +
(this.notes.length === 0
? 0
: this.notes.map(it => it.height || 0).reduce((acc, h) => acc + h))
);
},
clear: function() {
this.actors = [];
this.loops = [];
this.messages = [];
this.notes = [];
},
addActor: function(actorModel) {
this.actors.push(actorModel);
},
addLoop: function(loopModel) {
this.loops.push(loopModel);
},
addMessage: function(msgModel) {
this.messages.push(msgModel);
},
addNote: function(noteModel) {
this.notes.push(noteModel);
},
lastActor: function() {
return this.actors[this.actors.length - 1];
},
lastLoop: function() {
return this.loops[this.loops.length - 1];
},
lastMessage: function() {
return this.messages[this.messages.length - 1];
},
lastNote: function() {
return this.notes[this.notes.length - 1];
},
actors: [],
loops: [], loops: [],
messages: [], messages: [],
notes: [] notes: []
@@ -101,11 +76,7 @@ export const bounds = {
init: function() { init: function() {
this.sequenceItems = []; this.sequenceItems = [];
this.activations = []; this.activations = [];
this.models = { this.models.clear();
loops: [],
messages: [],
notes: []
};
this.data = { this.data = {
startx: undefined, startx: undefined,
stopx: undefined, stopx: undefined,
@@ -113,6 +84,7 @@ export const bounds = {
stopy: undefined stopy: undefined
}; };
this.verticalPos = 0; this.verticalPos = 0;
setConf(parser.yy.getConfig());
}, },
updateVal: function(obj, key, val, fun) { updateVal: function(obj, key, val, fun) {
if (typeof obj[key] === 'undefined') { if (typeof obj[key] === 'undefined') {
@@ -219,7 +191,7 @@ export const bounds = {
return this.verticalPos; return this.verticalPos;
}, },
getBounds: function() { getBounds: function() {
return this.data; return { bounds: this.data, models: this.models };
} }
}; };
@@ -229,17 +201,20 @@ export const bounds = {
* @param noteModel:{x: number, y: number, message: string, width: number} - startx: x axis start position, verticalPos: y axis position, messsage: the message to be shown, width: Set this with a custom width to override the default configured width. * @param noteModel:{x: number, y: number, message: string, width: number} - startx: x axis start position, verticalPos: y axis position, messsage: the message to be shown, width: Set this with a custom width to override the default configured width.
*/ */
const drawNote = function(elem, noteModel) { const drawNote = function(elem, noteModel) {
bounds.bumpVerticalPos(conf.boxMargin);
noteModel.height = conf.boxMargin;
noteModel.starty = bounds.getVerticalPos();
const rect = svgDraw.getNoteRect(); const rect = svgDraw.getNoteRect();
rect.x = noteModel.x; rect.x = noteModel.startx;
rect.y = noteModel.y; rect.y = noteModel.starty;
rect.width = noteModel.width || conf.width; rect.width = noteModel.width || conf.width;
rect.class = 'note'; rect.class = 'note';
let g = elem.append('g'); let g = elem.append('g');
const rectElem = svgDraw.drawRect(g, rect); const rectElem = svgDraw.drawRect(g, rect);
const textObj = svgDraw.getTextObj(); const textObj = svgDraw.getTextObj();
textObj.x = noteModel.x; textObj.x = noteModel.startx;
textObj.y = noteModel.y; textObj.y = noteModel.starty;
textObj.width = rect.width; textObj.width = rect.width;
textObj.dy = '1em'; textObj.dy = '1em';
textObj.text = noteModel.message; textObj.text = noteModel.message;
@@ -258,12 +233,13 @@ const drawNote = function(elem, noteModel) {
textElem.map(te => (te._groups || te)[0][0].getBBox().height).reduce((acc, curr) => acc + curr) textElem.map(te => (te._groups || te)[0][0].getBBox().height).reduce((acc, curr) => acc + curr)
); );
noteModel.height = textHeight + 2 * conf.noteMargin; rectElem.attr('height', textHeight + 2 * conf.noteMargin);
noteModel.height += textHeight + 2 * conf.noteMargin;
bounds.insert(noteModel.x, noteModel.y, noteModel.x + rect.width, noteModel.y + noteModel.height); bounds.bumpVerticalPos(textHeight + 2 * conf.noteMargin);
noteModel.stopy = noteModel.starty + textHeight + 2 * conf.noteMargin;
rectElem.attr('height', noteModel.height); noteModel.stopx = noteModel.startx + rect.width;
bounds.bumpVerticalPos(noteModel.height); bounds.insert(noteModel.startx, noteModel.starty, noteModel.stopx, noteModel.stopy);
bounds.models.addNote(noteModel);
}; };
/** /**
@@ -272,10 +248,13 @@ const drawNote = function(elem, noteModel) {
* @param msgModel - the model containing fields describing a message * @param msgModel - the model containing fields describing a message
*/ */
const drawMessage = function(g, msgModel) { const drawMessage = function(g, msgModel) {
bounds.bumpVerticalPos(conf.messageMargin);
msgModel.height += conf.messageMargin;
msgModel.starty = bounds.getVerticalPos();
const { startx, stopx, starty: verticalPos, message, type, sequenceIndex, wrap } = msgModel; const { startx, stopx, starty: verticalPos, message, type, sequenceIndex, wrap } = msgModel;
const textObj = svgDraw.getTextObj(); const textObj = svgDraw.getTextObj();
textObj.x = startx; textObj.x = startx;
textObj.y = verticalPos - (conf.messageFontSize + 7); textObj.y = verticalPos;
textObj.width = stopx - startx; textObj.width = stopx - startx;
textObj.class = 'messageText'; textObj.class = 'messageText';
textObj.dy = '1em'; textObj.dy = '1em';
@@ -290,20 +269,18 @@ const drawMessage = function(g, msgModel) {
textObj.wrap = wrap; textObj.wrap = wrap;
let textElem = drawText(g, textObj); let textElem = drawText(g, textObj);
const lineHeight = (textElem[0]._groups || textElem[0])[0][0].getBBox().height;
textElem.forEach(te => te.attr('y', verticalPos - 7 - lineHeight / 2));
const lines = message.split(common.lineBreakRegex).length - 1 > 0 ? 1 : 0; const lines = message.split(common.lineBreakRegex).length - 1;
let totalOffset = Math.round( let totalOffset = Math.round(lineHeight + lines * lineHeight);
textElem.map(te => (te._groups || te)[0][0].getBBox().height).reduce((acc, curr) => acc + curr)
);
let textWidth = Math.max.apply( let textWidth = Math.max.apply(
null, null,
textElem.map(te => (te._groups || te)[0][0].getBBox().width) textElem.map(te => (te._groups || te)[0][0].getBBox().width)
); );
bounds.bumpVerticalPos(totalOffset - lines * conf.messageFontSize);
let line; let line;
if (startx === stopx) { if (startx === stopx) {
if (conf.rightAngles) { if (conf.rightAngles) {
@@ -342,25 +319,32 @@ const drawMessage = function(g, msgModel) {
} }
bounds.bumpVerticalPos(30); bounds.bumpVerticalPos(30);
msgModel.height += 30;
const dx = Math.max(textWidth / 2, 100); const dx = Math.max(textWidth / 2, 100);
bounds.insert( bounds.insert(
startx - dx, startx - dx,
bounds.getVerticalPos() - 10 + totalOffset, bounds.getVerticalPos() - 10 + totalOffset,
stopx + dx, stopx + dx,
bounds.getVerticalPos() + totalOffset bounds.getVerticalPos() + 30 + totalOffset
); );
bounds.bumpVerticalPos(10);
msgModel.height += 10;
} else { } else {
line = g.append('line'); line = g.append('line');
line.attr('x1', startx); line.attr('x1', startx);
line.attr('y1', verticalPos + totalOffset); line.attr('y1', verticalPos + totalOffset);
line.attr('x2', stopx); line.attr('x2', stopx);
line.attr('y2', verticalPos + totalOffset); line.attr('y2', verticalPos + totalOffset);
bounds.bumpVerticalPos(10);
msgModel.height += 10;
bounds.insert( bounds.insert(
startx, startx,
bounds.getVerticalPos() - 10 + totalOffset, bounds.getVerticalPos() - 10 + totalOffset,
stopx, stopx,
bounds.getVerticalPos() + totalOffset bounds.getVerticalPos() + totalOffset
); );
msgModel.height += 10;
bounds.bumpVerticalPos(10);
} }
// Make an SVG Container // Make an SVG Container
// Draw the line // Draw the line
@@ -411,6 +395,9 @@ const drawMessage = function(g, msgModel) {
.attr('class', 'sequenceNumber') .attr('class', 'sequenceNumber')
.text(sequenceIndex); .text(sequenceIndex);
} }
msgModel.stopy = msgModel.starty + msgModel.height;
bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.stopy);
logger.debug(`mm.h:${msgModel.height} vs c.h:${msgModel.stopy - msgModel.starty}`);
}; };
export const drawActors = function(diagram, actors, actorKeys, verticalPos) { export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
@@ -435,6 +422,7 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
prevWidth += actor.width; prevWidth += actor.width;
prevMargin += actor.margin; prevMargin += actor.margin;
bounds.models.addActor(actor);
} }
// Add a margin between the actor boxes and the first arrow // Add a margin between the actor boxes and the first arrow
@@ -461,7 +449,7 @@ const actorActivations = function(actor) {
}); });
}; };
const actorFlowVerticaBounds = function(actor, actors) { const activationBounds = function(actor, actors) {
// handle multiple stacked activations for same actor // handle multiple stacked activations for same actor
const actorObj = actors[actor]; const actorObj = actors[actor];
const activations = actorActivations(actor); const activations = actorActivations(actor);
@@ -498,7 +486,7 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
*/ */
export const draw = function(text, id) { export const draw = function(text, id) {
parser.yy.clear(); parser.yy.clear();
parser.yy.setWrap(conf.wrapEnabled); parser.yy.setWrap(conf.wrap);
parser.parse(text + '\n'); parser.parse(text + '\n');
bounds.init(); bounds.init();
@@ -545,11 +533,8 @@ export const draw = function(text, id) {
switch (msg.type) { switch (msg.type) {
case parser.yy.LINETYPE.NOTE: case parser.yy.LINETYPE.NOTE:
bounds.bumpVerticalPos(conf.boxMargin);
noteModel = msg.noteModel; noteModel = msg.noteModel;
noteModel.y = bounds.getVerticalPos();
drawNote(diagram, noteModel); drawNote(diagram, noteModel);
bounds.models.notes.push(noteModel);
break; break;
case parser.yy.LINETYPE.ACTIVE_START: case parser.yy.LINETYPE.ACTIVE_START:
bounds.newActivation(msg, diagram, actors); bounds.newActivation(msg, diagram, actors);
@@ -568,9 +553,9 @@ export const draw = function(text, id) {
break; break;
case parser.yy.LINETYPE.LOOP_END: case parser.yy.LINETYPE.LOOP_END:
loopModel = bounds.endLoop(); loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'loop', conf); svgDraw.drawLoop(diagram, loopModel, 'loop', conf, bounds);
bounds.models.loops.push(loopModel);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break; break;
case parser.yy.LINETYPE.RECT_START: case parser.yy.LINETYPE.RECT_START:
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message => adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
@@ -580,7 +565,7 @@ export const draw = function(text, id) {
case parser.yy.LINETYPE.RECT_END: case parser.yy.LINETYPE.RECT_END:
loopModel = bounds.endLoop(); loopModel = bounds.endLoop();
svgDraw.drawBackgroundRect(diagram, loopModel); svgDraw.drawBackgroundRect(diagram, loopModel);
bounds.models.loops.push(loopModel); bounds.models.addLoop(loopModel);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
break; break;
case parser.yy.LINETYPE.OPT_START: case parser.yy.LINETYPE.OPT_START:
@@ -595,8 +580,8 @@ export const draw = function(text, id) {
case parser.yy.LINETYPE.OPT_END: case parser.yy.LINETYPE.OPT_END:
loopModel = bounds.endLoop(); loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'opt', conf); svgDraw.drawLoop(diagram, loopModel, 'opt', conf);
bounds.models.loops.push(loopModel);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break; break;
case parser.yy.LINETYPE.ALT_START: case parser.yy.LINETYPE.ALT_START:
adjustLoopHeightForWrap( adjustLoopHeightForWrap(
@@ -619,8 +604,8 @@ export const draw = function(text, id) {
case parser.yy.LINETYPE.ALT_END: case parser.yy.LINETYPE.ALT_END:
loopModel = bounds.endLoop(); loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'alt', conf); svgDraw.drawLoop(diagram, loopModel, 'alt', conf);
bounds.models.loops.push(loopModel);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break; break;
case parser.yy.LINETYPE.PAR_START: case parser.yy.LINETYPE.PAR_START:
adjustLoopHeightForWrap( adjustLoopHeightForWrap(
@@ -643,19 +628,16 @@ export const draw = function(text, id) {
case parser.yy.LINETYPE.PAR_END: case parser.yy.LINETYPE.PAR_END:
loopModel = bounds.endLoop(); loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'par', conf); svgDraw.drawLoop(diagram, loopModel, 'par', conf);
bounds.models.loops.push(loopModel);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break; break;
default: default:
try { try {
// lastMsg = msg // lastMsg = msg
bounds.bumpVerticalPos(conf.messageMargin);
msgModel = msg.msgModel; msgModel = msg.msgModel;
msgModel.starty = bounds.getVerticalPos();
msgModel.sequenceIndex = sequenceIndex; msgModel.sequenceIndex = sequenceIndex;
drawMessage(diagram, msgModel); drawMessage(diagram, msgModel);
bounds.models.messages.push(msgModel); bounds.models.addMessage(msgModel);
bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.starty);
} catch (e) { } catch (e) {
logger.error('error while drawing message', e); logger.error('error while drawing message', e);
} }
@@ -681,7 +663,7 @@ export const draw = function(text, id) {
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos()); drawActors(diagram, actors, actorKeys, bounds.getVerticalPos());
} }
const box = bounds.getBounds(); const { bounds: box } = bounds.getBounds();
// Adjust line height of actor lines now that the height of the diagram is known // Adjust line height of actor lines now that the height of the diagram is known
logger.debug('For line height fix Querying: #' + id + ' .actor-line'); logger.debug('For line height fix Querying: #' + id + ' .actor-line');
@@ -724,7 +706,7 @@ export const draw = function(text, id) {
' ' + ' ' +
(height + extraVertForTitle) (height + extraVertForTitle)
); );
logger.debug('bounds', bounds); logger.debug(`models: ${JSON.stringify(bounds.models, null, 2)}`);
}; };
/** /**
@@ -879,13 +861,16 @@ const buildNoteModel = function(msg, actors) {
shouldWrap ? utils.wrapLabel(msg.message, conf.width, conf.noteFont()) : msg.message, shouldWrap ? utils.wrapLabel(msg.message, conf.width, conf.noteFont()) : msg.message,
conf.noteFont() conf.noteFont()
); );
logger.debug(`TD:[${textDimensions.width},${textDimensions.height}]`);
let noteModel = { let noteModel = {
width: shouldWrap width: shouldWrap
? conf.width ? conf.width
: Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin), : Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin),
height: 0, height: 0,
x: actors[msg.from].x, startx: actors[msg.from].x,
y: 0, stopx: 0,
starty: 0,
stopy: 0,
message: msg.message message: msg.message
}; };
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) { if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
@@ -895,7 +880,7 @@ const buildNoteModel = function(msg, actors) {
actors[msg.from].width / 2 + actors[msg.to].width / 2, actors[msg.from].width / 2 + actors[msg.to].width / 2,
textDimensions.width + 2 * conf.noteMargin textDimensions.width + 2 * conf.noteMargin
); );
noteModel.x = startx + (actors[msg.from].width + conf.actorMargin) / 2; noteModel.startx = startx + (actors[msg.from].width + conf.actorMargin) / 2;
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) { } else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
noteModel.width = shouldWrap noteModel.width = shouldWrap
? Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin) ? Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin)
@@ -903,7 +888,7 @@ const buildNoteModel = function(msg, actors) {
actors[msg.from].width / 2 + actors[msg.to].width / 2, actors[msg.from].width / 2 + actors[msg.to].width / 2,
textDimensions.width + 2 * conf.noteMargin textDimensions.width + 2 * conf.noteMargin
); );
noteModel.x = startx - noteModel.width + (actors[msg.from].width - conf.actorMargin) / 2; noteModel.startx = startx - noteModel.width + (actors[msg.from].width - conf.actorMargin) / 2;
} else if (msg.to === msg.from) { } else if (msg.to === msg.from) {
textDimensions = utils.calculateTextDimensions( textDimensions = utils.calculateTextDimensions(
shouldWrap shouldWrap
@@ -914,12 +899,12 @@ const buildNoteModel = function(msg, actors) {
noteModel.width = shouldWrap noteModel.width = shouldWrap
? Math.max(conf.width, actors[msg.to].width) ? Math.max(conf.width, actors[msg.to].width)
: Math.max(actors[msg.to].width, conf.width, textDimensions.width + 2 * conf.noteMargin); : Math.max(actors[msg.to].width, conf.width, textDimensions.width + 2 * conf.noteMargin);
noteModel.x = startx + (actors[msg.to].width - noteModel.width) / 2; noteModel.startx = startx + (actors[msg.to].width - noteModel.width) / 2;
} else { } else {
noteModel.width = noteModel.width =
Math.abs(startx + actors[msg.from].width / 2 - (stopx + actors[msg.to].width / 2)) + Math.abs(startx + actors[msg.from].width / 2 - (stopx + actors[msg.to].width / 2)) +
conf.actorMargin; conf.actorMargin;
noteModel.x = noteModel.startx =
startx < stopx startx < stopx
? startx + actors[msg.from].width / 2 - conf.actorMargin / 2 ? startx + actors[msg.from].width / 2 - conf.actorMargin / 2
: stopx + actors[msg.to].width / 2 - conf.actorMargin / 2; : stopx + actors[msg.to].width / 2 - conf.actorMargin / 2;
@@ -951,8 +936,8 @@ const buildMessageModel = function(msg, actors) {
if (!process) { if (!process) {
return {}; return {};
} }
const fromBounds = actorFlowVerticaBounds(msg.from, actors); const fromBounds = activationBounds(msg.from, actors);
const toBounds = actorFlowVerticaBounds(msg.to, actors); const toBounds = activationBounds(msg.to, actors);
const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0; const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0;
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1; const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
const allBounds = fromBounds.concat(toBounds); const allBounds = fromBounds.concat(toBounds);
@@ -985,7 +970,7 @@ const calculateLoopBounds = function(messages, actors) {
let current, noteModel, msgModel; let current, noteModel, msgModel;
messages.forEach(function(msg) { messages.forEach(function(msg) {
msg.id = utils.generateId(); msg.id = utils.random({ length: 10 });
switch (msg.type) { switch (msg.type) {
case parser.yy.LINETYPE.LOOP_START: case parser.yy.LINETYPE.LOOP_START:
case parser.yy.LINETYPE.ALT_START: case parser.yy.LINETYPE.ALT_START:
@@ -1046,8 +1031,8 @@ const calculateLoopBounds = function(messages, actors) {
let depth = 0; let depth = 0;
stack.forEach(stk => { stack.forEach(stk => {
current = stk; current = stk;
current.from = Math.min(current.from, noteModel.x); current.from = Math.min(current.from, noteModel.startx);
current.to = Math.max(current.to, noteModel.x + noteModel.width); current.to = Math.max(current.to, noteModel.startx + noteModel.width);
current.width = current.width =
Math.max(current.width, Math.abs(current.from - current.to)) - Math.max(current.width, Math.abs(current.from - current.to)) -
50 - 50 -

View File

@@ -251,11 +251,11 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct
/** /**
* Draws a loop in the diagram * Draws a loop in the diagram
* @param elem - elemenet to append the loop to. * @param elem - elemenet to append the loop to.
* @param bounds - bounds of the given loop. * @param loopModel - loopModel of the given loop.
* @param labelText - Text within the loop. * @param labelText - Text within the loop.
* @param conf * @param conf - diagrom configuration
*/ */
export const drawLoop = function(elem, bounds, labelText, conf) { export const drawLoop = function(elem, loopModel, labelText, conf) {
const g = elem.append('g'); const g = elem.append('g');
const drawLoopLine = function(startx, starty, stopx, stopy) { const drawLoopLine = function(startx, starty, stopx, stopy) {
return g return g
@@ -266,20 +266,23 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
.attr('y2', stopy) .attr('y2', stopy)
.attr('class', 'loopLine'); .attr('class', 'loopLine');
}; };
drawLoopLine(bounds.startx, bounds.starty, bounds.stopx, bounds.starty); drawLoopLine(loopModel.startx, loopModel.starty, loopModel.stopx, loopModel.starty);
drawLoopLine(bounds.stopx, bounds.starty, bounds.stopx, bounds.stopy); drawLoopLine(loopModel.stopx, loopModel.starty, loopModel.stopx, loopModel.stopy);
drawLoopLine(bounds.startx, bounds.stopy, bounds.stopx, bounds.stopy); drawLoopLine(loopModel.startx, loopModel.stopy, loopModel.stopx, loopModel.stopy);
drawLoopLine(bounds.startx, bounds.starty, bounds.startx, bounds.stopy); drawLoopLine(loopModel.startx, loopModel.starty, loopModel.startx, loopModel.stopy);
if (typeof bounds.sections !== 'undefined') { if (typeof loopModel.sections !== 'undefined') {
bounds.sections.forEach(function(item) { loopModel.sections.forEach(function(item) {
drawLoopLine(bounds.startx, item.y, bounds.stopx, item.y).style('stroke-dasharray', '3, 3'); drawLoopLine(loopModel.startx, item.y, loopModel.stopx, item.y).style(
'stroke-dasharray',
'3, 3'
);
}); });
} }
let txt = getTextObj(); let txt = getTextObj();
txt.text = labelText; txt.text = labelText;
txt.x = bounds.startx; txt.x = loopModel.startx;
txt.y = bounds.starty; txt.y = loopModel.starty;
const msgFont = conf.messageFont(); const msgFont = conf.messageFont();
txt.fontFamily = msgFont.fontFamily; txt.fontFamily = msgFont.fontFamily;
txt.fontSize = msgFont.fontSize; txt.fontSize = msgFont.fontSize;
@@ -294,9 +297,9 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
drawLabel(g, txt); drawLabel(g, txt);
txt = getTextObj(); txt = getTextObj();
txt.text = bounds.title; txt.text = loopModel.title;
txt.x = bounds.startx + conf.labelBoxWidth / 2 + (bounds.stopx - bounds.startx) / 2; txt.x = loopModel.startx + conf.labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2;
txt.y = bounds.starty + conf.boxMargin + conf.boxTextMargin; txt.y = loopModel.starty + conf.boxMargin + conf.boxTextMargin;
txt.anchor = 'middle'; txt.anchor = 'middle';
txt.class = 'loopText'; txt.class = 'loopText';
txt.fontFamily = msgFont.fontFamily; txt.fontFamily = msgFont.fontFamily;
@@ -306,12 +309,12 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
let textElem = drawText(g, txt); let textElem = drawText(g, txt);
if (typeof bounds.sectionTitles !== 'undefined') { if (typeof loopModel.sectionTitles !== 'undefined') {
bounds.sectionTitles.forEach(function(item, idx) { loopModel.sectionTitles.forEach(function(item, idx) {
if (item.message) { if (item.message) {
txt.text = item.message; txt.text = item.message;
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2; txt.x = loopModel.startx + (loopModel.stopx - loopModel.startx) / 2;
txt.y = bounds.sections[idx].y + conf.boxMargin + conf.boxTextMargin; txt.y = loopModel.sections[idx].y + conf.boxMargin + conf.boxTextMargin;
txt.class = 'loopText'; txt.class = 'loopText';
txt.anchor = 'middle'; txt.anchor = 'middle';
txt.valign = 'middle'; txt.valign = 'middle';
@@ -319,20 +322,19 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
txt.fontFamily = msgFont.fontFamily; txt.fontFamily = msgFont.fontFamily;
txt.fontSize = msgFont.fontSize; txt.fontSize = msgFont.fontSize;
txt.fontWeight = msgFont.fontWeight; txt.fontWeight = msgFont.fontWeight;
txt.wrap = bounds.wrap; txt.wrap = loopModel.wrap;
textElem = drawText(g, txt); textElem = drawText(g, txt);
let sectionHeight = Math.round( let sectionHeight = Math.round(
textElem textElem
.map(te => (te._groups || te)[0][0].getBBox().height) .map(te => (te._groups || te)[0][0].getBBox().height)
.reduce((acc, curr) => acc + curr) .reduce((acc, curr) => acc + curr)
); );
bounds.sections[idx].height += sectionHeight - (conf.boxMargin + conf.boxTextMargin); loopModel.sections[idx].height += sectionHeight - (conf.boxMargin + conf.boxTextMargin);
} }
}); });
} }
bounds.height = Math.round(bounds.stopy - bounds.starty); loopModel.height = Math.round(loopModel.stopy - loopModel.starty);
return g; return g;
}; };

View File

@@ -71,11 +71,11 @@ const init = function() {
logger.debug('Start On Load before: ' + mermaid.startOnLoad); logger.debug('Start On Load before: ' + mermaid.startOnLoad);
if (typeof mermaid.startOnLoad !== 'undefined') { if (typeof mermaid.startOnLoad !== 'undefined') {
logger.debug('Start On Load inner: ' + mermaid.startOnLoad); logger.debug('Start On Load inner: ' + mermaid.startOnLoad);
mermaidAPI.setConfig({ startOnLoad: mermaid.startOnLoad }); mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad });
} }
if (typeof mermaid.ganttConfig !== 'undefined') { if (typeof mermaid.ganttConfig !== 'undefined') {
mermaidAPI.setConfig({ gantt: mermaid.ganttConfig }); mermaidAPI.initialize({ gantt: mermaid.ganttConfig });
} }
let txt; let txt;

View File

@@ -13,7 +13,7 @@
import { select } from 'd3'; import { select } from 'd3';
import scope from 'scope-css'; import scope from 'scope-css';
import pkg from '../package.json'; import pkg from '../package.json';
import { setConfig, getConfig } from './config'; import { setConfig, getConfig, setSiteConfig, getSiteConfig } from './config';
import { logger, setLogLevel } from './logger'; import { logger, setLogLevel } from './logger';
import utils, { assignWithDepth } from './utils'; import utils, { assignWithDepth } from './utils';
import flowRenderer from './diagrams/flowchart/flowRenderer'; import flowRenderer from './diagrams/flowchart/flowRenderer';
@@ -56,529 +56,11 @@ for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
themes[themeName] = require(`./themes/${themeName}/index.scss`); themes[themeName] = require(`./themes/${themeName}/index.scss`);
} }
/**
* These are the default options which can be overridden with the initialization call like so:
* **Example 1:**
* <pre>
* mermaid.initialize({
* flowchart:{
* htmlLabels: false
* }
* });
* </pre>
*
* **Example 2:**
* <pre>
* &lt;script>
* var config = {
* startOnLoad:true,
* flowchart:{
* useMaxWidth:true,
* htmlLabels:true,
* curve:'cardinal',
* },
*
* securityLevel:'loose',
* };
* mermaid.initialize(config);
* &lt;/script>
* </pre>
* A summary of all options and their defaults is found [here](https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#mermaidapi-configuration-defaults). A description of each option follows below.
*
* @name Configuration
*/
const config = {
/** theme , the CSS style sheet
*
* **theme** - Choose one of the built-in themes:
* * default
* * forest
* * dark
* * neutral.
* To disable any pre-defined mermaid theme, use "null".
*
* **themeCSS** - Use your own CSS. This overrides **theme**.
* <pre>
* "theme": "forest",
* "themeCSS": ".node rect { fill: red; }"
* </pre>
*/
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;
*/
fontFamily: '"trebuchet ms", verdana, arial;',
/**
* This option decides the amount of logging to be used.
* * debug: 1
* * info: 2
* * warn: 3
* * error: 4
* * fatal: (**default**) 5
*/
logLevel: 5,
/**
* Sets the level of trust to be used on the parsed diagrams.
* * **strict**: (**default**) tags in text are encoded, click functionality is disabeled
* * **loose**: tags in text are allowed, click functionality is enabled
*/
securityLevel: 'strict',
/**
* This options controls whether or mermaid starts when the page loads
* **Default value true**.
*/
startOnLoad: true,
/**
* This options controls whether or arrow markers in html code will be absolute paths or
* an anchor, #. This matters if you are using base tag settings.
* **Default value false**.
*/
arrowMarkerAbsolute: false,
/**
* The object containing configurations specific for flowcharts
*/
flowchart: {
/**
* Flag for setting whether or not a html tag should be used for rendering labels
* on the edges.
* **Default value true**.
*/
htmlLabels: true,
/**
* Defines the spacing between nodes on the same level (meaning horizontal spacing for
* TB or BT graphs, and the vertical spacing for LR as well as RL graphs).
* **Default value 50**.
*/
nodeSpacing: 50,
/**
* Defines the spacing between nodes on different levels (meaning vertical spacing for
* TB or BT graphs, and the horizontal spacing for LR as well as RL graphs).
* **Default value 50**.
*/
rankSpacing: 50,
/**
* How mermaid renders curves for flowcharts. Possible values are
* * basis
* * linear **default**
* * cardinal
*/
curve: 'linear',
// Only used in new experimental rendering
// repreesents the padding between the labels and the shape
padding: 15
},
/**
* The object containing configurations specific for sequence diagrams
*/
sequence: {
/**
* margin to the right and left of the sequence diagram.
* **Default value 50**.
*/
diagramMarginX: 50,
/**
* margin to the over and under the sequence diagram.
* **Default value 10**.
*/
diagramMarginY: 10,
/**
* Margin between actors.
* **Default value 50**.
*/
actorMargin: 50,
/**
* Width of actor boxes
* **Default value 150**.
*/
width: 150,
/**
* Height of actor boxes
* **Default value 65**.
*/
height: 65,
/**
* Margin around loop boxes
* **Default value 10**.
*/
boxMargin: 10,
/**
* margin around the text in loop/alt/opt boxes
* **Default value 5**.
*/
boxTextMargin: 5,
/**
* margin around notes.
* **Default value 10**.
*/
noteMargin: 10,
/**
* Space between messages.
* **Default value 35**.
*/
messageMargin: 35,
/**
* Multiline message alignment. Possible values are:
* * left
* * center **default**
* * right
*/
messageAlign: 'center',
/**
* mirror actors under diagram.
* **Default value true**.
*/
mirrorActors: true,
/**
* Depending on css styling this might need adjustment.
* Prolongs the edge of the diagram downwards.
* **Default value 1**.
*/
bottomMarginAdj: 1,
/**
* when this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used.
* **Default value true**.
*/
useMaxWidth: true,
/**
* This will display arrows that start and begin at the same node as right angles, rather than a curve
* **Default value false**.
*/
rightAngles: false,
/**
* This will show the node numbers
* **Default value false**.
*/
showSequenceNumbers: false,
/**
* This sets the font size of the actor's description
* **Default value 14**.
*/
actorFontSize: 14,
/**
* This sets the font family of the actor's description
* **Default value "Open-Sans", "sans-serif"**.
*/
actorFontFamily: '"Open-Sans", "sans-serif"',
/**
* This sets the font weight of the actor's description
* **Default value 400.
*/
actorFontWeight: 400,
/**
* This sets the font size of actor-attached notes.
* **Default value 14**.
*/
noteFontSize: 14,
/**
* This sets the font family of actor-attached notes.
* **Default value "trebuchet ms", verdana, arial**.
*/
noteFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the note's description
* **Default value 400.
*/
noteFontWeight: 400,
/**
* This sets the text alignment of actor-attached notes.
* **Default value center**.
*/
noteAlign: 'center',
/**
* This sets the font size of actor messages.
* **Default value 16**.
*/
messageFontSize: 16,
/**
* This sets the font family of actor messages.
* **Default value "trebuchet ms", verdana, arial**.
*/
messageFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the message's description
* **Default value 400.
*/
messageFontWeight: 400,
/**
* This sets the auto-wrap state for the diagram
* **Default value false.
*/
wrapEnabled: false,
/**
* This sets the auto-wrap padding for the diagram (sides only)
* **Default value 15.
*/
wrapPadding: 15,
/**
* This sets the width of the loop-box (loop, alt, opt, par)
* **Default value 50.
*/
labelBoxWidth: 50,
/**
* This sets the height of the loop-box (loop, alt, opt, par)
* **Default value 20.
*/
labelBoxHeight: 20
},
/**
* The object containing configurations specific for gantt diagrams*
*/
gantt: {
/**
* Margin top for the text over the gantt diagram
* **Default value 25**.
*/
titleTopMargin: 25,
/**
* The height of the bars in the graph
* **Default value 20**.
*/
barHeight: 20,
/**
* The margin between the different activities in the gantt diagram.
* **Default value 4**.
*/
barGap: 4,
/**
* Margin between title and gantt diagram and between axis and gantt diagram.
* **Default value 50**.
*/
topPadding: 50,
/**
* The space allocated for the section name to the left of the activities.
* **Default value 75**.
*/
leftPadding: 75,
/**
* Vertical starting position of the grid lines.
* **Default value 35**.
*/
gridLineStartPadding: 35,
/**
* Font size ...
* **Default value 11**.
*/
fontSize: 11,
/**
* font family ...
* **Default value '"Open-Sans", "sans-serif"'**.
*/
fontFamily: '"Open-Sans", "sans-serif"',
/**
* The number of alternating section styles.
* **Default value 4**.
*/
numberSectionStyles: 4,
/**
* Datetime format of the axis. This might need adjustment to match your locale and preferences
* **Default value '%Y-%m-%d'**.
*/
axisFormat: '%Y-%m-%d'
},
/**
* The object containing configurations specific for sequence diagrams
*/
journey: {
/**
* margin to the right and left of the sequence diagram.
* **Default value 50**.
*/
diagramMarginX: 50,
/**
* margin to the over and under the sequence diagram.
* **Default value 10**.
*/
diagramMarginY: 10,
/**
* Margin between actors.
* **Default value 50**.
*/
actorMargin: 50,
/**
* Width of actor boxes
* **Default value 150**.
*/
width: 150,
/**
* Height of actor boxes
* **Default value 65**.
*/
height: 65,
/**
* Margin around loop boxes
* **Default value 10**.
*/
boxMargin: 10,
/**
* margin around the text in loop/alt/opt boxes
* **Default value 5**.
*/
boxTextMargin: 5,
/**
* margin around notes.
* **Default value 10**.
*/
noteMargin: 10,
/**
* Space between messages.
* **Default value 35**.
*/
messageMargin: 35,
/**
* Multiline message alignment. Possible values are:
* * left
* * center **default**
* * right
*/
messageAlign: 'center',
/**
* Depending on css styling this might need adjustment.
* Prolongs the edge of the diagram downwards.
* **Default value 1**.
*/
bottomMarginAdj: 1,
/**
* when this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used.
* **Default value true**.
*/
useMaxWidth: true,
/**
* This will display arrows that start and begin at the same node as right angles, rather than a curve
* **Default value false**.
*/
rightAngles: false
},
class: {},
git: {},
state: {
dividerMargin: 10,
sizeUnit: 5,
padding: 8,
textHeight: 10,
titleShift: -15,
noteMargin: 10,
forkWidth: 70,
forkHeight: 7,
// Used
miniPadding: 2,
// Font size factor, this is used to guess the width of the edges labels before rendering by dagre
// layout. This might need updating if/when switching font
fontSizeFactor: 5.02,
fontSize: 24,
labelHeight: 16,
edgeLengthFactor: '20',
compositTitleSize: 35,
radius: 5
},
/**
* The object containing configurations specific for entity relationship diagrams
*/
er: {
/**
* The amount of padding around the diagram as a whole so that embedded diagrams have margins, expressed in pixels
*/
diagramPadding: 20,
/**
* Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL',
* where T = top, B = bottom, L = left, and R = right.
*/
layoutDirection: 'TB',
/**
* The mimimum width of an entity box, expressed in pixels
*/
minEntityWidth: 100,
/**
* The minimum height of an entity box, expressed in pixels
*/
minEntityHeight: 75,
/**
* The minimum internal padding between the text in an entity box and the enclosing box borders, expressed in pixels
*/
entityPadding: 15,
/**
* Stroke color of box edges and lines
*/
stroke: 'gray',
/**
* Fill color of entity boxes
*/
fill: 'honeydew',
/**
* Font size (expressed as an integer representing a number of pixels)
*/
fontSize: 12
}
};
export const defaultConfig = Object.freeze(assignWithDepth({}, config));
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
config.git.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
setLogLevel(config.logLevel);
configApi.reset(config);
function parse(text) { function parse(text) {
const graphInit = utils.detectInit(text); const graphInit = utils.detectInit(text);
if (graphInit) { if (graphInit) {
initialize(graphInit); reinitialize(graphInit);
logger.debug('Init ', graphInit); logger.debug('reinit ', graphInit);
} }
const graphType = utils.detectType(text); const graphType = utils.detectType(text);
let parser; let parser;
@@ -640,7 +122,7 @@ function parse(text) {
parser.parser.yy = journeyDb; parser.parser.yy = journeyDb;
break; break;
} }
parser.parser.yy.graphType = graphType;
parser.parser.yy.parseError = (str, hash) => { parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash }; const error = { str, hash };
throw error; throw error;
@@ -722,7 +204,7 @@ const render = function(id, _txt, cb, container) {
} }
const graphInit = utils.detectInit(txt); const graphInit = utils.detectInit(txt);
if (graphInit) { if (graphInit) {
initialize(graphInit); reinitialize(graphInit);
assignWithDepth(cnf, getConfig()); assignWithDepth(cnf, getConfig());
} }
@@ -942,6 +424,79 @@ const render = function(id, _txt, cb, container) {
return svgCode; return svgCode;
}; };
let currentDirective = {};
const parseDirective = function(statement, context, type) {
try {
if (statement !== undefined) {
statement = statement.trim();
switch (context) {
case 'open_directive':
currentDirective = {};
break;
case 'type_directive':
currentDirective.type = statement.toLowerCase();
break;
case 'arg_directive':
currentDirective.args = JSON.parse(statement);
break;
case 'close_directive':
handleDirective(currentDirective, type);
currentDirective = null;
break;
}
}
} catch (error) {
logger.error(
`Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}`
);
logger.error(error.message);
}
};
const handleDirective = function(directive, type) {
logger.debug(`Directive type=${directive.type} with args:`, directive.args);
switch (directive.type) {
case 'init':
case 'initialize': {
['config'].forEach(prop => {
if (typeof directive.args[prop] !== 'undefined') {
if (type === 'flowchart-v2') {
type = 'flowchart';
}
directive.args[type] = directive.args[prop];
delete directive.args[prop];
}
});
reinitialize(directive.args);
break;
}
case 'wrap':
case 'nowrap':
directive.args = { config: { wrap: directive.type === 'wrap' } };
['config'].forEach(prop => {
if (typeof directive.args[prop] !== 'undefined') {
if (type === 'flowchart-v2') {
type = 'flowchart';
}
directive.args[type] = directive.args[prop];
delete directive.args[prop];
}
});
reinitialize(directive.args);
break;
default:
logger.warn(
`Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify(
directive.args ? directive.args : {}
)}}%%`,
directive
);
break;
}
};
function updateRendererConfigs(conf) { function updateRendererConfigs(conf) {
gitGraphRenderer.setConf(conf.git); gitGraphRenderer.setConf(conf.git);
flowRenderer.setConf(conf.flowchart); flowRenderer.setConf(conf.flowchart);
@@ -961,14 +516,20 @@ function updateRendererConfigs(conf) {
errorRenderer.setConf(conf.class); errorRenderer.setConf(conf.class);
} }
function initialize(options) { function reinitialize(options) {
console.log(`mermaidAPI.initialize: v${pkg.version}`); console.log(`mermaidAPI.reinitialize: v${pkg.version}`, options);
// Set default options // Set default options
if (typeof options === 'object') { const config = typeof options === 'object' ? setConfig(options) : getSiteConfig();
assignWithDepth(config, options); updateRendererConfigs(config);
updateRendererConfigs(config); setLogLevel(config.logLevel);
} logger.debug('mermaidAPI.reinitialize: ', config);
setConfig(config); }
function initialize(options) {
// console.log(`mermaidAPI.initialize: v${pkg.version}`);
// Set default options
const config = typeof options === 'object' ? setSiteConfig(options) : getSiteConfig();
updateRendererConfigs(config);
setLogLevel(config.logLevel); setLogLevel(config.logLevel);
logger.debug('mermaidAPI.initialize: ', config); logger.debug('mermaidAPI.initialize: ', config);
} }
@@ -977,22 +538,30 @@ function initialize(options) {
// console.warn('get config') // console.warn('get config')
// return config // return config
// } // }
const mermaidAPI = Object.freeze({ const mermaidAPI = Object.freeze({
render, render,
parse, parse,
parseDirective,
initialize, initialize,
reinitialize,
getConfig, getConfig,
setConfig, getSiteConfig,
reset: () => { reset: () => {
// console.warn('reset'); // console.warn('reset');
configApi.reset(defaultConfig); configApi.reset();
assignWithDepth(config, defaultConfig, { clobber: true }); const siteConfig = getSiteConfig();
updateRendererConfigs(config); updateRendererConfigs(siteConfig);
}, },
defaultConfig globalReset: () => {
configApi.reset(configApi.defaultConfig);
updateRendererConfigs(getConfig());
},
defaultConfig: configApi.defaultConfig
}); });
setLogLevel(getConfig().logLevel);
configApi.reset(getConfig());
export default mermaidAPI; export default mermaidAPI;
/** /**
* ## mermaidAPI configuration defaults * ## mermaidAPI configuration defaults

View File

@@ -6,7 +6,7 @@ describe('when using mermaidAPI and ', function() {
describe('doing initialize ', function() { describe('doing initialize ', function() {
beforeEach(function() { beforeEach(function() {
document.body.innerHTML = ''; document.body.innerHTML = '';
mermaidAPI.reset(); mermaidAPI.globalReset();
}); });
it('should copy a literal into the configuration', function() { it('should copy a literal into the configuration', function() {
@@ -30,11 +30,9 @@ describe('when using mermaidAPI and ', function() {
mermaidAPI.initialize({ testObject: object }); mermaidAPI.initialize({ testObject: object });
let config = mermaidAPI.getConfig(); let config = mermaidAPI.getConfig();
console.log('1:', config);
expect(config.testObject.test1).toBe(1); expect(config.testObject.test1).toBe(1);
mermaidAPI.initialize({ testObject: { test3: true } }); mermaidAPI.initialize({ testObject: { test3: true } });
config = mermaidAPI.getConfig(); config = mermaidAPI.getConfig();
console.log(config);
expect(config.testObject.test1).toBe(1); expect(config.testObject.test1).toBe(1);
expect(config.testObject.test2).toBe(false); expect(config.testObject.test2).toBe(false);
@@ -42,13 +40,50 @@ describe('when using mermaidAPI and ', function() {
}); });
it('should reset mermaid config to global defaults', function() { it('should reset mermaid config to global defaults', function() {
let config = { let config = {
logLevel: 0 logLevel: 0,
securityLevel: 'loose'
}; };
mermaidAPI.initialize(config); mermaidAPI.initialize(config);
expect(mermaidAPI.getConfig().logLevel).toBe(0); expect(mermaidAPI.getConfig().logLevel).toBe(0);
mermaidAPI.reset(); expect(mermaidAPI.getConfig().securityLevel).toBe('loose');
mermaidAPI.globalReset();
expect(mermaidAPI.getConfig()).toEqual(mermaidAPI.defaultConfig); expect(mermaidAPI.getConfig()).toEqual(mermaidAPI.defaultConfig);
}); });
it('should reset mermaid config to site defaults', function() {
let config = {
logLevel: 0
};
mermaidAPI.initialize(config);
const siteConfig = mermaidAPI.getSiteConfig();
expect(mermaidAPI.getConfig().logLevel).toBe(0);
config.logLevel = 3;
config.securityLevel = 'loose';
mermaidAPI.reinitialize(config);
expect(mermaidAPI.getConfig().logLevel).toBe(3);
expect(mermaidAPI.getConfig().securityLevel).toBe('strict');
mermaidAPI.reset();
expect(mermaidAPI.getSiteConfig()).toEqual(siteConfig)
expect(mermaidAPI.getConfig()).toEqual(siteConfig);
});
it('should prevent changes to site defaults (sneaky)', function() {
let config = {
logLevel: 0
};
mermaidAPI.initialize(config);
const siteConfig = mermaidAPI.getSiteConfig();
expect(mermaidAPI.getConfig().logLevel).toBe(0);
config.secure = {
toString: function() {
mermaidAPI.initialize({ securityLevel: 'loose' });
}
};
mermaidAPI.reinitialize(config);
expect(mermaidAPI.getConfig().secure).toEqual(mermaidAPI.getSiteConfig().secure);
expect(mermaidAPI.getConfig().securityLevel).toBe('strict');
mermaidAPI.reset();
expect(mermaidAPI.getSiteConfig()).toEqual(siteConfig)
expect(mermaidAPI.getConfig()).toEqual(siteConfig);
});
it('should prevent clobbering global defaults (direct)', function() { it('should prevent clobbering global defaults (direct)', function() {
let config = assignWithDepth({}, mermaidAPI.defaultConfig); let config = assignWithDepth({}, mermaidAPI.defaultConfig);
assignWithDepth(config, { logLevel: 0 }); assignWithDepth(config, { logLevel: 0 });
@@ -65,7 +100,7 @@ describe('when using mermaidAPI and ', function() {
it('should prevent changes to global defaults (direct)', function() { it('should prevent changes to global defaults (direct)', function() {
let error = { message: '' }; let error = { message: '' };
try { try {
mermaidAPI.defaultConfig.logLevel = 0; mermaidAPI.defaultConfig['logLevel'] = 0;
} catch(e) { } catch(e) {
error = e; error = e;
} }

View File

@@ -15,6 +15,7 @@ import {
import { logger } from './logger'; import { logger } from './logger';
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';
import common from './diagrams/common/common'; import common from './diagrams/common/common';
import cryptoRandomString from 'crypto-random-string';
// Effectively an enum of the supported curve types, accessible by name // Effectively an enum of the supported curve types, accessible by name
const d3CurveTypes = { const d3CurveTypes = {
@@ -396,6 +397,10 @@ export const generateId = () => {
); );
}; };
export const random = options => {
return cryptoRandomString(options);
};
/** /**
* @function assignWithDepth * @function assignWithDepth
* Extends the functionality of {@link ObjectConstructor.assign} with the ability to merge arbitrary-depth objects * Extends the functionality of {@link ObjectConstructor.assign} with the ability to merge arbitrary-depth objects
@@ -442,7 +447,7 @@ export const assignWithDepth = function(dst, src, config) {
(dst[key] === undefined || typeof dst[key] === 'object') (dst[key] === undefined || typeof dst[key] === 'object')
) { ) {
if (dst[key] === undefined) { if (dst[key] === undefined) {
dst[key] = {}; dst[key] = Array.isArray(src[key]) ? [] : {};
} }
dst[key] = assignWithDepth(dst[key], src[key], { depth: depth - 1, clobber }); dst[key] = assignWithDepth(dst[key], src[key], { depth: depth - 1, clobber });
} else if (clobber || (typeof dst[key] !== 'object' && typeof src[key] !== 'object')) { } else if (clobber || (typeof dst[key] !== 'object' && typeof src[key] !== 'object')) {
@@ -501,6 +506,10 @@ export const wrapLabel = (label, maxWidth, config) => {
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0, joinWith: '<br/>' }, { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0, joinWith: '<br/>' },
config config
); );
const cacheKey = `${label}-${maxWidth}-${JSON.stringify(config)}`;
if (wrapLabel[cacheKey]) {
return wrapLabel[cacheKey];
}
if (common.lineBreakRegex.test(label)) { if (common.lineBreakRegex.test(label)) {
return label; return label;
} }
@@ -526,11 +535,17 @@ export const wrapLabel = (label, maxWidth, config) => {
completedLines.push(nextLine); completedLines.push(nextLine);
} }
}); });
return completedLines.filter(line => line !== '').join(config.joinWith); const result = completedLines.filter(line => line !== '').join(config.joinWith);
wrapLabel[cacheKey] = result;
return result;
}; };
const breakString = (word, maxWidth, hyphenCharacter = '-', config) => { const breakString = (word, maxWidth, hyphenCharacter = '-', config) => {
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 }, config); config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 }, config);
const cacheKey = `${word}-${maxWidth}-${hyphenCharacter}-${JSON.stringify(config)}`;
if (breakString[cacheKey]) {
return breakString[cacheKey];
}
const characters = word.split(''); const characters = word.split('');
const lines = []; const lines = [];
let currentLine = ''; let currentLine = '';
@@ -547,7 +562,9 @@ const breakString = (word, maxWidth, hyphenCharacter = '-', config) => {
currentLine = nextLine; currentLine = nextLine;
} }
}); });
return { hyphenatedStrings: lines, remainingWord: currentLine }; const result = { hyphenatedStrings: lines, remainingWord: currentLine };
breakString[cacheKey] = result;
return result;
}; };
/** /**
@@ -598,7 +615,11 @@ export const calculateTextDimensions = function(text, config) {
); );
const { fontSize, fontFamily, fontWeight } = config; const { fontSize, fontFamily, fontWeight } = config;
if (!text) { if (!text) {
return 0; return { width: 0, height: 0 };
}
const cacheKey = `${text}-${JSON.stringify(config)}`;
if (calculateTextDimensions[cacheKey]) {
return calculateTextDimensions[cacheKey];
} }
// We can't really know if the user supplied font family will render on the user agent; // We can't really know if the user supplied font family will render on the user agent;
@@ -638,7 +659,9 @@ export const calculateTextDimensions = function(text, config) {
g.remove(); g.remove();
// Adds some padding, so the text won't sit exactly within the actor's borders // Adds some padding, so the text won't sit exactly within the actor's borders
return { width: Math.round(maxWidth), height: Math.round(height) }; const result = { width: Math.round(maxWidth), height: Math.round(height) };
calculateTextDimensions[cacheKey] = result;
return result;
}; };
export default { export default {
@@ -657,5 +680,6 @@ export default {
formatUrl, formatUrl,
getStylesFromArray, getStylesFromArray,
generateId, generateId,
random,
runFunc runFunc
}; };