1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | import * as d3 from 'd3'
|
15 | import scope from 'scope-css'
|
16 |
|
17 | import { logger, setLogLevel } from './logger'
|
18 | import utils from './utils'
|
19 | import flowRenderer from './diagrams/flowchart/flowRenderer'
|
20 | import flowParser from './diagrams/flowchart/parser/flow'
|
21 | import flowDb from './diagrams/flowchart/flowDb'
|
22 | import sequenceRenderer from './diagrams/sequence/sequenceRenderer'
|
23 | import sequenceParser from './diagrams/sequence/parser/sequenceDiagram'
|
24 | import sequenceDb from './diagrams/sequence/sequenceDb'
|
25 | import ganttRenderer from './diagrams/gantt/ganttRenderer'
|
26 | import ganttParser from './diagrams/gantt/parser/gantt'
|
27 | import ganttDb from './diagrams/gantt/ganttDb'
|
28 | import classRenderer from './diagrams/class/classRenderer'
|
29 | import classParser from './diagrams/class/parser/classDiagram'
|
30 | import classDb from './diagrams/class/classDb'
|
31 | import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
|
32 | import gitGraphParser from './diagrams/git/parser/gitGraph'
|
33 | import gitGraphAst from './diagrams/git/gitGraphAst'
|
34 |
|
35 | const themes = {}
|
36 | for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
|
37 | themes[themeName] = require(`./themes/${themeName}/index.scss`)
|
38 | }
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | const config = {
|
52 |
|
53 | |
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | theme: 'default',
|
64 | themeCSS: undefined,
|
65 |
|
66 | |
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | logLevel: 5,
|
75 |
|
76 | |
77 |
|
78 |
|
79 | startOnLoad: true,
|
80 |
|
81 | |
82 |
|
83 |
|
84 |
|
85 | arrowMarkerAbsolute: false,
|
86 |
|
87 | |
88 |
|
89 |
|
90 |
|
91 | flowchart: {
|
92 | |
93 |
|
94 |
|
95 |
|
96 | htmlLabels: true,
|
97 |
|
98 | curve: 'linear'
|
99 | },
|
100 |
|
101 | |
102 |
|
103 |
|
104 |
|
105 | sequence: {
|
106 |
|
107 | |
108 |
|
109 |
|
110 | diagramMarginX: 50,
|
111 |
|
112 | |
113 |
|
114 |
|
115 | diagramMarginY: 10,
|
116 |
|
117 | |
118 |
|
119 |
|
120 | actorMargin: 50,
|
121 |
|
122 | |
123 |
|
124 |
|
125 | width: 150,
|
126 |
|
127 | |
128 |
|
129 |
|
130 | height: 65,
|
131 |
|
132 | |
133 |
|
134 |
|
135 | boxMargin: 10,
|
136 |
|
137 | |
138 |
|
139 |
|
140 | boxTextMargin: 5,
|
141 |
|
142 | |
143 |
|
144 |
|
145 | noteMargin: 10,
|
146 |
|
147 | |
148 |
|
149 |
|
150 | messageMargin: 35,
|
151 |
|
152 | |
153 |
|
154 |
|
155 | mirrorActors: true,
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 | bottomMarginAdj: 1,
|
162 |
|
163 | |
164 |
|
165 |
|
166 |
|
167 | useMaxWidth: true,
|
168 |
|
169 | |
170 |
|
171 |
|
172 | rightAngles: false
|
173 | },
|
174 |
|
175 | |
176 |
|
177 |
|
178 | gantt: {
|
179 | |
180 |
|
181 |
|
182 | titleTopMargin: 25,
|
183 |
|
184 | |
185 |
|
186 |
|
187 | barHeight: 20,
|
188 |
|
189 | |
190 |
|
191 |
|
192 | barGap: 4,
|
193 |
|
194 | |
195 |
|
196 |
|
197 | topPadding: 50,
|
198 |
|
199 | |
200 |
|
201 |
|
202 | leftPadding: 75,
|
203 |
|
204 | |
205 |
|
206 |
|
207 | gridLineStartPadding: 35,
|
208 |
|
209 | |
210 |
|
211 |
|
212 | fontSize: 11,
|
213 |
|
214 | |
215 |
|
216 |
|
217 | fontFamily: '"Open-Sans", "sans-serif"',
|
218 |
|
219 | |
220 |
|
221 |
|
222 | numberSectionStyles: 4,
|
223 |
|
224 | |
225 |
|
226 |
|
227 | axisFormat: '%Y-%m-%d'
|
228 | },
|
229 | class: {},
|
230 | git: {}
|
231 | }
|
232 |
|
233 | setLogLevel(config.logLevel)
|
234 |
|
235 | function parse (text) {
|
236 | const graphType = utils.detectType(text)
|
237 | let parser
|
238 |
|
239 | switch (graphType) {
|
240 | case 'git':
|
241 | parser = gitGraphParser
|
242 | parser.parser.yy = gitGraphAst
|
243 | break
|
244 | case 'flowchart':
|
245 | parser = flowParser
|
246 | parser.parser.yy = flowDb
|
247 | break
|
248 | case 'sequence':
|
249 | parser = sequenceParser
|
250 | parser.parser.yy = sequenceDb
|
251 | break
|
252 | case 'gantt':
|
253 | parser = ganttParser
|
254 | parser.parser.yy = ganttDb
|
255 | break
|
256 | case 'class':
|
257 | parser = classParser
|
258 | parser.parser.yy = classDb
|
259 | break
|
260 | }
|
261 |
|
262 | parser.parser.yy.parseError = (str, hash) => {
|
263 | const error = { str, hash }
|
264 | throw error
|
265 | }
|
266 |
|
267 | parser.parse(text)
|
268 | }
|
269 |
|
270 | export const encodeEntities = function (text) {
|
271 | let txt = text
|
272 |
|
273 | txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
|
274 | const innerTxt = s.substring(0, s.length - 1)
|
275 | return innerTxt
|
276 | })
|
277 | txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
|
278 | const innerTxt = s.substring(0, s.length - 1)
|
279 | return innerTxt
|
280 | })
|
281 |
|
282 | txt = txt.replace(/#\w+;/g, function (s) {
|
283 | const innerTxt = s.substring(1, s.length - 1)
|
284 |
|
285 | const isInt = /^\+?\d+$/.test(innerTxt)
|
286 | if (isInt) {
|
287 | return 'fl°°' + innerTxt + '¶ß'
|
288 | } else {
|
289 | return 'fl°' + innerTxt + '¶ß'
|
290 | }
|
291 | })
|
292 |
|
293 | return txt
|
294 | }
|
295 |
|
296 | export const decodeEntities = function (text) {
|
297 | let txt = text
|
298 |
|
299 | txt = txt.replace(/fl°°/g, function () {
|
300 | return '&#'
|
301 | })
|
302 | txt = txt.replace(/fl°/g, function () {
|
303 | return '&'
|
304 | })
|
305 | txt = txt.replace(/¶ß/g, function () {
|
306 | return ';'
|
307 | })
|
308 |
|
309 | return txt
|
310 | }
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 | const render = function (id, txt, cb, container) {
|
335 | if (typeof container !== 'undefined') {
|
336 | container.innerHTML = ''
|
337 |
|
338 | d3.select(container).append('div')
|
339 | .attr('id', 'd' + id)
|
340 | .append('svg')
|
341 | .attr('id', id)
|
342 | .attr('width', '100%')
|
343 | .attr('xmlns', 'http://www.w3.org/2000/svg')
|
344 | .append('g')
|
345 | } else {
|
346 | const element = document.querySelector('#' + 'd' + id)
|
347 | if (element) {
|
348 | element.innerHTML = ''
|
349 | }
|
350 |
|
351 | d3.select('body').append('div')
|
352 | .attr('id', 'd' + id)
|
353 | .append('svg')
|
354 | .attr('id', id)
|
355 | .attr('width', '100%')
|
356 | .attr('xmlns', 'http://www.w3.org/2000/svg')
|
357 | .append('g')
|
358 | }
|
359 |
|
360 | window.txt = txt
|
361 | txt = encodeEntities(txt)
|
362 |
|
363 | const element = d3.select('#d' + id).node()
|
364 | const graphType = utils.detectType(txt)
|
365 |
|
366 |
|
367 | const svg = element.firstChild
|
368 | const firstChild = svg.firstChild
|
369 |
|
370 |
|
371 | let style = themes[config.theme]
|
372 | if (style === undefined) {
|
373 | style = ''
|
374 | }
|
375 |
|
376 |
|
377 | if (config.themeCSS !== undefined) {
|
378 | style += `\n${config.themeCSS}`
|
379 | }
|
380 |
|
381 |
|
382 | if (graphType === 'flowchart') {
|
383 | const classes = flowRenderer.getClasses(txt)
|
384 | for (const className in classes) {
|
385 | style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }`
|
386 | }
|
387 | }
|
388 |
|
389 | const style1 = document.createElement('style')
|
390 | style1.innerHTML = scope(style, `#${id}`)
|
391 | svg.insertBefore(style1, firstChild)
|
392 |
|
393 | const style2 = document.createElement('style')
|
394 | const cs = window.getComputedStyle(svg)
|
395 | style2.innerHTML = `#${id} {
|
396 | color: ${cs.color};
|
397 | font: ${cs.font};
|
398 | }`
|
399 | svg.insertBefore(style2, firstChild)
|
400 |
|
401 | switch (graphType) {
|
402 | case 'git':
|
403 | config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
404 | gitGraphRenderer.setConf(config.git)
|
405 | gitGraphRenderer.draw(txt, id, false)
|
406 | break
|
407 | case 'flowchart':
|
408 | config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
409 | flowRenderer.setConf(config.flowchart)
|
410 | flowRenderer.draw(txt, id, false)
|
411 | break
|
412 | case 'sequence':
|
413 | config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
414 | if (config.sequenceDiagram) {
|
415 | sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram))
|
416 | console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.')
|
417 | } else {
|
418 | sequenceRenderer.setConf(config.sequence)
|
419 | }
|
420 | sequenceRenderer.draw(txt, id)
|
421 | break
|
422 | case 'gantt':
|
423 | config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
424 | ganttRenderer.setConf(config.gantt)
|
425 | ganttRenderer.draw(txt, id)
|
426 | break
|
427 | case 'class':
|
428 | config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
429 | classRenderer.setConf(config.class)
|
430 | classRenderer.draw(txt, id)
|
431 | break
|
432 | }
|
433 |
|
434 | d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')
|
435 |
|
436 | let url = ''
|
437 | if (config.arrowMarkerAbsolute) {
|
438 | url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
|
439 | url = url.replace(/\(/g, '\\(')
|
440 | url = url.replace(/\)/g, '\\)')
|
441 | }
|
442 |
|
443 |
|
444 | let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g')
|
445 |
|
446 | svgCode = decodeEntities(svgCode)
|
447 |
|
448 | if (typeof cb !== 'undefined') {
|
449 | cb(svgCode, flowDb.bindFunctions)
|
450 | } else {
|
451 | logger.warn('CB = undefined!')
|
452 | }
|
453 |
|
454 | const node = d3.select('#d' + id).node()
|
455 | if (node !== null && typeof node.remove === 'function') {
|
456 | d3.select('#d' + id).node().remove()
|
457 | }
|
458 |
|
459 | return svgCode
|
460 | }
|
461 |
|
462 | const setConf = function (cnf) {
|
463 |
|
464 | const lvl1Keys = Object.keys(cnf)
|
465 | for (let i = 0; i < lvl1Keys.length; i++) {
|
466 | if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
|
467 | const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])
|
468 |
|
469 | for (let j = 0; j < lvl2Keys.length; j++) {
|
470 | logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
|
471 | if (typeof config[lvl1Keys[i]] === 'undefined') {
|
472 | config[lvl1Keys[i]] = {}
|
473 | }
|
474 | logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
|
475 | config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
|
476 | }
|
477 | } else {
|
478 | config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
|
479 | }
|
480 | }
|
481 | }
|
482 |
|
483 | function initialize (options) {
|
484 | logger.debug('Initializing mermaidAPI')
|
485 |
|
486 | if (typeof options === 'object') {
|
487 | setConf(options)
|
488 | }
|
489 | setLogLevel(config.logLevel)
|
490 | }
|
491 |
|
492 | function getConfig () {
|
493 | return config
|
494 | }
|
495 |
|
496 | const mermaidAPI = {
|
497 | render,
|
498 | parse,
|
499 | initialize,
|
500 | getConfig
|
501 | }
|
502 |
|
503 | export default mermaidAPI
|