#931 Last code standard fixes

This commit is contained in:
knsv
2019-09-12 12:59:13 -07:00
parent 0e8164d805
commit 7e5802e799
4 changed files with 497 additions and 452 deletions

View File

@@ -2,10 +2,10 @@
* Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid functionality and to render * Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid functionality and to render
* the diagrams to svg code. * the diagrams to svg code.
*/ */
import he from 'he' import he from 'he';
import mermaidAPI from './mermaidAPI' import mermaidAPI from './mermaidAPI';
import { logger } from './logger' import { logger } from './logger';
/** /**
* ## init * ## init
@@ -28,126 +28,142 @@ import { logger } from './logger'
* Renders the mermaid diagrams * Renders the mermaid diagrams
* @param nodes a css selector or an array of nodes * @param nodes a css selector or an array of nodes
*/ */
const init = function () { const init = function() {
const conf = mermaidAPI.getConfig() const conf = mermaidAPI.getConfig();
logger.debug('Starting rendering diagrams') logger.debug('Starting rendering diagrams');
let nodes let nodes;
if (arguments.length >= 2) { if (arguments.length >= 2) {
/*! sequence config was passed as #1 */ /*! sequence config was passed as #1 */
if (typeof arguments[0] !== 'undefined') { if (typeof arguments[0] !== 'undefined') {
mermaid.sequenceConfig = arguments[0] mermaid.sequenceConfig = arguments[0];
} }
nodes = arguments[1] nodes = arguments[1];
} else { } else {
nodes = arguments[0] nodes = arguments[0];
} }
// if last argument is a function this is the callback function // if last argument is a function this is the callback function
let callback let callback;
if (typeof arguments[arguments.length - 1] === 'function') { if (typeof arguments[arguments.length - 1] === 'function') {
callback = arguments[arguments.length - 1] callback = arguments[arguments.length - 1];
logger.debug('Callback function found') logger.debug('Callback function found');
} else { } else {
if (typeof conf.mermaid !== 'undefined') { if (typeof conf.mermaid !== 'undefined') {
if (typeof conf.mermaid.callback === 'function') { if (typeof conf.mermaid.callback === 'function') {
callback = conf.mermaid.callback callback = conf.mermaid.callback;
logger.debug('Callback function found') logger.debug('Callback function found');
} else { } else {
logger.debug('No Callback function found') logger.debug('No Callback function found');
} }
} }
} }
nodes = nodes === undefined ? document.querySelectorAll('.mermaid') nodes =
: typeof nodes === 'string' ? document.querySelectorAll(nodes) nodes === undefined
: nodes instanceof window.Node ? [nodes] ? document.querySelectorAll('.mermaid')
: nodes // Last case - sequence config was passed pick next : typeof nodes === 'string'
? document.querySelectorAll(nodes)
: nodes instanceof window.Node
? [nodes]
: nodes; // Last case - sequence config was passed pick next
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.initialize({ startOnLoad: mermaid.startOnLoad }) mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad });
} }
if (typeof mermaid.ganttConfig !== 'undefined') { if (typeof mermaid.ganttConfig !== 'undefined') {
mermaidAPI.initialize({ gantt: mermaid.ganttConfig }) mermaidAPI.initialize({ gantt: mermaid.ganttConfig });
} }
let txt let txt;
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const element = nodes[i] const element = nodes[i];
/*! Check if previously processed */ /*! Check if previously processed */
if (!element.getAttribute('data-processed')) { if (!element.getAttribute('data-processed')) {
element.setAttribute('data-processed', true) element.setAttribute('data-processed', true);
} else { } else {
continue continue;
} }
const id = `mermaid-${Date.now()}` const id = `mermaid-${Date.now()}`;
// Fetch the graph definition including tags // Fetch the graph definition including tags
txt = element.innerHTML txt = element.innerHTML;
// transforms the html to pure text // transforms the html to pure text
txt = he.decode(txt).trim().replace(/<br>/ig, '<br/>') txt = he
.decode(txt)
.trim()
.replace(/<br>/gi, '<br/>');
mermaidAPI.render(id, txt, (svgCode, bindFunctions) => { mermaidAPI.render(
element.innerHTML = svgCode id,
if (typeof callback !== 'undefined') { txt,
callback(id) (svgCode, bindFunctions) => {
} element.innerHTML = svgCode;
if (bindFunctions) bindFunctions(element) if (typeof callback !== 'undefined') {
}, element) callback(id);
}
if (bindFunctions) bindFunctions(element);
},
element
);
} }
} };
const initialize = function (config) { const initialize = function(config) {
logger.debug('Initializing mermaid ') logger.debug('Initializing mermaid ');
if (typeof config.mermaid !== 'undefined') { if (typeof config.mermaid !== 'undefined') {
if (typeof config.mermaid.startOnLoad !== 'undefined') { if (typeof config.mermaid.startOnLoad !== 'undefined') {
mermaid.startOnLoad = config.mermaid.startOnLoad mermaid.startOnLoad = config.mermaid.startOnLoad;
} }
if (typeof config.mermaid.htmlLabels !== 'undefined') { if (typeof config.mermaid.htmlLabels !== 'undefined') {
mermaid.htmlLabels = config.mermaid.htmlLabels mermaid.htmlLabels = config.mermaid.htmlLabels;
} }
} }
mermaidAPI.initialize(config) mermaidAPI.initialize(config);
} };
/** /**
* ##contentLoaded * ##contentLoaded
* Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and * Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and
* calls init for rendering the mermaid diagrams on the page. * calls init for rendering the mermaid diagrams on the page.
*/ */
const contentLoaded = function () { const contentLoaded = function() {
let config let config;
if (mermaid.startOnLoad) { if (mermaid.startOnLoad) {
// No config found, do check API config // No config found, do check API config
config = mermaidAPI.getConfig() config = mermaidAPI.getConfig();
if (config.startOnLoad) { if (config.startOnLoad) {
mermaid.init() mermaid.init();
} }
} else { } else {
if (typeof mermaid.startOnLoad === 'undefined') { if (typeof mermaid.startOnLoad === 'undefined') {
logger.debug('In start, no config') logger.debug('In start, no config');
config = mermaidAPI.getConfig() config = mermaidAPI.getConfig();
if (config.startOnLoad) { if (config.startOnLoad) {
mermaid.init() mermaid.init();
} }
} }
} }
} };
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
/*! /*!
* Wait for document loaded before starting the execution * Wait for document loaded before starting the execution
*/ */
window.addEventListener('load', function () { window.addEventListener(
contentLoaded() 'load',
}, false) function() {
contentLoaded();
},
false
);
} }
const mermaid = { const mermaid = {
@@ -162,6 +178,6 @@ const mermaid = {
initialize, initialize,
contentLoaded contentLoaded
} };
export default mermaid export default mermaid;

View File

@@ -1,195 +1,200 @@
/* eslint-env jasmine */ /* eslint-env jasmine */
import mermaid from './mermaid' import mermaid from './mermaid';
import flowDb from './diagrams/flowchart/flowDb' import flowDb from './diagrams/flowchart/flowDb';
import flowParser from './diagrams/flowchart/parser/flow' import flowParser from './diagrams/flowchart/parser/flow';
import flowRenderer from './diagrams/flowchart/flowRenderer' import flowRenderer from './diagrams/flowchart/flowRenderer';
describe('when using mermaid and ', function () { describe('when using mermaid and ', function() {
describe('when detecting chart type ', function () { describe('when detecting chart type ', function() {
it('should not start rendering with mermaid.startOnLoad set to false', function () { it('should not start rendering with mermaid.startOnLoad set to false', function() {
mermaid.startOnLoad = false mermaid.startOnLoad = false;
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(mermaid, 'init') spyOn(mermaid, 'init');
mermaid.contentLoaded() mermaid.contentLoaded();
expect(mermaid.init).not.toHaveBeenCalled() expect(mermaid.init).not.toHaveBeenCalled();
}) });
it('should start rendering with both startOnLoad set', function () { it('should start rendering with both startOnLoad set', function() {
mermaid.startOnLoad = true mermaid.startOnLoad = true;
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(mermaid, 'init') spyOn(mermaid, 'init');
mermaid.contentLoaded() mermaid.contentLoaded();
expect(mermaid.init).toHaveBeenCalled() expect(mermaid.init).toHaveBeenCalled();
}) });
it('should start rendering with mermaid.startOnLoad', function () { it('should start rendering with mermaid.startOnLoad', function() {
mermaid.startOnLoad = true mermaid.startOnLoad = true;
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(mermaid, 'init') spyOn(mermaid, 'init');
mermaid.contentLoaded() mermaid.contentLoaded();
expect(mermaid.init).toHaveBeenCalled() expect(mermaid.init).toHaveBeenCalled();
}) });
it('should start rendering as a default with no changes performed', function () { it('should start rendering as a default with no changes performed', function() {
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(mermaid, 'init') spyOn(mermaid, 'init');
mermaid.contentLoaded() mermaid.contentLoaded();
expect(mermaid.init).toHaveBeenCalled() expect(mermaid.init).toHaveBeenCalled();
}) });
}) });
describe('when calling addEdges ', function () { describe('when calling addEdges ', function() {
beforeEach(function () { beforeEach(function() {
flowParser.parser.yy = flowDb flowParser.parser.yy = flowDb;
flowDb.clear() flowDb.clear();
}) });
it('it should handle edges with text', function () { it('it should handle edges with text', function() {
flowParser.parser.parse('graph TD;A-->|text ex|B;') flowParser.parser.parse('graph TD;A-->|text ex|B;');
flowParser.parser.yy.getVertices() flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges() const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('normal') expect(options.arrowhead).toBe('normal');
expect(options.label.match('text ex')).toBeTruthy() expect(options.label.match('text ex')).toBeTruthy();
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should handle edges without text', function () { it('should handle edges without text', function() {
flowParser.parser.parse('graph TD;A-->B;') flowParser.parser.parse('graph TD;A-->B;');
flowParser.parser.yy.getVertices() flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges() const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('normal') expect(options.arrowhead).toBe('normal');
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should handle open-ended edges', function () { it('should handle open-ended edges', function() {
flowParser.parser.parse('graph TD;A---B;') flowParser.parser.parse('graph TD;A---B;');
flowParser.parser.yy.getVertices() flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges() const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('none') expect(options.arrowhead).toBe('none');
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should handle edges with styles defined', function () { it('should handle edges with styles defined', function() {
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;') flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
flowParser.parser.yy.getVertices() flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges() const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('none') expect(options.arrowhead).toBe('none');
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;') expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should handle edges with interpolation defined', function () { it('should handle edges with interpolation defined', function() {
flowParser.parser.parse('graph TD;A---B; linkStyle 0 interpolate basis') flowParser.parser.parse('graph TD;A---B; linkStyle 0 interpolate basis');
flowParser.parser.yy.getVertices() flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges() const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('none') expect(options.arrowhead).toBe('none');
expect(options.curve).toBe('basis') // mocked as string expect(options.curve).toBe('basis'); // mocked as string
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should handle edges with text and styles defined', function () { it('should handle edges with text and styles defined', function() {
flowParser.parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;') flowParser.parser.parse(
flowParser.parser.yy.getVertices() 'graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;'
const edges = flowParser.parser.yy.getEdges() );
flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('none') expect(options.arrowhead).toBe('none');
expect(options.label.match('the text')).toBeTruthy() expect(options.label.match('the text')).toBeTruthy();
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;') expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should set fill to "none" by default when handling edges', function () { it('should set fill to "none" by default when handling edges', function() {
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;') flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
flowParser.parser.yy.getVertices() flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges() const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('none') expect(options.arrowhead).toBe('none');
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;') expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
it('should not set fill to none if fill is set in linkStyle', function () { it('should not set fill to none if fill is set in linkStyle', function() {
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;') flowParser.parser.parse(
flowParser.parser.yy.getVertices() 'graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;'
const edges = flowParser.parser.yy.getEdges() );
flowParser.parser.yy.getVertices();
const edges = flowParser.parser.yy.getEdges();
const mockG = { const mockG = {
setEdge: function (start, end, options) { setEdge: function(start, end, options) {
expect(start).toBe('A') expect(start).toBe('A');
expect(end).toBe('B') expect(end).toBe('B');
expect(options.arrowhead).toBe('none') expect(options.arrowhead).toBe('none');
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;') expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;');
} }
} };
flowRenderer.addEdges(edges, mockG) flowRenderer.addEdges(edges, mockG);
}) });
}) });
describe('checking validity of input ', function () { describe('checking validity of input ', function() {
it('it should throw for an invalid definiton', function () { it('it should throw for an invalid definiton', function() {
expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow() expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow();
}) });
it('it should not throw for a valid flow definition', function () { it('it should not throw for a valid flow definition', function() {
expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow() expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
}) });
it('it should throw for an invalid flow definition', function () { it('it should throw for an invalid flow definition', function() {
expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow() expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow();
}) });
it('it should not throw for a valid sequenceDiagram definition', function () { it('it should not throw for a valid sequenceDiagram definition', function() {
const text = 'sequenceDiagram\n' + const text =
'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n\n' + 'Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' + '%% Comment\n' +
'Note right of Bob: Bob thinks\n' + 'Note right of Bob: Bob thinks\n' +
@@ -197,12 +202,13 @@ describe('when using mermaid and ', function () {
'Bob-->Alice: I am good thanks!\n' + 'Bob-->Alice: I am good thanks!\n' +
'else isSick\n' + 'else isSick\n' +
'Bob-->Alice: Feel sick...\n' + 'Bob-->Alice: Feel sick...\n' +
'end' 'end';
expect(() => mermaid.parse(text)).not.toThrow() expect(() => mermaid.parse(text)).not.toThrow();
}) });
it('it should throw for an invalid sequenceDiagram definition', function () { it('it should throw for an invalid sequenceDiagram definition', function() {
const text = 'sequenceDiagram\n' + const text =
'sequenceDiagram\n' +
'Alice:->Bob: Hello Bob, how are you?\n\n' + 'Alice:->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' + '%% Comment\n' +
'Note right of Bob: Bob thinks\n' + 'Note right of Bob: Bob thinks\n' +
@@ -210,8 +216,8 @@ describe('when using mermaid and ', function () {
'Bob-->Alice: I am good thanks!\n' + 'Bob-->Alice: I am good thanks!\n' +
'else isSick\n' + 'else isSick\n' +
'Bob-->Alice: Feel sick...\n' + 'Bob-->Alice: Feel sick...\n' +
'end' 'end';
expect(() => mermaid.parse(text)).toThrow() expect(() => mermaid.parse(text)).toThrow();
}) });
}) });
}) });

View File

@@ -9,38 +9,38 @@
* 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.
* *
* @name mermaidAPI * @name mermaidAPI
*/ */
import * as d3 from 'd3' import * as d3 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 } from './config';
import { logger, setLogLevel } from './logger' import { logger, setLogLevel } from './logger';
import utils from './utils' import utils from './utils';
import flowRenderer from './diagrams/flowchart/flowRenderer' import flowRenderer from './diagrams/flowchart/flowRenderer';
import flowParser from './diagrams/flowchart/parser/flow' import flowParser from './diagrams/flowchart/parser/flow';
import flowDb from './diagrams/flowchart/flowDb' import flowDb from './diagrams/flowchart/flowDb';
import sequenceRenderer from './diagrams/sequence/sequenceRenderer' import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram' import sequenceParser from './diagrams/sequence/parser/sequenceDiagram';
import sequenceDb from './diagrams/sequence/sequenceDb' import sequenceDb from './diagrams/sequence/sequenceDb';
import ganttRenderer from './diagrams/gantt/ganttRenderer' import ganttRenderer from './diagrams/gantt/ganttRenderer';
import ganttParser from './diagrams/gantt/parser/gantt' import ganttParser from './diagrams/gantt/parser/gantt';
import ganttDb from './diagrams/gantt/ganttDb' import ganttDb from './diagrams/gantt/ganttDb';
import classRenderer from './diagrams/class/classRenderer' import classRenderer from './diagrams/class/classRenderer';
import classParser from './diagrams/class/parser/classDiagram' import classParser from './diagrams/class/parser/classDiagram';
import classDb from './diagrams/class/classDb' import classDb from './diagrams/class/classDb';
import gitGraphRenderer from './diagrams/git/gitGraphRenderer' import gitGraphRenderer from './diagrams/git/gitGraphRenderer';
import gitGraphParser from './diagrams/git/parser/gitGraph' import gitGraphParser from './diagrams/git/parser/gitGraph';
import gitGraphAst from './diagrams/git/gitGraphAst' import gitGraphAst from './diagrams/git/gitGraphAst';
import infoRenderer from './diagrams/info/infoRenderer' import infoRenderer from './diagrams/info/infoRenderer';
import infoParser from './diagrams/info/parser/info' import infoParser from './diagrams/info/parser/info';
import infoDb from './diagrams/info/infoDb' import infoDb from './diagrams/info/infoDb';
import pieRenderer from './diagrams/pie/pieRenderer' import pieRenderer from './diagrams/pie/pieRenderer';
import pieParser from './diagrams/pie/parser/pie' import pieParser from './diagrams/pie/parser/pie';
import pieDb from './diagrams/pie/pieDb' import pieDb from './diagrams/pie/pieDb';
const themes = {} const themes = {};
for (const themeName of ['default', 'forest', 'dark', 'neutral']) { for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
themes[themeName] = require(`./themes/${themeName}/index.scss`) themes[themeName] = require(`./themes/${themeName}/index.scss`);
} }
/** /**
@@ -75,22 +75,21 @@ for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
* @name Configuration * @name Configuration
*/ */
const config = { const config = {
/** theme , the CSS style sheet /** theme , the CSS style sheet
* *
* **theme** - Choose one of the built-in themes: * **theme** - Choose one of the built-in themes:
* * default * * default
* * forest * * forest
* * dark * * dark
* * neutral. * * neutral.
* To disable any pre-defined mermaid theme, use "null". * To disable any pre-defined mermaid theme, use "null".
* *
* **themeCSS** - Use your own CSS. This overrides **theme**. * **themeCSS** - Use your own CSS. This overrides **theme**.
* <pre> * <pre>
* "theme": "forest", * "theme": "forest",
* "themeCSS": ".node rect { fill: red; }" * "themeCSS": ".node rect { fill: red; }"
* </pre> * </pre>
*/ */
theme: 'default', theme: 'default',
themeCSS: undefined, themeCSS: undefined,
@@ -149,7 +148,6 @@ const config = {
* The object containing configurations specific for sequence diagrams * The object containing configurations specific for sequence diagrams
*/ */
sequence: { sequence: {
/** /**
* margin to the right and left of the sequence diagram. * margin to the right and left of the sequence diagram.
* **Default value 50**. * **Default value 50**.
@@ -234,7 +232,6 @@ const config = {
* **Default value false**. * **Default value false**.
*/ */
showSequenceNumbers: false showSequenceNumbers: false
}, },
/** /**
@@ -303,100 +300,100 @@ const config = {
}, },
class: {}, class: {},
git: {} git: {}
} };
setLogLevel(config.logLevel) setLogLevel(config.logLevel);
setConfig(config) setConfig(config);
function parse (text) { function parse(text) {
const graphType = utils.detectType(text) const graphType = utils.detectType(text);
let parser let parser;
logger.debug('Type ' + graphType) logger.debug('Type ' + graphType);
switch (graphType) { switch (graphType) {
case 'git': case 'git':
parser = gitGraphParser parser = gitGraphParser;
parser.parser.yy = gitGraphAst parser.parser.yy = gitGraphAst;
break break;
case 'flowchart': case 'flowchart':
parser = flowParser parser = flowParser;
parser.parser.yy = flowDb parser.parser.yy = flowDb;
break break;
case 'sequence': case 'sequence':
parser = sequenceParser parser = sequenceParser;
parser.parser.yy = sequenceDb parser.parser.yy = sequenceDb;
break break;
case 'gantt': case 'gantt':
parser = ganttParser parser = ganttParser;
parser.parser.yy = ganttDb parser.parser.yy = ganttDb;
break break;
case 'class': case 'class':
parser = classParser parser = classParser;
parser.parser.yy = classDb parser.parser.yy = classDb;
break break;
case 'info': case 'info':
logger.debug('info info info') logger.debug('info info info');
console.warn('In API', pkg.version) console.warn('In API', pkg.version);
parser = infoParser parser = infoParser;
parser.parser.yy = infoDb parser.parser.yy = infoDb;
break break;
case 'pie': case 'pie':
logger.debug('pie') logger.debug('pie');
parser = pieParser parser = pieParser;
parser.parser.yy = pieDb parser.parser.yy = pieDb;
break break;
} }
parser.parser.yy.parseError = (str, hash) => { parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash } const error = { str, hash };
throw error throw error;
} };
parser.parse(text) parser.parse(text);
} }
export const encodeEntities = function (text) { export const encodeEntities = function(text) {
let txt = text let txt = text;
txt = txt.replace(/style.*:\S*#.*;/g, function (s) { txt = txt.replace(/style.*:\S*#.*;/g, function(s) {
const innerTxt = s.substring(0, s.length - 1) const innerTxt = s.substring(0, s.length - 1);
return innerTxt return innerTxt;
}) });
txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) { txt = txt.replace(/classDef.*:\S*#.*;/g, function(s) {
const innerTxt = s.substring(0, s.length - 1) const innerTxt = s.substring(0, s.length - 1);
return innerTxt return innerTxt;
}) });
txt = txt.replace(/#\w+;/g, function (s) { txt = txt.replace(/#\w+;/g, function(s) {
const innerTxt = s.substring(1, s.length - 1) const innerTxt = s.substring(1, s.length - 1);
const isInt = /^\+?\d+$/.test(innerTxt) const isInt = /^\+?\d+$/.test(innerTxt);
if (isInt) { if (isInt) {
return 'fl°°' + innerTxt + '¶ß' return 'fl°°' + innerTxt + '¶ß';
} else { } else {
return 'fl°' + innerTxt + '¶ß' return 'fl°' + innerTxt + '¶ß';
} }
}) });
return txt return txt;
} };
export const decodeEntities = function (text) { export const decodeEntities = function(text) {
let txt = text let txt = text;
txt = txt.replace(/fl°°/g, function () { txt = txt.replace(/fl°°/g, function() {
return '&#' return '&#';
}) });
txt = txt.replace(/fl°/g, function () { txt = txt.replace(/fl°/g, function() {
return '&' return '&';
}) });
txt = txt.replace(/¶ß/g, function () { txt = txt.replace(/¶ß/g, function() {
return ';' return ';';
}) });
return txt return txt;
} };
/** /**
* 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.
* *
@@ -419,183 +416,209 @@ export const decodeEntities = function (text) {
* provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is * provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
* completed. * completed.
*/ */
const render = function (id, txt, cb, container) { const render = function(id, txt, cb, container) {
if (typeof container !== 'undefined') { if (typeof container !== 'undefined') {
container.innerHTML = '' container.innerHTML = '';
d3.select(container).append('div') d3.select(container)
.append('div')
.attr('id', 'd' + id) .attr('id', 'd' + id)
.append('svg') .append('svg')
.attr('id', id) .attr('id', id)
.attr('width', '100%') .attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg') .attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g') .append('g');
} else { } else {
const element = document.querySelector('#' + 'd' + id) const element = document.querySelector('#' + 'd' + id);
if (element) { if (element) {
element.innerHTML = '' element.innerHTML = '';
} }
d3.select('body').append('div') d3.select('body')
.append('div')
.attr('id', 'd' + id) .attr('id', 'd' + id)
.append('svg') .append('svg')
.attr('id', id) .attr('id', id)
.attr('width', '100%') .attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg') .attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g') .append('g');
} }
window.txt = txt window.txt = txt;
txt = encodeEntities(txt) txt = encodeEntities(txt);
const element = d3.select('#d' + id).node() const element = d3.select('#d' + id).node();
const graphType = utils.detectType(txt) const graphType = utils.detectType(txt);
// insert inline style into svg // insert inline style into svg
const svg = element.firstChild const svg = element.firstChild;
const firstChild = svg.firstChild const firstChild = svg.firstChild;
// pre-defined theme // pre-defined theme
let style = themes[config.theme] let style = themes[config.theme];
if (style === undefined) { if (style === undefined) {
style = '' style = '';
} }
// user provided theme CSS // user provided theme CSS
if (config.themeCSS !== undefined) { if (config.themeCSS !== undefined) {
style += `\n${config.themeCSS}` style += `\n${config.themeCSS}`;
} }
// classDef // classDef
if (graphType === 'flowchart') { if (graphType === 'flowchart') {
const classes = flowRenderer.getClasses(txt) const classes = flowRenderer.getClasses(txt);
for (const className in classes) { for (const className in classes) {
style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }` style += `\n.${className} > * { ${classes[className].styles.join(
' !important; '
)} !important; }`;
} }
} }
const style1 = document.createElement('style') const style1 = document.createElement('style');
style1.innerHTML = scope(style, `#${id}`) style1.innerHTML = scope(style, `#${id}`);
svg.insertBefore(style1, firstChild) svg.insertBefore(style1, firstChild);
const style2 = document.createElement('style') const style2 = document.createElement('style');
const cs = window.getComputedStyle(svg) const cs = window.getComputedStyle(svg);
style2.innerHTML = `#${id} { style2.innerHTML = `#${id} {
color: ${cs.color}; color: ${cs.color};
font: ${cs.font}; font: ${cs.font};
}` }`;
svg.insertBefore(style2, firstChild) svg.insertBefore(style2, firstChild);
switch (graphType) { switch (graphType) {
case 'git': case 'git':
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
gitGraphRenderer.setConf(config.git) gitGraphRenderer.setConf(config.git);
gitGraphRenderer.draw(txt, id, false) gitGraphRenderer.draw(txt, id, false);
break break;
case 'flowchart': case 'flowchart':
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
flowRenderer.setConf(config.flowchart) flowRenderer.setConf(config.flowchart);
flowRenderer.draw(txt, id, false) flowRenderer.draw(txt, id, false);
break break;
case 'sequence': case 'sequence':
config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
if (config.sequenceDiagram) { // backwards compatibility if (config.sequenceDiagram) {
sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram)) // backwards compatibility
console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.') sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram));
console.error(
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
);
} else { } else {
sequenceRenderer.setConf(config.sequence) sequenceRenderer.setConf(config.sequence);
} }
sequenceRenderer.draw(txt, id) sequenceRenderer.draw(txt, id);
break break;
case 'gantt': case 'gantt':
config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
ganttRenderer.setConf(config.gantt) ganttRenderer.setConf(config.gantt);
ganttRenderer.draw(txt, id) ganttRenderer.draw(txt, id);
break break;
case 'class': case 'class':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
classRenderer.setConf(config.class) classRenderer.setConf(config.class);
classRenderer.draw(txt, id) classRenderer.draw(txt, id);
break break;
case 'info': case 'info':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
infoRenderer.setConf(config.class) infoRenderer.setConf(config.class);
infoRenderer.draw(txt, id, pkg.version) infoRenderer.draw(txt, id, pkg.version);
break break;
case 'pie': case 'pie':
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
pieRenderer.setConf(config.class) pieRenderer.setConf(config.class);
pieRenderer.draw(txt, id, pkg.version) pieRenderer.draw(txt, id, pkg.version);
break break;
} }
d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml') d3.select(`[id="${id}"]`)
.selectAll('foreignobject > *')
.attr('xmlns', 'http://www.w3.org/1999/xhtml');
let url = '' let url = '';
if (config.arrowMarkerAbsolute) { if (config.arrowMarkerAbsolute) {
url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search url =
url = url.replace(/\(/g, '\\(') window.location.protocol +
url = url.replace(/\)/g, '\\)') '//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
} }
// Fix for when the base tag is used // Fix for when the base tag is used
let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g') let svgCode = d3
.select('#d' + id)
.node()
.innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g');
svgCode = decodeEntities(svgCode) svgCode = decodeEntities(svgCode);
if (typeof cb !== 'undefined') { if (typeof cb !== 'undefined') {
switch (graphType) { switch (graphType) {
case 'flowchart': case 'flowchart':
cb(svgCode, flowDb.bindFunctions) cb(svgCode, flowDb.bindFunctions);
break break;
case 'gantt': case 'gantt':
cb(svgCode, ganttDb.bindFunctions) cb(svgCode, ganttDb.bindFunctions);
break break;
default: default:
cb(svgCode) cb(svgCode);
} }
} else { } else {
logger.debug('CB = undefined!') logger.debug('CB = undefined!');
} }
const node = d3.select('#d' + id).node() const node = d3.select('#d' + id).node();
if (node !== null && typeof node.remove === 'function') { if (node !== null && typeof node.remove === 'function') {
d3.select('#d' + id).node().remove() d3.select('#d' + id)
.node()
.remove();
} }
return svgCode return svgCode;
} };
const setConf = function (cnf) { const setConf = function(cnf) {
// Top level initially mermaid, gflow, sequenceDiagram and gantt // Top level initially mermaid, gflow, sequenceDiagram and gantt
const lvl1Keys = Object.keys(cnf) const lvl1Keys = Object.keys(cnf);
for (let i = 0; i < lvl1Keys.length; i++) { for (let i = 0; i < lvl1Keys.length; i++) {
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) { if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]) const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
for (let j = 0; j < lvl2Keys.length; j++) { for (let j = 0; j < lvl2Keys.length; j++) {
logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]) logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]);
if (typeof config[lvl1Keys[i]] === 'undefined') { if (typeof config[lvl1Keys[i]] === 'undefined') {
config[lvl1Keys[i]] = {} config[lvl1Keys[i]] = {};
} }
logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]]) logger.debug(
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]] 'Setting config: ' +
lvl1Keys[i] +
' ' +
lvl2Keys[j] +
' to ' +
cnf[lvl1Keys[i]][lvl2Keys[j]]
);
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
} }
} else { } else {
config[lvl1Keys[i]] = cnf[lvl1Keys[i]] config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
} }
} }
} };
function initialize (options) { function initialize(options) {
logger.debug('Initializing mermaidAPI ', pkg.version) logger.debug('Initializing mermaidAPI ', pkg.version);
// Update default config with options supplied at initialization // Update default config with options supplied at initialization
if (typeof options === 'object') { if (typeof options === 'object') {
setConf(options) setConf(options);
} }
setConfig(config) setConfig(config);
setLogLevel(config.logLevel) setLogLevel(config.logLevel);
} }
// function getConfig () { // function getConfig () {
@@ -608,9 +631,9 @@ const mermaidAPI = {
parse, parse,
initialize, initialize,
getConfig getConfig
} };
export default mermaidAPI export default mermaidAPI;
/** /**
* ## mermaidAPI configuration defaults * ## mermaidAPI configuration defaults
* <pre> * <pre>

View File

@@ -1,45 +1,45 @@
/* eslint-env jasmine */ /* eslint-env jasmine */
import mermaidAPI from './mermaidAPI' import mermaidAPI from './mermaidAPI';
describe('when using mermaidAPI and ', function () { describe('when using mermaidAPI and ', function() {
describe('doing initialize ', function () { describe('doing initialize ', function() {
beforeEach(function () { beforeEach(function() {
document.body.innerHTML = '' document.body.innerHTML = '';
}) });
it('should copy a literal into the configuration', function () { it('should copy a literal into the configuration', function() {
const orgConfig = mermaidAPI.getConfig() const orgConfig = mermaidAPI.getConfig();
expect(orgConfig.testLiteral).toBe(undefined) expect(orgConfig.testLiteral).toBe(undefined);
mermaidAPI.initialize({ 'testLiteral': true }) mermaidAPI.initialize({ testLiteral: true });
const config = mermaidAPI.getConfig() const config = mermaidAPI.getConfig();
expect(config.testLiteral).toBe(true) expect(config.testLiteral).toBe(true);
}) });
it('should copy a an object into the configuration', function () { it('should copy a an object into the configuration', function() {
const orgConfig = mermaidAPI.getConfig() const orgConfig = mermaidAPI.getConfig();
expect(orgConfig.testObject).toBe(undefined) expect(orgConfig.testObject).toBe(undefined);
const object = { const object = {
test1: 1, test1: 1,
test2: false test2: false
} };
mermaidAPI.initialize({ 'testObject': object }) mermaidAPI.initialize({ testObject: object });
mermaidAPI.initialize({ 'testObject': { 'test3': true } }) mermaidAPI.initialize({ testObject: { test3: true } });
const config = mermaidAPI.getConfig() const config = mermaidAPI.getConfig();
expect(config.testObject.test1).toBe(1) expect(config.testObject.test1).toBe(1);
expect(config.testObject.test2).toBe(false) expect(config.testObject.test2).toBe(false);
expect(config.testObject.test3).toBe(true) expect(config.testObject.test3).toBe(true);
}) });
}) });
describe('checking validity of input ', function () { describe('checking validity of input ', function() {
it('it should throw for an invalid definiton', function () { it('it should throw for an invalid definiton', function() {
expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow() expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow();
}) });
it('it should not throw for a valid definiton', function () { it('it should not throw for a valid definiton', function() {
expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow() expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
}) });
}) });
}) });