Merge pull request #1458 from chrismoran-bkt/develop
Auto-wrap, inline config, initialization, dark theme tweaks, etc
This commit is contained in:
commit
145d337db9
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -219,6 +219,11 @@ This sets the font size of the actor's description
|
|||
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.
|
||||
|
@ -229,6 +234,11 @@ This sets the font size of actor-attached notes.
|
|||
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.
|
||||
|
@ -244,6 +254,21 @@ This sets the font size of actor messages.
|
|||
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\*
|
||||
|
|
|
@ -506,8 +506,11 @@ mermaid.sequenceConfig = {
|
|||
| bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwanted clipping which is why this config param exists. | 1 |
|
||||
| actorFontSize | Sets the font size for the actor's description | 14 |
|
||||
| actorFontFamily | Sets the font family for the actor's description | "Open-Sans", "sans-serif" |
|
||||
| actorFontWeight | Sets the font weight for the actor's description | "Open-Sans", "sans-serif" |
|
||||
| noteFontSize | Sets the font size for actor-attached notes | 14 |
|
||||
| noteFontFamily | Sets the font family for actor-attached notes | "trebuchet ms", verdana, arial |
|
||||
| noteFontWeight | Sets the font weight for actor-attached notes | "trebuchet ms", verdana, arial |
|
||||
| noteAlign | Sets the text alignment for text in actor-attached notes | center |
|
||||
| messageFontSize | Sets the font size for actor<->actor messages | 16 |
|
||||
| messageFontFamily | Sets the font family for actor<->actor messages | "trebuchet ms", verdana, arial |
|
||||
| messageFontWeight | Sets the font weight for actor<->actor messages | "trebuchet ms", verdana, arial |
|
||||
|
|
|
@ -16,52 +16,63 @@
|
|||
%x ID
|
||||
%x ALIAS
|
||||
|
||||
// Directive states
|
||||
%x OPEN_DIRECTIVE
|
||||
%x TYPE_DIRECTIVE
|
||||
%x ARG_DIRECTIVE
|
||||
|
||||
// A special state for grabbing text up to the first comment/newline
|
||||
%x LINE
|
||||
|
||||
%%
|
||||
|
||||
[\n]+ return 'NL';
|
||||
\s+ /* skip all whitespace */
|
||||
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
|
||||
<INITIAL,ID,ALIAS,LINE>\#[^\n]* /* skip comments */
|
||||
\%%[^\n]* /* skip comments */
|
||||
"participant" { this.begin('ID'); return 'participant'; }
|
||||
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
||||
<ALIAS>(?:) { this.popState(); this.popState(); return 'NL'; }
|
||||
"loop" { this.begin('LINE'); return 'loop'; }
|
||||
"rect" { this.begin('LINE'); return 'rect'; }
|
||||
"opt" { this.begin('LINE'); return 'opt'; }
|
||||
"alt" { this.begin('LINE'); return 'alt'; }
|
||||
"else" { this.begin('LINE'); return 'else'; }
|
||||
"par" { this.begin('LINE'); return 'par'; }
|
||||
"and" { this.begin('LINE'); return 'and'; }
|
||||
<LINE>[^#\n;]* { this.popState(); return 'restOfLine'; }
|
||||
"end" return 'end';
|
||||
"left of" return 'left_of';
|
||||
"right of" return 'right_of';
|
||||
"over" return 'over';
|
||||
"note" return 'note';
|
||||
"activate" { this.begin('ID'); return 'activate'; }
|
||||
"deactivate" { this.begin('ID'); return 'deactivate'; }
|
||||
"title" return 'title';
|
||||
"sequenceDiagram" return 'SD';
|
||||
"autonumber" return 'autonumber';
|
||||
"," return ',';
|
||||
";" return 'NL';
|
||||
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
|
||||
"->>" return 'SOLID_ARROW';
|
||||
"-->>" return 'DOTTED_ARROW';
|
||||
"->" return 'SOLID_OPEN_ARROW';
|
||||
"-->" return 'DOTTED_OPEN_ARROW';
|
||||
\-[x] return 'SOLID_CROSS';
|
||||
\-\-[x] return 'DOTTED_CROSS';
|
||||
":"[^#\n;]+ return 'TXT';
|
||||
"+" return '+';
|
||||
"-" return '-';
|
||||
<<EOF>> return 'NL';
|
||||
. return 'INVALID';
|
||||
\%\%\{ { this.begin('OPEN_DIRECTIVE'); return 'open_directive'; }
|
||||
<OPEN_DIRECTIVE>((?:(?!\}\%\%)[^:.])*) { this.begin('TYPE_DIRECTIVE'); return 'type_directive'; }
|
||||
<TYPE_DIRECTIVE>":" { this.popState(); this.begin('ARG_DIRECTIVE'); return ':'; }
|
||||
<TYPE_DIRECTIVE,ARG_DIRECTIVE>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<ARG_DIRECTIVE>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
[\n]+ return 'NL';
|
||||
\s+ /* skip all whitespace */
|
||||
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
|
||||
<INITIAL,ID,ALIAS,LINE,ARG_DIRECTIVE,TYPE_DIRECTIVE,OPEN_DIRECTIVE>\#[^\n]* /* skip comments */
|
||||
\%%(?!\{)[^\n]* /* skip comments */
|
||||
[^\}]\%\%[^\n]* /* skip comments */
|
||||
"participant" { this.begin('ID'); return 'participant'; }
|
||||
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
||||
<ALIAS>(?:) { this.popState(); this.popState(); return 'NL'; }
|
||||
"loop" { this.begin('LINE'); return 'loop'; }
|
||||
"rect" { this.begin('LINE'); return 'rect'; }
|
||||
"opt" { this.begin('LINE'); return 'opt'; }
|
||||
"alt" { this.begin('LINE'); return 'alt'; }
|
||||
"else" { this.begin('LINE'); return 'else'; }
|
||||
"par" { this.begin('LINE'); return 'par'; }
|
||||
"and" { this.begin('LINE'); return 'and'; }
|
||||
<LINE>(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; }
|
||||
"end" return 'end';
|
||||
"left of" return 'left_of';
|
||||
"right of" return 'right_of';
|
||||
"over" return 'over';
|
||||
"note" return 'note';
|
||||
"activate" { this.begin('ID'); return 'activate'; }
|
||||
"deactivate" { this.begin('ID'); return 'deactivate'; }
|
||||
"title" return 'title';
|
||||
"sequenceDiagram" return 'SD';
|
||||
"autonumber" return 'autonumber';
|
||||
"," return ',';
|
||||
";" return 'NL';
|
||||
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
|
||||
"->>" return 'SOLID_ARROW';
|
||||
"-->>" return 'DOTTED_ARROW';
|
||||
"->" return 'SOLID_OPEN_ARROW';
|
||||
"-->" return 'DOTTED_OPEN_ARROW';
|
||||
\-[x] return 'SOLID_CROSS';
|
||||
\-\-[x] return 'DOTTED_CROSS';
|
||||
":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT';
|
||||
"+" return '+';
|
||||
"-" return '-';
|
||||
<<EOF>> return 'NL';
|
||||
. return 'INVALID';
|
||||
|
||||
/lex
|
||||
|
||||
|
@ -74,6 +85,7 @@
|
|||
start
|
||||
: SPACE start
|
||||
| NL start
|
||||
| directive start
|
||||
| SD document { yy.apply($2);return $2; }
|
||||
;
|
||||
|
||||
|
@ -85,11 +97,16 @@ document
|
|||
line
|
||||
: SPACE statement { $$ = $2 }
|
||||
| statement { $$ = $1 }
|
||||
| NL { $$=[];}
|
||||
| NL { $$=[]; }
|
||||
;
|
||||
|
||||
directive
|
||||
: openDirective typeDirective closeDirective 'NL'
|
||||
| openDirective typeDirective ':' argDirective closeDirective 'NL'
|
||||
;
|
||||
|
||||
statement
|
||||
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=$4; $$=$2;}
|
||||
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=yy.parseMessage($4); $$=$2;}
|
||||
| 'participant' actor 'NL' {$$=$2;}
|
||||
| signal 'NL'
|
||||
| autonumber {yy.enableSequenceNumbers()}
|
||||
|
@ -99,23 +116,23 @@ statement
|
|||
| title text2 'NL' {$$=[{type:'setTitle', text:$2}]}
|
||||
| 'loop' restOfLine document end
|
||||
{
|
||||
$3.unshift({type: 'loopStart', loopText:$2, signalType: yy.LINETYPE.LOOP_START});
|
||||
$3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START});
|
||||
$3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END});
|
||||
$$=$3;}
|
||||
| 'rect' restOfLine document end
|
||||
{
|
||||
$3.unshift({type: 'rectStart', color:$2, signalType: yy.LINETYPE.RECT_START });
|
||||
$3.push({type: 'rectEnd', color:$2, signalType: yy.LINETYPE.RECT_END });
|
||||
$3.unshift({type: 'rectStart', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_START });
|
||||
$3.push({type: 'rectEnd', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_END });
|
||||
$$=$3;}
|
||||
| opt restOfLine document end
|
||||
{
|
||||
$3.unshift({type: 'optStart', optText:$2, signalType: yy.LINETYPE.OPT_START});
|
||||
$3.push({type: 'optEnd', optText:$2, signalType: yy.LINETYPE.OPT_END});
|
||||
$3.unshift({type: 'optStart', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_START});
|
||||
$3.push({type: 'optEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_END});
|
||||
$$=$3;}
|
||||
| alt restOfLine else_sections end
|
||||
{
|
||||
// Alt start
|
||||
$3.unshift({type: 'altStart', altText:$2, signalType: yy.LINETYPE.ALT_START});
|
||||
$3.unshift({type: 'altStart', altText:yy.parseMessage($2), signalType: yy.LINETYPE.ALT_START});
|
||||
// Content in alt is already in $3
|
||||
// End
|
||||
$3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END});
|
||||
|
@ -123,23 +140,24 @@ statement
|
|||
| par restOfLine par_sections end
|
||||
{
|
||||
// Parallel start
|
||||
$3.unshift({type: 'parStart', parText:$2, signalType: yy.LINETYPE.PAR_START});
|
||||
$3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_START});
|
||||
// Content in par is already in $3
|
||||
// End
|
||||
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
|
||||
$$=$3;}
|
||||
| directive
|
||||
;
|
||||
|
||||
par_sections
|
||||
: document
|
||||
| document and restOfLine par_sections
|
||||
{ $$ = $1.concat([{type: 'and', parText:$3, signalType: yy.LINETYPE.PAR_AND}, $4]); }
|
||||
{ $$ = $1.concat([{type: 'and', parText:yy.parseMessage($3), signalType: yy.LINETYPE.PAR_AND}, $4]); }
|
||||
;
|
||||
|
||||
else_sections
|
||||
: document
|
||||
| document else restOfLine else_sections
|
||||
{ $$ = $1.concat([{type: 'else', altText:$3, signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
|
||||
{ $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
|
||||
;
|
||||
|
||||
note_statement
|
||||
|
@ -195,6 +213,24 @@ signaltype
|
|||
| DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; }
|
||||
;
|
||||
|
||||
text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ;
|
||||
text2
|
||||
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) }
|
||||
;
|
||||
|
||||
openDirective
|
||||
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
|
||||
;
|
||||
|
||||
typeDirective
|
||||
: type_directive { yy.parseDirective($1, 'type_directive'); }
|
||||
;
|
||||
|
||||
argDirective
|
||||
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
|
||||
;
|
||||
|
||||
closeDirective
|
||||
: close_directive { yy.parseDirective('}%%', 'close_directive'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
|
|
@ -1,11 +1,68 @@
|
|||
import { logger } from '../../logger';
|
||||
import { getConfig, setConfig } from '../../config';
|
||||
import mermaidAPI from '../../mermaidAPI';
|
||||
|
||||
let prevActor = undefined;
|
||||
let actors = {};
|
||||
let messages = [];
|
||||
const notes = [];
|
||||
let title = '';
|
||||
let titleWrapped = false;
|
||||
let sequenceNumbersEnabled = false;
|
||||
let wrapEnabled = false;
|
||||
let configUpdated = 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':
|
||||
mermaidAPI.initialize(directive.args);
|
||||
break;
|
||||
case 'config':
|
||||
updateConfig(directive.args);
|
||||
break;
|
||||
case 'wrap':
|
||||
case 'nowrap':
|
||||
wrapEnabled = directive.type === 'wrap';
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
`Unrecognized directive: source: '%%{${directive.type}: ${directive.args}}%%`,
|
||||
directive
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export const addActor = function(id, name, description) {
|
||||
// Don't allow description nulling
|
||||
|
@ -13,9 +70,16 @@ export const addActor = function(id, name, description) {
|
|||
if (old && name === old.name && description == null) return;
|
||||
|
||||
// Don't allow null descriptions, either
|
||||
if (description == null) description = name;
|
||||
if (description == null || description.text == null) {
|
||||
description = { text: name, wrap: null };
|
||||
}
|
||||
|
||||
actors[id] = { name: name, description: description, prevActor: prevActor };
|
||||
actors[id] = {
|
||||
name: name,
|
||||
description: description.text,
|
||||
wrap: (description.wrap === null && autoWrap()) || !!description.wrap,
|
||||
prevActor: prevActor
|
||||
};
|
||||
if (prevActor && actors[prevActor]) {
|
||||
actors[prevActor].nextActor = id;
|
||||
}
|
||||
|
@ -24,7 +88,7 @@ export const addActor = function(id, name, description) {
|
|||
};
|
||||
|
||||
const activationCount = part => {
|
||||
let i = 0;
|
||||
let i;
|
||||
let count = 0;
|
||||
for (i = 0; i < messages.length; i++) {
|
||||
// console.warn(i, messages[i]);
|
||||
|
@ -43,12 +107,27 @@ const activationCount = part => {
|
|||
};
|
||||
|
||||
export const addMessage = function(idFrom, idTo, message, answer) {
|
||||
messages.push({ from: idFrom, to: idTo, message: message, answer: answer });
|
||||
messages.push({
|
||||
from: idFrom,
|
||||
to: idTo,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
|
||||
answer: answer
|
||||
});
|
||||
};
|
||||
|
||||
export const addSignal = function(idFrom, idTo, message, messageType) {
|
||||
export const addSignal = function(idFrom, idTo, message = { text: null, wrap: null }, messageType) {
|
||||
logger.debug(
|
||||
'Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType
|
||||
'Adding message from=' +
|
||||
idFrom +
|
||||
' to=' +
|
||||
idTo +
|
||||
' message=' +
|
||||
message.text +
|
||||
' wrap=' +
|
||||
message.wrap +
|
||||
' type=' +
|
||||
messageType
|
||||
);
|
||||
|
||||
if (messageType === LINETYPE.ACTIVE_END) {
|
||||
|
@ -56,7 +135,7 @@ export const addSignal = function(idFrom, idTo, message, messageType) {
|
|||
logger.debug('Adding message from=', messages, cnt);
|
||||
if (cnt < 1) {
|
||||
// Bail out as there is an activation signal from an inactive participant
|
||||
var error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
|
||||
let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
|
||||
error.hash = {
|
||||
text: '->>-',
|
||||
token: '->>-',
|
||||
|
@ -67,7 +146,13 @@ export const addSignal = function(idFrom, idTo, message, messageType) {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
messages.push({ from: idFrom, to: idTo, message: message, type: messageType });
|
||||
messages.push({
|
||||
from: idFrom,
|
||||
to: idTo,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
|
||||
type: messageType
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -87,14 +172,43 @@ export const getActorKeys = function() {
|
|||
export const getTitle = function() {
|
||||
return title;
|
||||
};
|
||||
export const getTitleWrapped = function() {
|
||||
return titleWrapped;
|
||||
};
|
||||
export const enableSequenceNumbers = function() {
|
||||
sequenceNumbersEnabled = true;
|
||||
};
|
||||
export const showSequenceNumbers = () => sequenceNumbersEnabled;
|
||||
|
||||
export const enableWrap = function() {
|
||||
wrapEnabled = true;
|
||||
};
|
||||
|
||||
export const disableWrap = function() {
|
||||
wrapEnabled = false;
|
||||
};
|
||||
|
||||
export const autoWrap = () => wrapEnabled;
|
||||
|
||||
export const clear = function() {
|
||||
actors = {};
|
||||
messages = [];
|
||||
configUpdated = false;
|
||||
};
|
||||
|
||||
export const parseMessage = function(str) {
|
||||
const _str = str.trim();
|
||||
return {
|
||||
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
|
||||
wrap:
|
||||
_str.match(/^[:]?(?:no)?wrap:/) === null
|
||||
? autoWrap()
|
||||
: _str.match(/^[:]?wrap:/) !== null
|
||||
? true
|
||||
: _str.match(/^[:]?nowrap:/) !== null
|
||||
? false
|
||||
: autoWrap()
|
||||
};
|
||||
};
|
||||
|
||||
export const LINETYPE = {
|
||||
|
@ -133,7 +247,12 @@ export const PLACEMENT = {
|
|||
};
|
||||
|
||||
export const addNote = function(actor, placement, message) {
|
||||
const note = { actor: actor, placement: placement, message: message };
|
||||
const note = {
|
||||
actor: actor,
|
||||
placement: placement,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap
|
||||
};
|
||||
|
||||
// Coerce actor into a [to, from, ...] array
|
||||
const actors = [].concat(actor, actor);
|
||||
|
@ -142,14 +261,29 @@ export const addNote = function(actor, placement, message) {
|
|||
messages.push({
|
||||
from: actors[0],
|
||||
to: actors[1],
|
||||
message: message,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
|
||||
type: LINETYPE.NOTE,
|
||||
placement: placement
|
||||
});
|
||||
};
|
||||
|
||||
export const setTitle = function(titleText) {
|
||||
title = titleText;
|
||||
export const setTitle = function(titleWrap) {
|
||||
title = titleWrap.text;
|
||||
titleWrapped = (titleWrap.wrap === null && autoWrap()) || !!titleWrap.wrap;
|
||||
};
|
||||
|
||||
export const updateConfig = function(config = getConfig()) {
|
||||
try {
|
||||
setConfig(config);
|
||||
configUpdated = true;
|
||||
} catch (error) {
|
||||
logger.error('Error: unable to parse config');
|
||||
}
|
||||
};
|
||||
|
||||
export const hasConfigChange = function() {
|
||||
return configUpdated;
|
||||
};
|
||||
|
||||
export const apply = function(param) {
|
||||
|
@ -221,14 +355,23 @@ export default {
|
|||
addActor,
|
||||
addMessage,
|
||||
addSignal,
|
||||
enableWrap,
|
||||
disableWrap,
|
||||
enableSequenceNumbers,
|
||||
showSequenceNumbers,
|
||||
autoWrap,
|
||||
getMessages,
|
||||
getActors,
|
||||
getActor,
|
||||
getActorKeys,
|
||||
getTitle,
|
||||
parseDirective,
|
||||
hasConfigChange,
|
||||
getConfig,
|
||||
updateConfig,
|
||||
getTitleWrapped,
|
||||
clear,
|
||||
parseMessage,
|
||||
LINETYPE,
|
||||
ARROWTYPE,
|
||||
PLACEMENT,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@ import { logger } from '../../logger';
|
|||
import { parser } from './parser/sequenceDiagram';
|
||||
import common from '../common/common';
|
||||
import sequenceDb from './sequenceDb';
|
||||
import { getConfig } from '../../config';
|
||||
|
||||
parser.yy = sequenceDb;
|
||||
|
||||
|
@ -18,13 +19,17 @@ const conf = {
|
|||
height: 65,
|
||||
actorFontSize: 14,
|
||||
actorFontFamily: '"Open-Sans", "sans-serif"',
|
||||
// 400 = normal
|
||||
actorFontWeight: 400,
|
||||
// Note font settings
|
||||
noteFontSize: 14,
|
||||
noteFontFamily: '"trebuchet ms", verdana, arial',
|
||||
noteFontWeight: 400,
|
||||
noteAlign: 'center',
|
||||
// Message font settings
|
||||
messageFontSize: 16,
|
||||
messageFontFamily: '"trebuchet ms", verdana, arial',
|
||||
messageFontWeight: 400,
|
||||
// Margin around loop boxes
|
||||
boxMargin: 10,
|
||||
boxTextMargin: 5,
|
||||
|
@ -45,7 +50,12 @@ const conf = {
|
|||
// text placement as: tspan | fo | old only text as before
|
||||
textPlacement: 'tspan',
|
||||
|
||||
showSequenceNumbers: false
|
||||
showSequenceNumbers: false,
|
||||
|
||||
// wrap text
|
||||
wrapEnabled: false,
|
||||
// padding for wrapped text
|
||||
wrapPadding: 15
|
||||
};
|
||||
|
||||
export const bounds = {
|
||||
|
@ -69,6 +79,9 @@ export const bounds = {
|
|||
stopy: undefined
|
||||
};
|
||||
this.verticalPos = 0;
|
||||
if (parser.yy.hasConfigChange()) {
|
||||
setConf(getConfig());
|
||||
}
|
||||
},
|
||||
updateVal: function(obj, key, val, fun) {
|
||||
if (typeof obj[key] === 'undefined') {
|
||||
|
@ -138,8 +151,7 @@ export const bounds = {
|
|||
return activation.actor;
|
||||
})
|
||||
.lastIndexOf(message.from.actor);
|
||||
const activation = this.activations.splice(lastActorActivationIdx, 1)[0];
|
||||
return activation;
|
||||
return this.activations.splice(lastActorActivationIdx, 1)[0];
|
||||
},
|
||||
newLoop: function(title, fill) {
|
||||
this.sequenceItems.push({
|
||||
|
@ -152,8 +164,7 @@ export const bounds = {
|
|||
});
|
||||
},
|
||||
endLoop: function() {
|
||||
const loop = this.sequenceItems.pop();
|
||||
return loop;
|
||||
return this.sequenceItems.pop();
|
||||
},
|
||||
addSectionToLoop: function(message) {
|
||||
const loop = this.sequenceItems.pop();
|
||||
|
@ -175,6 +186,55 @@ export const bounds = {
|
|||
}
|
||||
};
|
||||
|
||||
const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
|
||||
if (common.lineBreakRegex.test(label)) {
|
||||
return label;
|
||||
}
|
||||
const words = label.split(' ');
|
||||
const completedLines = [];
|
||||
let nextLine = '';
|
||||
words.forEach((word, index) => {
|
||||
const wordLength = calculateTextWidth(`${word} `);
|
||||
const nextLineLength = calculateTextWidth(nextLine);
|
||||
if (wordLength > maxWidth) {
|
||||
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth);
|
||||
completedLines.push(nextLine, ...hyphenatedStrings);
|
||||
nextLine = remainingWord;
|
||||
} else if (nextLineLength + wordLength >= maxWidth) {
|
||||
completedLines.push(nextLine);
|
||||
nextLine = word;
|
||||
} else {
|
||||
nextLine = [nextLine, word].filter(Boolean).join(' ');
|
||||
}
|
||||
const currentWord = index + 1;
|
||||
const isLastWord = currentWord === words.length;
|
||||
if (isLastWord) {
|
||||
completedLines.push(nextLine);
|
||||
}
|
||||
});
|
||||
return completedLines.filter(line => line !== '').join(joinWith);
|
||||
};
|
||||
|
||||
const breakString = (word, maxWidth, hyphenCharacter = '-') => {
|
||||
const characters = word.split('');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
characters.forEach((character, index) => {
|
||||
const nextLine = `${currentLine}${character}`;
|
||||
const lineWidth = calculateTextWidth(nextLine);
|
||||
if (lineWidth >= maxWidth) {
|
||||
const currentCharacter = index + 1;
|
||||
const isLastLine = characters.length === currentCharacter;
|
||||
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
|
||||
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
|
||||
currentLine = '';
|
||||
} else {
|
||||
currentLine = nextLine;
|
||||
}
|
||||
});
|
||||
return { hyphenatedStrings: lines, remainingWord: currentLine };
|
||||
};
|
||||
|
||||
const _drawLongText = (text, x, y, g, width) => {
|
||||
let textHeight = 0;
|
||||
let prevTextHeight = 0;
|
||||
|
@ -187,7 +247,6 @@ const _drawLongText = (text, x, y, g, width) => {
|
|||
right: 'end',
|
||||
end: 'end'
|
||||
};
|
||||
|
||||
const lines = text.split(common.lineBreakRegex);
|
||||
for (const line of lines) {
|
||||
const textObj = svgDraw.getTextObj();
|
||||
|
@ -215,6 +274,7 @@ const _drawLongText = (text, x, y, g, width) => {
|
|||
.style('text-anchor', alignment)
|
||||
.style('font-size', conf.noteFontSize)
|
||||
.style('font-family', conf.noteFontFamily)
|
||||
.style('font-weight', conf.noteFontWeight)
|
||||
.attr('dominant-baseline', 'central')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
|
@ -263,25 +323,48 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
|
|||
* @param startx
|
||||
* @param stopx
|
||||
* @param verticalPos
|
||||
* @param txtCenter
|
||||
* @param msg
|
||||
* @param sequenceIndex
|
||||
*/
|
||||
const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceIndex) {
|
||||
const g = elem.append('g');
|
||||
const txtCenter = startx + (stopx - startx) / 2;
|
||||
|
||||
let textElems = [];
|
||||
/*
|
||||
let textHeight = 0;
|
||||
const breaklines = msg.message.split(common.lineBreakRegex);
|
||||
for (const breakline of breaklines) {
|
||||
let textElem = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', txtCenter)
|
||||
.attr('y', verticalPos + textHeight)
|
||||
.style('font-size', conf.messageFontSize)
|
||||
.style('font-family', conf.messageFontFamily)
|
||||
.style('font-weight', conf.messageFontWeight)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'messageText')
|
||||
.text(breakline.trim());
|
||||
textElems.push(textElem);
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
}
|
||||
|
||||
let totalOffset = textHeight;
|
||||
*/
|
||||
|
||||
let counterBreaklines = 0;
|
||||
let breaklineOffset = 17;
|
||||
let breaklineOffset = conf.messageFontSize + 4;
|
||||
const breaklines = msg.message.split(common.lineBreakRegex);
|
||||
for (const breakline of breaklines) {
|
||||
textElems.push(
|
||||
g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', txtCenter)
|
||||
// .attr('y', verticalPos - breaklineVerticalOffset + counterBreaklines * breaklineOffset)
|
||||
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
|
||||
.style('font-size', conf.messageFontSize)
|
||||
.style('font-family', conf.messageFontFamily)
|
||||
.style('font-weight', conf.messageFontWeight)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'messageText')
|
||||
.text(breakline.trim())
|
||||
|
@ -290,7 +373,6 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
|||
}
|
||||
const offsetLineCounter = counterBreaklines - 1;
|
||||
let totalOffset = offsetLineCounter * breaklineOffset;
|
||||
|
||||
let textWidths = textElems.map(function(textElem) {
|
||||
return (textElem._groups || textElem)[0][0].getBBox().width;
|
||||
});
|
||||
|
@ -418,28 +500,42 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
|
|||
// Draw the actors
|
||||
let prevWidth = 0;
|
||||
let prevMargin = 0;
|
||||
let maxActorHeight = conf.height;
|
||||
|
||||
for (let i = 0; i < actorKeys.length; i++) {
|
||||
const actor = actors[actorKeys[i]];
|
||||
|
||||
// Add some rendering data to the object
|
||||
actor.width = actor.width || calculateActorWidth(actor);
|
||||
actor.height = conf.height;
|
||||
actor.height = actor.wrap
|
||||
? calculateTextHeight(
|
||||
actor.message,
|
||||
conf.height,
|
||||
actor.width,
|
||||
conf.wrapPadding,
|
||||
actor.wrap,
|
||||
conf.actorFontSize
|
||||
)
|
||||
: conf.height;
|
||||
maxActorHeight = Math.max(maxActorHeight, actor.height);
|
||||
actor.margin = actor.margin || conf.actorMargin;
|
||||
|
||||
actor.x = prevWidth + prevMargin;
|
||||
actor.y = verticalPos;
|
||||
|
||||
if (actor.wrap) {
|
||||
actor.description = wrapLabel(actor.description, actor.width);
|
||||
}
|
||||
// Draw the box with the attached line
|
||||
svgDraw.drawActor(diagram, actor, conf);
|
||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, conf.height);
|
||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
|
||||
|
||||
prevWidth += actor.width;
|
||||
prevMargin += actor.margin;
|
||||
}
|
||||
|
||||
// Add a margin between the actor boxes and the first arrow
|
||||
bounds.bumpVerticalPos(conf.height);
|
||||
bounds.bumpVerticalPos(maxActorHeight);
|
||||
};
|
||||
|
||||
export const setConf = function(cnf) {
|
||||
|
@ -450,7 +546,14 @@ export const setConf = function(cnf) {
|
|||
});
|
||||
|
||||
if (cnf.fontFamily) {
|
||||
conf.actorFontFamily = conf.noteFontFamily = cnf.fontFamily;
|
||||
conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily;
|
||||
}
|
||||
if (cnf.fontSize) {
|
||||
conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize;
|
||||
// conf.height = cnf.fontSize * (65 / 14);
|
||||
}
|
||||
if (cnf.fontWeight) {
|
||||
conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -489,10 +592,50 @@ const calculateActorWidth = function(actor) {
|
|||
return conf.width;
|
||||
}
|
||||
|
||||
return Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
|
||||
);
|
||||
return actor.wrap
|
||||
? conf.width
|
||||
: Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(
|
||||
actor.description,
|
||||
conf.actorFontSize,
|
||||
conf.actorFontFamily,
|
||||
conf.actorFontWeight
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the text's height, taking into account the wrap value and
|
||||
* both the statically configured height, width, and the length of the text (in pixels).
|
||||
*
|
||||
* If the wrapped text text has greater height, we extend the height, so it's
|
||||
* value won't overflow.
|
||||
*
|
||||
* @return - The height for the given actor
|
||||
* @param message the text to measure
|
||||
* @param elementHeight the height of the default bounding box containing the text
|
||||
* @param elementWidth the width of the default bounding box containing the text
|
||||
* @param margin space above and below
|
||||
* @param wrap wrap the text based on: elementWidth - 2 * margin
|
||||
* @param fontSize
|
||||
*/
|
||||
export const calculateTextHeight = function(
|
||||
message,
|
||||
elementHeight,
|
||||
elementWidth,
|
||||
margin,
|
||||
wrap,
|
||||
fontSize
|
||||
) {
|
||||
if (!message) {
|
||||
return elementHeight;
|
||||
}
|
||||
let lineHeightFactor = wrap
|
||||
? wrapLabel(message, elementWidth - 2 * margin).split(common.lineBreakRegex).length
|
||||
: 1;
|
||||
|
||||
return wrap ? Math.max(elementHeight, lineHeightFactor * fontSize) : elementHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -501,14 +644,16 @@ const calculateActorWidth = function(actor) {
|
|||
* @param text - The text to calculate the width of
|
||||
* @param fontSize - The font size of the given text
|
||||
* @param fontFamily - The font family (one, or more fonts) to render
|
||||
* @param fontWeight - The font weight (normal, bold, italics)
|
||||
*/
|
||||
export const calculateTextWidth = function(text, fontSize, fontFamily) {
|
||||
export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeight) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fontSize = fontSize ? fontSize : conf.actorFontSize;
|
||||
fontFamily = fontFamily ? fontFamily : conf.actorFontFamily;
|
||||
fontWeight = fontWeight ? fontWeight : conf.actorFontWeight;
|
||||
|
||||
// We can't really know if the user supplied font family will render on the user agent;
|
||||
// thus, we'll take the max width between the user supplied font family, and a default
|
||||
|
@ -518,7 +663,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
|
|||
let maxWidth = 0;
|
||||
|
||||
const body = select('body');
|
||||
// We don'y want to leak DOM elements - if a removal operation isn't available
|
||||
// We don't want to leak DOM elements - if a removal operation isn't available
|
||||
// for any reason, do not continue.
|
||||
if (!body.remove) {
|
||||
return 0;
|
||||
|
@ -533,6 +678,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
|
|||
const textElem = svgDraw
|
||||
.drawText(g, textObj)
|
||||
.style('font-size', fontSize)
|
||||
.style('font-weight', fontWeight)
|
||||
.style('font-family', fontFamily);
|
||||
|
||||
maxWidth = Math.max(maxWidth, (textElem._groups || textElem)[0][0].getBBox().width);
|
||||
|
@ -542,7 +688,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
|
|||
g.remove();
|
||||
|
||||
// Adds some padding, so the text won't sit exactly within the actor's borders
|
||||
return maxWidth + 35;
|
||||
return maxWidth + conf.wrapPadding * 2;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -568,10 +714,16 @@ export const draw = function(text, id) {
|
|||
const title = parser.yy.getTitle();
|
||||
|
||||
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
|
||||
calculateActorMargins(actors, maxMessageWidthPerActor);
|
||||
const maxActorHeight = calculateActorMargins(actors, maxMessageWidthPerActor);
|
||||
|
||||
drawActors(diagram, actors, actorKeys, 0);
|
||||
|
||||
bounds.bumpVerticalPos(
|
||||
maxActorHeight > conf.height
|
||||
? Math.min(conf.boxMargin, Math.abs(maxActorHeight - conf.height))
|
||||
: 0
|
||||
);
|
||||
|
||||
// The arrow head definition is attached to the svg once
|
||||
svgDraw.insertArrowHead(diagram);
|
||||
svgDraw.insertArrowCrossHead(diagram);
|
||||
|
@ -597,11 +749,10 @@ export const draw = function(text, id) {
|
|||
// Draw the messages/signals
|
||||
let sequenceIndex = 1;
|
||||
messages.forEach(function(msg) {
|
||||
let loopData;
|
||||
const noteWidth = Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(msg.message, conf.noteFontSize, conf.noteFontFamily)
|
||||
);
|
||||
let loopData,
|
||||
noteWidth,
|
||||
textWidth,
|
||||
shouldWrap = msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message);
|
||||
|
||||
switch (msg.type) {
|
||||
case parser.yy.LINETYPE.NOTE:
|
||||
|
@ -609,8 +760,18 @@ export const draw = function(text, id) {
|
|||
|
||||
startx = actors[msg.from].x;
|
||||
stopx = actors[msg.to].x;
|
||||
textWidth = calculateTextWidth(
|
||||
msg.message,
|
||||
conf.noteFontSize,
|
||||
conf.noteFontFamily,
|
||||
conf.noteFontWeight
|
||||
);
|
||||
noteWidth = shouldWrap ? conf.width : Math.max(conf.width, textWidth);
|
||||
|
||||
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
}
|
||||
drawNote(
|
||||
diagram,
|
||||
startx + (actors[msg.from].width + conf.actorMargin) / 2,
|
||||
|
@ -619,6 +780,9 @@ export const draw = function(text, id) {
|
|||
noteWidth
|
||||
);
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
}
|
||||
drawNote(
|
||||
diagram,
|
||||
startx - noteWidth + (actors[msg.from].width - conf.actorMargin) / 2,
|
||||
|
@ -628,6 +792,9 @@ export const draw = function(text, id) {
|
|||
);
|
||||
} else if (msg.to === msg.from) {
|
||||
// Single-actor over
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
}
|
||||
drawNote(
|
||||
diagram,
|
||||
startx + (actors[msg.to].width - noteWidth) / 2,
|
||||
|
@ -637,11 +804,17 @@ export const draw = function(text, id) {
|
|||
);
|
||||
} else {
|
||||
// Multi-actor over
|
||||
forceWidth = Math.abs(startx - stopx) + conf.actorMargin;
|
||||
forceWidth = Math.abs(startx - stopx) + conf.actorMargin / 2;
|
||||
if (shouldWrap) {
|
||||
noteWidth = forceWidth;
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
} else {
|
||||
noteWidth = Math.max(forceWidth, textWidth - 2 * conf.noteMargin);
|
||||
}
|
||||
let x =
|
||||
startx < stopx
|
||||
? startx + (actors[msg.from].width - conf.actorMargin) / 2
|
||||
: stopx + (actors[msg.to].width - conf.actorMargin) / 2;
|
||||
? startx + (actors[msg.from].width - conf.actorMargin / 2) / 2
|
||||
: stopx + (actors[msg.to].width - conf.actorMargin / 2) / 2;
|
||||
|
||||
drawNote(diagram, x, bounds.getVerticalPos(), msg, forceWidth);
|
||||
}
|
||||
|
@ -703,6 +876,9 @@ export const draw = function(text, id) {
|
|||
break;
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, conf.boxMargin);
|
||||
}
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
break;
|
||||
|
@ -726,6 +902,15 @@ export const draw = function(text, id) {
|
|||
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
|
||||
startx = fromBounds[fromIdx];
|
||||
stopx = toBounds[toIdx];
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(
|
||||
msg.message,
|
||||
Math.max(
|
||||
Math.abs(stopx - startx) + conf.messageMargin * 2,
|
||||
conf.width + conf.messageMargin * 2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const verticalPos = bounds.getVerticalPos();
|
||||
drawMessage(diagram, startx, stopx, verticalPos, msg, sequenceIndex);
|
||||
|
@ -823,12 +1008,12 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
|||
const actor = actors[msg.to];
|
||||
|
||||
// If this is the first actor, and the message is left of it, no need to calculate the margin
|
||||
if (msg.placement == parser.yy.PLACEMENT.LEFTOF && !actor.prevActor) {
|
||||
if (msg.placement === parser.yy.PLACEMENT.LEFTOF && !actor.prevActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is the last actor, and the message is right of it, no need to calculate the margin
|
||||
if (msg.placement == parser.yy.PLACEMENT.RIGHTOF && !actor.nextActor) {
|
||||
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF && !actor.nextActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -837,7 +1022,13 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
|||
|
||||
const fontSize = isNote ? conf.noteFontSize : conf.messageFontSize;
|
||||
const fontFamily = isNote ? conf.noteFontFamily : conf.messageFontFamily;
|
||||
const messageWidth = calculateTextWidth(msg.message, fontSize, fontFamily);
|
||||
const fontWeight = isNote ? conf.noteFontWeight : conf.messageFontWeight;
|
||||
const messageWidth = calculateTextWidth(
|
||||
msg.wrap ? wrapLabel(msg.message, conf.width - conf.noteMargin) : msg.message,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fontWeight
|
||||
);
|
||||
|
||||
/*
|
||||
* The following scenarios should be supported:
|
||||
|
@ -855,25 +1046,25 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
|||
* - If the note is on the right of the actor, we should define the current actor
|
||||
* margin
|
||||
*/
|
||||
if (isMessage && msg.from == actor.nextActor) {
|
||||
if (isMessage && msg.from === actor.nextActor) {
|
||||
maxMessageWidthPerActor[msg.to] = Math.max(
|
||||
maxMessageWidthPerActor[msg.to] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (
|
||||
(isMessage && msg.from == actor.prevActor) ||
|
||||
msg.placement == parser.yy.PLACEMENT.RIGHTOF
|
||||
(isMessage && msg.from === actor.prevActor) ||
|
||||
msg.placement === parser.yy.PLACEMENT.RIGHTOF
|
||||
) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (msg.placement == parser.yy.PLACEMENT.LEFTOF) {
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (msg.placement == parser.yy.PLACEMENT.OVER) {
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.OVER) {
|
||||
if (actor.prevActor) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
|
@ -905,6 +1096,7 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
|||
* @param actorToMessageWidth - A map of actor key -> max message width it holds
|
||||
*/
|
||||
const calculateActorMargins = function(actors, actorToMessageWidth) {
|
||||
let maxHeight = 0;
|
||||
for (let actorKey in actorToMessageWidth) {
|
||||
const actor = actors[actorKey];
|
||||
|
||||
|
@ -919,21 +1111,42 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
|
|||
continue;
|
||||
}
|
||||
|
||||
actor.width = Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
|
||||
);
|
||||
[actor, nextActor].forEach(function(act) {
|
||||
act.width = act.wrap
|
||||
? conf.width
|
||||
: Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(
|
||||
act.description,
|
||||
conf.actorFontSize,
|
||||
conf.actorFontFamily,
|
||||
conf.actorFontWeight
|
||||
)
|
||||
);
|
||||
|
||||
nextActor.width = Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(nextActor.description, conf.actorFontSize, conf.actorFontFamily)
|
||||
);
|
||||
act.height = act.wrap
|
||||
? calculateTextHeight(
|
||||
act.description,
|
||||
conf.height,
|
||||
actor.width,
|
||||
conf.actorMargin,
|
||||
act.wrap,
|
||||
conf.actorFontSize
|
||||
)
|
||||
: conf.height;
|
||||
maxHeight = Math.max(maxHeight, act.height);
|
||||
});
|
||||
|
||||
const messageWidth = actorToMessageWidth[actorKey];
|
||||
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
|
||||
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
}
|
||||
Object.keys(actors).forEach(function(key) {
|
||||
actors[key].height = maxHeight;
|
||||
});
|
||||
|
||||
return maxHeight;
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -26,6 +26,9 @@ export const drawText = function(elem, textData) {
|
|||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
textElem.style('font-family', textData.fontFamily);
|
||||
textElem.style('font-size', textData.fontSize);
|
||||
textElem.style('font-weight', textData.fontWeight);
|
||||
textElem.attr('fill', textData.fill);
|
||||
if (typeof textData.class !== 'undefined') {
|
||||
textElem.attr('class', textData.class);
|
||||
|
@ -77,7 +80,7 @@ let actorCnt = -1;
|
|||
* Draws an actor in the diagram with the attaced line
|
||||
* @param elem - The diagram we'll draw to.
|
||||
* @param actor - The actor to draw.
|
||||
* @param config - The sequence diagram config object.
|
||||
* @param conf - drawText implementation discriminator object
|
||||
*/
|
||||
export const drawActor = function(elem, actor, conf) {
|
||||
const center = actor.x + actor.width / 2;
|
||||
|
@ -146,7 +149,7 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct
|
|||
* @param elem - elemenet to append the loop to.
|
||||
* @param bounds - bounds of the given loop.
|
||||
* @param labelText - Text within the loop.
|
||||
* @param config - sequence diagram config object.
|
||||
* @param conf
|
||||
*/
|
||||
export const drawLoop = function(elem, bounds, labelText, conf) {
|
||||
const g = elem.append('g');
|
||||
|
@ -169,11 +172,17 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
|
|||
});
|
||||
}
|
||||
|
||||
let minSize =
|
||||
Math.round((3 * conf.fontSize) / 4) < 10 ? conf.fontSize : Math.round((3 * conf.fontSize) / 4);
|
||||
|
||||
let txt = getTextObj();
|
||||
txt.text = labelText;
|
||||
txt.x = bounds.startx;
|
||||
txt.y = bounds.starty;
|
||||
txt.labelMargin = 1.5 * 10; // This is the small box that says "loop"
|
||||
txt.fontFamily = conf.fontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.fontWeight;
|
||||
txt.class = 'labelText'; // Its size & position are fixed.
|
||||
|
||||
drawLabel(g, txt);
|
||||
|
@ -184,23 +193,31 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
|
|||
txt.y = bounds.starty + 1.5 * conf.boxMargin;
|
||||
txt.anchor = 'middle';
|
||||
txt.class = 'loopText';
|
||||
txt.fontFamily = conf.fontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.fontWeight;
|
||||
|
||||
drawText(g, txt);
|
||||
let textElem = drawText(g, txt);
|
||||
let textHeight = (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
|
||||
if (typeof bounds.sectionTitles !== 'undefined') {
|
||||
bounds.sectionTitles.forEach(function(item, idx) {
|
||||
if (item !== '') {
|
||||
txt.text = '[ ' + item + ' ]';
|
||||
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin;
|
||||
drawText(g, txt);
|
||||
textElem = drawText(g, txt);
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return textHeight + 4;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
* @param color - The fill color for the background
|
||||
* @param elem diagram (reference for bounds)
|
||||
* @param bounds shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function(elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
|
@ -325,7 +342,7 @@ const _drawTextCandidateFunc = (function() {
|
|||
}
|
||||
|
||||
function byTspan(content, g, x, y, width, height, textAttrs, conf) {
|
||||
const { actorFontSize, actorFontFamily } = conf;
|
||||
const { actorFontSize, actorFontFamily, actorFontWeight } = conf;
|
||||
|
||||
const lines = content.split(common.lineBreakRegex);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
|
@ -336,6 +353,7 @@ const _drawTextCandidateFunc = (function() {
|
|||
.attr('y', y)
|
||||
.style('text-anchor', 'middle')
|
||||
.style('font-size', actorFontSize)
|
||||
.style('font-weight', actorFontWeight)
|
||||
.style('font-family', actorFontFamily);
|
||||
text
|
||||
.append('tspan')
|
||||
|
|
|
@ -16,7 +16,13 @@ export const logger = {
|
|||
fatal: () => {}
|
||||
};
|
||||
|
||||
export const setLogLevel = function(level) {
|
||||
export const setLogLevel = function(level = 'fatal') {
|
||||
if (isNaN(level)) {
|
||||
level = level.toLowerCase();
|
||||
if (LEVELS[level] !== undefined) {
|
||||
level = LEVELS[level];
|
||||
}
|
||||
}
|
||||
logger.trace = () => {};
|
||||
logger.debug = () => {};
|
||||
logger.info = () => {};
|
||||
|
|
|
@ -285,6 +285,11 @@ const config = {
|
|||
* **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**.
|
||||
|
@ -295,6 +300,11 @@ const config = {
|
|||
* **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**.
|
||||
|
@ -309,7 +319,22 @@ const config = {
|
|||
* This sets the font family of actor messages.
|
||||
* **Default value "trebuchet ms", verdana, arial**.
|
||||
*/
|
||||
messageFontFamily: '"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
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -536,6 +561,11 @@ setLogLevel(config.logLevel);
|
|||
setConfig(config);
|
||||
|
||||
function parse(text) {
|
||||
const graphInit = utils.detectInit(text);
|
||||
if (graphInit) {
|
||||
reinitialize(graphInit);
|
||||
logger.debug('Init ', graphInit);
|
||||
}
|
||||
const graphType = utils.detectType(text);
|
||||
let parser;
|
||||
|
||||
|
@ -711,6 +741,10 @@ const render = function(id, _txt, cb, container) {
|
|||
txt = encodeEntities(txt);
|
||||
|
||||
const element = select('#d' + id).node();
|
||||
const graphInit = utils.detectInit(txt);
|
||||
if (graphInit) {
|
||||
reinitialize(graphInit);
|
||||
}
|
||||
const graphType = utils.detectType(txt);
|
||||
|
||||
// insert inline style into svg
|
||||
|
@ -919,15 +953,25 @@ const setConf = function(cnf) {
|
|||
}
|
||||
};
|
||||
|
||||
function initialize(options) {
|
||||
logger.debug('Initializing mermaidAPI ', pkg.version);
|
||||
|
||||
// Update default config with options supplied at initialization
|
||||
function reinitialize(options) {
|
||||
if (typeof options === 'object') {
|
||||
setConf(options);
|
||||
}
|
||||
setConfig(config);
|
||||
setLogLevel(config.logLevel);
|
||||
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, config });
|
||||
}
|
||||
|
||||
function initialize(options) {
|
||||
let _config = config;
|
||||
logger.debug('Initializing mermaidAPI ', { version: pkg.version, options, _config });
|
||||
// Update default config with options supplied at initialization
|
||||
if (typeof options === 'object') {
|
||||
_config = Object.assign(_config, options);
|
||||
setConf(_config);
|
||||
}
|
||||
setConfig(_config);
|
||||
setLogLevel(_config.logLevel);
|
||||
}
|
||||
|
||||
// function getConfig () {
|
||||
|
@ -939,6 +983,7 @@ const mermaidAPI = {
|
|||
render,
|
||||
parse,
|
||||
initialize,
|
||||
reinitialize,
|
||||
getConfig
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
$mainBkg: #BDD5EA;
|
||||
$secondBkg: #6D6D65;
|
||||
$mainBkg: #1f2020;
|
||||
$secondBkg: lighten(#1f2020, 16);
|
||||
$mainContrastColor: lightgrey;
|
||||
$darkTextColor: #323D47;
|
||||
$lineColor: $mainContrastColor;
|
||||
|
@ -21,18 +21,18 @@ $edgeLabelBackground: #e8e8e8;
|
|||
|
||||
$actorBorder: $border1;
|
||||
$actorBkg: $mainBkg;
|
||||
$actorTextColor: black;
|
||||
$actorTextColor: $mainContrastColor;
|
||||
$actorLineColor: $mainContrastColor;
|
||||
$signalColor: $mainContrastColor;
|
||||
$signalTextColor: $mainContrastColor;
|
||||
$labelBoxBkgColor: $actorBkg;
|
||||
$labelBoxBorderColor: $actorBorder;
|
||||
$labelTextColor: $darkTextColor;
|
||||
$labelTextColor: $mainContrastColor;
|
||||
$loopTextColor: $mainContrastColor;
|
||||
$noteBorderColor: $border2;
|
||||
$noteBkgColor: #fff5ad;
|
||||
$activationBorderColor: #666;
|
||||
$activationBkgColor: #f4f4f4;
|
||||
$activationBorderColor: $border1;
|
||||
$activationBkgColor: $secondBkg;
|
||||
$sequenceNumberColor: white;
|
||||
|
||||
/* Gantt chart variables */
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
fill: $actorBkg;
|
||||
}
|
||||
|
||||
text.actor {
|
||||
text.actor > tspan {
|
||||
fill: $actorTextColor;
|
||||
stroke: none;
|
||||
}
|
||||
|
@ -14,18 +14,19 @@ text.actor {
|
|||
|
||||
.messageLine0 {
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke-dasharray: none;
|
||||
stroke: $signalColor;
|
||||
}
|
||||
|
||||
.messageLine1 {
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke-dasharray: 2, 2;
|
||||
stroke: $signalColor;
|
||||
}
|
||||
|
||||
#arrowhead {
|
||||
#arrowhead path {
|
||||
fill: $signalColor;
|
||||
stroke: $signalColor;
|
||||
}
|
||||
|
||||
.sequenceNumber {
|
||||
|
@ -37,13 +38,13 @@ text.actor {
|
|||
}
|
||||
|
||||
#crosshead path {
|
||||
fill: $signalColor !important;
|
||||
stroke: $signalColor !important;
|
||||
fill: $signalColor;
|
||||
stroke: $signalColor;
|
||||
}
|
||||
|
||||
.messageText {
|
||||
fill: $signalTextColor;
|
||||
stroke: none;
|
||||
stroke: $signalTextColor;
|
||||
}
|
||||
|
||||
.labelBox {
|
||||
|
@ -51,20 +52,21 @@ text.actor {
|
|||
fill: $labelBoxBkgColor;
|
||||
}
|
||||
|
||||
.labelText {
|
||||
.labelText, .labelText > tspan {
|
||||
fill: $labelTextColor;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.loopText {
|
||||
.loopText, .loopText > tspan {
|
||||
fill: $loopTextColor;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.loopLine {
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke-width: 2px;
|
||||
stroke-dasharray: 2, 2;
|
||||
stroke: $labelBoxBorderColor;
|
||||
fill: $labelBoxBorderColor;
|
||||
}
|
||||
|
||||
.note {
|
||||
|
@ -74,11 +76,8 @@ text.actor {
|
|||
}
|
||||
|
||||
.noteText {
|
||||
fill: black;
|
||||
fill: $actorBkg;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.activation0 {
|
||||
|
@ -95,3 +94,14 @@ text.actor {
|
|||
fill: $activationBkgColor;
|
||||
stroke: $activationBorderColor;
|
||||
}
|
||||
|
||||
g > rect[class="note"] {
|
||||
stroke: $noteBorderColor;
|
||||
fill: $noteBkgColor;
|
||||
}
|
||||
|
||||
g > text[class="noteText"] > tspan {
|
||||
stroke: $actorBkg;
|
||||
fill: $actorBkg;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
|
119
src/utils.js
119
src/utils.js
|
@ -28,11 +28,124 @@ const d3CurveTypes = {
|
|||
curveStepAfter: curveStepAfter,
|
||||
curveStepBefore: curveStepBefore
|
||||
};
|
||||
const directive = /[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
|
||||
const directiveWithoutOpen = /\s*(?:(?:(\w+)(?=:):|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
|
||||
const anyComment = /\s*%%.*\n/gm;
|
||||
|
||||
/**
|
||||
* @function detectInit
|
||||
* Detects the init config object from the text
|
||||
* ```mermaid
|
||||
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
|
||||
* graph LR
|
||||
* a-->b
|
||||
* b-->c
|
||||
* c-->d
|
||||
* d-->e
|
||||
* e-->f
|
||||
* f-->g
|
||||
* g-->h
|
||||
* ```
|
||||
* or
|
||||
* ```mermaid
|
||||
* %%{initialize: {"theme": "dark", logLevel: "debug" }}%%
|
||||
* graph LR
|
||||
* a-->b
|
||||
* b-->c
|
||||
* c-->d
|
||||
* d-->e
|
||||
* e-->f
|
||||
* f-->g
|
||||
* g-->h
|
||||
* ```
|
||||
*
|
||||
* @param {string} text The text defining the graph
|
||||
* @returns {object} the json object representing the init to pass to mermaid.initialize()
|
||||
*/
|
||||
export const detectInit = function(text) {
|
||||
let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
|
||||
let results = {};
|
||||
if (Array.isArray(inits)) {
|
||||
let args = inits.map(init => init.args);
|
||||
results = Object.assign(results, ...args);
|
||||
} else {
|
||||
results = inits.args;
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function detectDirective
|
||||
* Detects the directive from the text. Text can be single line or multiline. If type is null or omitted
|
||||
* the first directive encountered in text will be returned
|
||||
* ```mermaid
|
||||
* graph LR
|
||||
* %%{somedirective}%%
|
||||
* a-->b
|
||||
* b-->c
|
||||
* c-->d
|
||||
* d-->e
|
||||
* e-->f
|
||||
* f-->g
|
||||
* g-->h
|
||||
* ```
|
||||
*
|
||||
* @param {string} text The text defining the graph
|
||||
* @param {string|RegExp} type The directive to return (default: null
|
||||
* @returns {object | Array} An object or Array representing the directive(s): { type: string, args: object|null } matchd by the input type
|
||||
* if a single directive was found, that directive object will be returned.
|
||||
*/
|
||||
export const detectDirective = function(text, type = null) {
|
||||
try {
|
||||
const commentWithoutDirectives = new RegExp(
|
||||
`[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).*\n`,
|
||||
'ig'
|
||||
);
|
||||
text = text
|
||||
.trim()
|
||||
.replace(commentWithoutDirectives, '')
|
||||
.replace(/'/gm, '"');
|
||||
logger.debug(
|
||||
`Detecting diagram directive${type !== null ? ' type:' + type : ''} based on the text:${text}`
|
||||
);
|
||||
let match,
|
||||
result = [];
|
||||
while ((match = directive.exec(text)) !== null) {
|
||||
// This is necessary to avoid infinite loops with zero-width matches
|
||||
if (match.index === directive.lastIndex) {
|
||||
directive.lastIndex++;
|
||||
}
|
||||
if (
|
||||
(match && !type) ||
|
||||
(type && match[1] && match[1].match(type)) ||
|
||||
(type && match[2] && match[2].match(type))
|
||||
) {
|
||||
let type = match[1] ? match[1] : match[2];
|
||||
let args = match[3] ? match[3].trim() : match[4] ? JSON.parse(match[4].trim()) : null;
|
||||
result.push({ type, args });
|
||||
}
|
||||
}
|
||||
if (result.length === 0) {
|
||||
result.push({ type: text, args: null });
|
||||
}
|
||||
|
||||
return result.length === 1 ? result[0] : result;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`ERROR: ${error.message} - Unable to parse directive${
|
||||
type !== null ? ' type:' + type : ''
|
||||
} based on the text:${text}`
|
||||
);
|
||||
return { type: null, args: null };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @function detectType
|
||||
* Detects the type of the graph text.
|
||||
* Detects the type of the graph text. Takes into consideration the possible existence of an %%init
|
||||
* directive
|
||||
* ```mermaid
|
||||
* %%{initialize: {"startOnLoad": true, logLevel: "fatal" }}%%
|
||||
* graph LR
|
||||
* a-->b
|
||||
* b-->c
|
||||
|
@ -47,7 +160,7 @@ const d3CurveTypes = {
|
|||
* @returns {string} A graph definition key
|
||||
*/
|
||||
export const detectType = function(text) {
|
||||
text = text.replace(/^\s*%%.*\n/g, '\n');
|
||||
text = text.replace(directive, '').replace(anyComment, '\n');
|
||||
logger.debug('Detecting diagram type based on the text ' + text);
|
||||
if (text.match(/^\s*sequenceDiagram/)) {
|
||||
return 'sequence';
|
||||
|
@ -270,6 +383,8 @@ export const generateId = () => {
|
|||
};
|
||||
|
||||
export default {
|
||||
detectInit,
|
||||
detectDirective,
|
||||
detectType,
|
||||
isSubstringInArray,
|
||||
interpolateToCurve,
|
||||
|
|
|
@ -2,23 +2,73 @@
|
|||
import utils from './utils';
|
||||
|
||||
describe('when detecting chart type ', function() {
|
||||
it('should handle a graph defintion', function() {
|
||||
it('should handle a graph definition', function() {
|
||||
const str = 'graph TB\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('flowchart');
|
||||
});
|
||||
it('should handle a graph defintion with leading spaces', function() {
|
||||
it('should handle an initialize definition', function() {
|
||||
const str = `
|
||||
%%{initialize: { 'logLevel': 0, 'theme': 'dark' }}%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: hi`;
|
||||
const type = utils.detectType(str);
|
||||
const init = utils.detectInit(str);
|
||||
expect(type).toBe('sequence');
|
||||
expect(init).toEqual({logLevel:0,theme:"dark"});
|
||||
});
|
||||
it('should handle an init definition', function() {
|
||||
const str = `
|
||||
%%{init: { 'logLevel': 0, 'theme': 'dark' }}%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: hi`;
|
||||
const type = utils.detectType(str);
|
||||
const init = utils.detectInit(str);
|
||||
expect(type).toBe('sequence');
|
||||
expect(init).toEqual({logLevel:0,theme:"dark"});
|
||||
});
|
||||
it('should handle a multiline init definition', function() {
|
||||
const str = `
|
||||
%%{
|
||||
init: {
|
||||
'logLevel': 0,
|
||||
'theme': 'dark'
|
||||
}
|
||||
}%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: hi`;
|
||||
const type = utils.detectType(str);
|
||||
const init = utils.detectInit(str);
|
||||
expect(type).toBe('sequence');
|
||||
expect(init).toEqual({logLevel:0,theme:"dark"});
|
||||
});
|
||||
it('should handle multiple init directives', function() {
|
||||
const str = `
|
||||
%%{ init: { 'logLevel': 0, 'theme': 'forest' } }%%
|
||||
%%{
|
||||
init: {
|
||||
'theme': 'dark'
|
||||
}
|
||||
}%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: hi`;
|
||||
const type = utils.detectType(str);
|
||||
const init = utils.detectInit(str);
|
||||
expect(type).toBe('sequence');
|
||||
expect(init).toEqual({logLevel:0,theme:"dark"});
|
||||
});
|
||||
it('should handle a graph definition with leading spaces', function() {
|
||||
const str = ' graph TB\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('flowchart');
|
||||
});
|
||||
|
||||
it('should handle a graph defintion with leading spaces and newline', function() {
|
||||
it('should handle a graph definition with leading spaces and newline', function() {
|
||||
const str = ' \n graph TB\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('flowchart');
|
||||
});
|
||||
it('should handle a graph defintion for gitGraph', function() {
|
||||
it('should handle a graph definition for gitGraph', function() {
|
||||
const str = ' \n gitGraph TB:\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('git');
|
||||
|
|
Loading…
Reference in New Issue