diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js
index 45cbfa047..d8ede8676 100644
--- a/cypress/integration/rendering/sequencediagram.spec.js
+++ b/cypress/integration/rendering/sequencediagram.spec.js
@@ -144,7 +144,7 @@ context('Sequence diagram', () => {
it('should wrap (directive) long actor descriptions', () => {
imgSnapshotTest(
`
- %%{init: {'config': {'wrapEnabled': true }}}%%
+ %%{init: {'config': {'wrap': true }}}%%
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
A->>Bob: Hola
@@ -326,7 +326,7 @@ context('Sequence diagram', () => {
it('should render a single and nested opt with long test wrapping', () => {
imgSnapshotTest(
`
- %%{init: { 'config': { 'wrapEnabled': true } } }%%
+ %%{init: { 'config': { 'wrap': true } } }%%
sequenceDiagram
participant A
participant B
@@ -482,7 +482,7 @@ context('Sequence diagram', () => {
it('should render with wrapping enabled', () => {
imgSnapshotTest(
`
- %%{init: { 'config': { 'wrapEnabled': true }}}%%
+ %%{init: { 'config': { 'wrap': true }}}%%
sequenceDiagram
participant A as Alice, the talkative one
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', () => {
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
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
@@ -520,11 +520,10 @@ context('Sequence diagram', () => {
it('should overide config with directive settings', () => {
imgSnapshotTest(
`
- %%{init: { "config": { "mirrorActors": false }}}%%
+ %%{init: { "config": { "mirrorActors": false, "wrap": true }}}%%
sequenceDiagram
- %%{config: { "mirrorActors": false} }%%
Alice->>Bob: I'm short
- note left of Alice: config set to mirrorActors: true
directive set to mirrorActors: false
+ note left of Alice: config: mirrorActors=true
directive: mirrorActors=false
Bob->>Alice: Short as well
`,
{ logLevel:0, sequence: { mirrorActors: true, noteFontSize: 18, noteFontFamily: 'Arial' } }
diff --git a/docs/mermaidAPI.md b/docs/mermaidAPI.md
index a4167b839..8cc7b295a 100644
--- a/docs/mermaidAPI.md
+++ b/docs/mermaidAPI.md
@@ -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.
-## Configuration
-
-These are the default options which can be overridden with the initialization call like so:
-**Example 1:**
-
-
-mermaid.initialize({ - flowchart:{ - htmlLabels: false - } -}); -- -**Example 2:** - -
-<script> - var config = { - startOnLoad:true, - flowchart:{ - useMaxWidth:true, - htmlLabels:true, - curve:'cardinal', - }, - - securityLevel:'loose', - }; - mermaid.initialize(config); -</script> -- -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**. - -
- "theme": "forest", - "themeCSS": ".node rect { fill: red; }" -- -## 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 Function that renders an svg with a graph from a chart definition. Usage example below. @@ -513,5 +90,3 @@ mermaidAPI.initialize({ [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 diff --git a/src/config.js b/src/config.js index c03a1bac4..1353ec005 100644 --- a/src/config.js +++ b/src/config.js @@ -1,19 +1,632 @@ 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:** + *
+ * mermaid.initialize({ + * flowchart:{ + * htmlLabels: false + * } + * }); + *+ * + * **Example 2:** + *
+ * <script> + * var currentConfig = { + * startOnLoad:true, + * flowchart:{ + * useMaxWidth:true, + * htmlLabels:true, + * curve:'cardinal', + * }, + * + * securityLevel:'loose', + * }; + * mermaid.initialize(currentConfig); + * </script> + *+ * 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**. + *
+ * "theme": "forest", + * "themeCSS": ".node rect { fill: red; }" + *+ */ + 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 => { - assignWithDepth(config, conf); + sanitize(conf); + assignWithDepth(currentConfig, conf); + return getConfig(); }; -export const getConfig = () => config; - -export const reset = conf => { - Object.keys(config).forEach(key => delete config[key]); - assignWithDepth(config, conf, { clobber: true }); +/** + * Obtains the currentConfig + * @returns {*} - the currentConfig + */ +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, getConfig, - reset -}; + reset, + defaultConfig +}); export default configApi; diff --git a/src/config.spec.js b/src/config.spec.js new file mode 100644 index 000000000..5f17ead13 --- /dev/null +++ b/src/config.spec.js @@ -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(); + }); +}); diff --git a/src/diagrams/sequence/parser/sequenceDiagram.jison b/src/diagrams/sequence/parser/sequenceDiagram.jison index 34a82d7bb..677103db3 100644 --- a/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -230,7 +230,7 @@ argDirective ; closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive'); } + : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); } ; %% diff --git a/src/diagrams/sequence/sequenceDb.js b/src/diagrams/sequence/sequenceDb.js index 8869eb50c..e615cb238 100644 --- a/src/diagrams/sequence/sequenceDb.js +++ b/src/diagrams/sequence/sequenceDb.js @@ -1,5 +1,5 @@ -import { logger } from '../../logger'; import mermaidAPI from '../../mermaidAPI'; +import configApi from '../../config'; let prevActor = undefined; let actors = {}; @@ -9,63 +9,9 @@ let title = ''; let titleWrapped = false; let sequenceNumbersEnabled = false; let wrapEnabled = false; -let currentDirective = {}; -export const parseDirective = function(statement, context) { - 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); - 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 parseDirective = function(statement, context, type) { + mermaidAPI.parseDirective(statement, context, type); }; export const addActor = function(id, name, description) { @@ -126,22 +72,8 @@ export const addSignal = function( message = { text: undefined, wrap: undefined }, messageType ) { - logger.debug( - 'Adding message from=' + - idFrom + - ' to=' + - idTo + - ' message=' + - message.text + - ' wrap=' + - message.wrap + - ' type=' + - messageType - ); - if (messageType === LINETYPE.ACTIVE_END) { const cnt = activationCount(idFrom.actor); - logger.debug('Adding message from=', messages, cnt); if (cnt < 1) { // Bail out as there is an activation signal from an inactive participant let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')'); @@ -356,6 +288,7 @@ export default { getActorKeys, getTitle, parseDirective, + getConfig: () => configApi.getConfig().sequence, getTitleWrapped, clear, parseMessage, diff --git a/src/diagrams/sequence/sequenceDiagram.spec.js b/src/diagrams/sequence/sequenceDiagram.spec.js index 25221a98c..69dca8b9e 100644 --- a/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/src/diagrams/sequence/sequenceDiagram.spec.js @@ -23,7 +23,7 @@ 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(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -41,7 +41,7 @@ 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); expect(parser.yy.showSequenceNumbers()).toBe(false); }); 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 Bob-->Alice: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); expect(parser.yy.showSequenceNumbers()).toBe(true); }); 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 Bob-->Alice: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -82,7 +82,7 @@ sequenceDiagram Alice->Bob:Hello Bob, how are - you? Bob-->Alice: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -101,7 +101,7 @@ participant B as Bob A->B:Hello Bob, how are you? B-->A: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(Object.keys(actors)).toEqual(['A', 'B']); @@ -118,7 +118,7 @@ B-->A: I am good thanks!`; sequenceDiagram Alice-xBob:Hello Bob, how are you?`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -133,7 +133,7 @@ Alice-xBob:Hello Bob, how are you?`; sequenceDiagram Alice--xBob:Hello Bob, how are you?`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -148,7 +148,7 @@ Alice--xBob:Hello Bob, how are you?`; sequenceDiagram Alice->>Bob:Hello Bob, how are you?`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); 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() { const str = 'sequenceDiagram\n' + 'Alice-->>Bob:Hello Bob, how are you?'; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -179,7 +179,7 @@ activate Bob Bob-->>Alice:Hello Alice, I'm fine and you? deactivate Bob`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -200,7 +200,7 @@ deactivate Bob`; Alice-->>+Bob:Hello Bob, how are you? Bob-->>- Alice:Hello Alice, I'm fine and you?`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -223,7 +223,7 @@ deactivate Bob`; Bob-->>- Alice:Hello Alice, please meet Carol? Carol->>- Bob:Oh Bob, I'm so happy to be here!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -261,7 +261,7 @@ deactivate Bob`; let error = false; try { - parser.parse(str); + mermaidAPI.parse(str); } catch (e) { console.log(e.hash); error = true; @@ -277,7 +277,7 @@ deactivate Bob`; Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -298,7 +298,7 @@ deactivate Bob`; Bob-->Alice: I am good thanks! `; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -313,7 +313,7 @@ deactivate Bob`; const str = ` 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(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -333,7 +333,7 @@ sequenceDiagram Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -353,7 +353,7 @@ sequenceDiagram Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - parser.parse(str); + mermaidAPI.parse(str); const actors = parser.yy.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -378,7 +378,7 @@ Note right of John: Rational thoughts
- * mermaid.initialize({ - * flowchart:{ - * htmlLabels: false - * } - * }); - *- * - * **Example 2:** - *
- * <script> - * var config = { - * startOnLoad:true, - * flowchart:{ - * useMaxWidth:true, - * htmlLabels:true, - * curve:'cardinal', - * }, - * - * securityLevel:'loose', - * }; - * mermaid.initialize(config); - * </script> - *- * 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**. - *
- * "theme": "forest", - * "themeCSS": ".node rect { fill: red; }" - *- */ - 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) { const graphInit = utils.detectInit(text); if (graphInit) { - initialize(graphInit); - logger.debug('Init ', graphInit); + reinitialize(graphInit); + logger.debug('reinit ', graphInit); } const graphType = utils.detectType(text); let parser; @@ -640,7 +122,7 @@ function parse(text) { parser.parser.yy = journeyDb; break; } - + parser.parser.yy.graphType = graphType; parser.parser.yy.parseError = (str, hash) => { const error = { str, hash }; throw error; @@ -722,7 +204,7 @@ const render = function(id, _txt, cb, container) { } const graphInit = utils.detectInit(txt); if (graphInit) { - initialize(graphInit); + reinitialize(graphInit); assignWithDepth(cnf, getConfig()); } @@ -942,6 +424,79 @@ const render = function(id, _txt, cb, container) { 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) { gitGraphRenderer.setConf(conf.git); flowRenderer.setConf(conf.flowchart); @@ -961,14 +516,20 @@ function updateRendererConfigs(conf) { errorRenderer.setConf(conf.class); } -function initialize(options) { - console.log(`mermaidAPI.initialize: v${pkg.version}`); +function reinitialize(options) { + console.log(`mermaidAPI.reinitialize: v${pkg.version}`, options); // Set default options - if (typeof options === 'object') { - assignWithDepth(config, options); - updateRendererConfigs(config); - } - setConfig(config); + const config = typeof options === 'object' ? setConfig(options) : getSiteConfig(); + updateRendererConfigs(config); + setLogLevel(config.logLevel); + logger.debug('mermaidAPI.reinitialize: ', 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); logger.debug('mermaidAPI.initialize: ', config); } @@ -977,22 +538,30 @@ function initialize(options) { // console.warn('get config') // return config // } - const mermaidAPI = Object.freeze({ render, parse, + parseDirective, initialize, + reinitialize, getConfig, - setConfig, + getSiteConfig, reset: () => { // console.warn('reset'); - configApi.reset(defaultConfig); - assignWithDepth(config, defaultConfig, { clobber: true }); - updateRendererConfigs(config); + configApi.reset(); + const siteConfig = getSiteConfig(); + updateRendererConfigs(siteConfig); }, - defaultConfig + globalReset: () => { + configApi.reset(configApi.defaultConfig); + updateRendererConfigs(getConfig()); + }, + defaultConfig: configApi.defaultConfig }); +setLogLevel(getConfig().logLevel); +configApi.reset(getConfig()); + export default mermaidAPI; /** * ## mermaidAPI configuration defaults diff --git a/src/mermaidAPI.spec.js b/src/mermaidAPI.spec.js index 423ee9d61..e26c6bc11 100644 --- a/src/mermaidAPI.spec.js +++ b/src/mermaidAPI.spec.js @@ -6,7 +6,7 @@ describe('when using mermaidAPI and ', function() { describe('doing initialize ', function() { beforeEach(function() { document.body.innerHTML = ''; - mermaidAPI.reset(); + mermaidAPI.globalReset(); }); it('should copy a literal into the configuration', function() { @@ -30,11 +30,9 @@ describe('when using mermaidAPI and ', function() { mermaidAPI.initialize({ testObject: object }); let config = mermaidAPI.getConfig(); - console.log('1:', config); expect(config.testObject.test1).toBe(1); mermaidAPI.initialize({ testObject: { test3: true } }); config = mermaidAPI.getConfig(); - console.log(config); expect(config.testObject.test1).toBe(1); 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() { let config = { - logLevel: 0 + logLevel: 0, + securityLevel: 'loose' }; mermaidAPI.initialize(config); expect(mermaidAPI.getConfig().logLevel).toBe(0); - mermaidAPI.reset(); + expect(mermaidAPI.getConfig().securityLevel).toBe('loose'); + mermaidAPI.globalReset(); 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() { let config = assignWithDepth({}, mermaidAPI.defaultConfig); assignWithDepth(config, { logLevel: 0 }); @@ -65,7 +100,7 @@ describe('when using mermaidAPI and ', function() { it('should prevent changes to global defaults (direct)', function() { let error = { message: '' }; try { - mermaidAPI.defaultConfig.logLevel = 0; + mermaidAPI.defaultConfig['logLevel'] = 0; } catch(e) { error = e; } diff --git a/src/utils.js b/src/utils.js index 968883888..a140fe9da 100644 --- a/src/utils.js +++ b/src/utils.js @@ -15,6 +15,7 @@ import { import { logger } from './logger'; import { sanitizeUrl } from '@braintree/sanitize-url'; import common from './diagrams/common/common'; +import cryptoRandomString from 'crypto-random-string'; // Effectively an enum of the supported curve types, accessible by name const d3CurveTypes = { @@ -396,6 +397,10 @@ export const generateId = () => { ); }; +export const random = options => { + return cryptoRandomString(options); +}; + /** * @function assignWithDepth * 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') ) { if (dst[key] === undefined) { - dst[key] = {}; + dst[key] = Array.isArray(src[key]) ? [] : {}; } dst[key] = assignWithDepth(dst[key], src[key], { depth: depth - 1, clobber }); } 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: '