mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-27 11:19:38 +02:00
Merge branch 'develop' into bug/4813_Fix_Elk_title_arrow_styling
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.6.0",
|
||||
"version": "10.6.1",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
|
@@ -3,6 +3,8 @@ import { log } from './logger.js';
|
||||
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js';
|
||||
import { detectType, getDiagramLoader } from './diagram-api/detectType.js';
|
||||
import { UnknownDiagramError } from './errors.js';
|
||||
import { encodeEntities } from './utils.js';
|
||||
|
||||
import type { DetailedError } from './utils.js';
|
||||
import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js';
|
||||
|
||||
@@ -21,6 +23,7 @@ export class Diagram {
|
||||
|
||||
private detectError?: UnknownDiagramError;
|
||||
constructor(public text: string, public metadata: Pick<DiagramMetadata, 'title'> = {}) {
|
||||
this.text = encodeEntities(text);
|
||||
this.text += '\n';
|
||||
const cnf = configApi.getConfig();
|
||||
try {
|
||||
|
@@ -2,7 +2,7 @@ import { select } from 'd3';
|
||||
import { log } from '../logger.js';
|
||||
import { getConfig } from '../diagram-api/diagramAPI.js';
|
||||
import { evaluate } from '../diagrams/common/common.js';
|
||||
import { decodeEntities } from '../mermaidAPI.js';
|
||||
import { decodeEntities } from '../utils.js';
|
||||
|
||||
/**
|
||||
* @param dom
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import createLabel from '../createLabel.js';
|
||||
import { createText } from '../../rendering-util/createText.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import { decodeEntities } from '../../mermaidAPI.js';
|
||||
import { select } from 'd3';
|
||||
import { evaluate, sanitizeText } from '../../diagrams/common/common.js';
|
||||
import { decodeEntities } from '../../utils.js';
|
||||
|
||||
export const labelHelper = async (parent, node, _classes, isNode) => {
|
||||
let classes;
|
||||
|
@@ -69,4 +69,18 @@ Expecting 'TXT', got 'NEWLINE'"
|
||||
'"No diagram type detected matching given configuration for text: thor TD; A-->B"'
|
||||
);
|
||||
});
|
||||
|
||||
test('should consider entity codes when present in diagram defination', async () => {
|
||||
const diagram = await getDiagramFromText(`sequenceDiagram
|
||||
A->>B: I #9829; you!
|
||||
B->>A: I #9829; you #infin; times more!`);
|
||||
// @ts-ignore: we need to add types for sequenceDb which will be done in separate PR
|
||||
const messages = diagram.db?.getMessages?.();
|
||||
if (!messages) {
|
||||
throw new Error('Messages not found!');
|
||||
}
|
||||
|
||||
expect(messages[0].message).toBe('I fl°°9829¶ß you!');
|
||||
expect(messages[1].message).toBe('I fl°°9829¶ß you fl°infin¶ß times more!');
|
||||
});
|
||||
});
|
||||
|
@@ -231,7 +231,7 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr
|
||||
//Set relationship style and line type
|
||||
classes: 'relation',
|
||||
pattern: edge.relation.lineType == 1 ? 'dashed' : 'solid',
|
||||
id: 'id' + cnt,
|
||||
id: `id_${edge.id1}_${edge.id2}_${cnt}`,
|
||||
// Set link type for rendering
|
||||
arrowhead: edge.type === 'arrow_open' ? 'none' : 'normal',
|
||||
//Set edge extra labels
|
||||
|
@@ -681,3 +681,82 @@ describe('given text representing a method, ', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given text representing an attribute', () => {
|
||||
describe('when the attribute has no modifiers', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = 'name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has public "+" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '+name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('+name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has protected "#" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '#name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('#name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has private "-" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '-name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('-name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has internal "~" modifier', () => {
|
||||
it('should parse the display text correctly', () => {
|
||||
const str = '~name String';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('~name String');
|
||||
expect(displayDetails.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has static "$" modifier', () => {
|
||||
it('should parse the display text correctly and apply static css style', () => {
|
||||
const str = 'name String$';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('name String');
|
||||
expect(displayDetails.cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the attribute has abstract "*" modifier', () => {
|
||||
it('should parse the display text correctly and apply abstract css style', () => {
|
||||
const str = 'name String*';
|
||||
|
||||
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
|
||||
|
||||
expect(displayDetails.displayText).toBe('name String');
|
||||
expect(displayDetails.cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -106,7 +106,7 @@ export class ClassMember {
|
||||
this.visibility = firstChar as Visibility;
|
||||
}
|
||||
|
||||
if (lastChar.match(/[*?]/)) {
|
||||
if (lastChar.match(/[$*]/)) {
|
||||
potentialClassifier = lastChar;
|
||||
}
|
||||
|
||||
|
@@ -145,6 +145,7 @@ g.classGroup line {
|
||||
|
||||
.edgeTerminals {
|
||||
font-size: 11px;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.classTitleText {
|
||||
|
@@ -38,6 +38,20 @@ describe('when securityLevel is antiscript, all script must be removed', () => {
|
||||
compareRemoveScript(`<img onerror="alert('hello');">`, `<img>`);
|
||||
});
|
||||
|
||||
it('should detect unsecured target attribute, if value is _blank then generate a secured link', () => {
|
||||
compareRemoveScript(
|
||||
`<a href="https://mermaid.js.org/" target="_blank">note about mermaid</a>`,
|
||||
`<a href="https://mermaid.js.org/" target="_blank" rel="noopener">note about mermaid</a>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect unsecured target attribute from links', () => {
|
||||
compareRemoveScript(
|
||||
`<a href="https://mermaid.js.org/" target="_self">note about mermaid</a>`,
|
||||
`<a href="https://mermaid.js.org/" target="_self">note about mermaid</a>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect iframes', () => {
|
||||
compareRemoveScript(
|
||||
`<iframe src="http://abc.com/script1.js"></iframe>
|
||||
|
@@ -25,7 +25,27 @@ export const getRows = (s?: string): string[] => {
|
||||
* @returns The safer text
|
||||
*/
|
||||
export const removeScript = (txt: string): string => {
|
||||
return DOMPurify.sanitize(txt);
|
||||
const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
|
||||
|
||||
DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => {
|
||||
if (node.tagName === 'A' && node.hasAttribute('target')) {
|
||||
node.setAttribute(TEMPORARY_ATTRIBUTE, node.getAttribute('target') || '');
|
||||
}
|
||||
});
|
||||
|
||||
const sanitizedText = DOMPurify.sanitize(txt);
|
||||
|
||||
DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {
|
||||
if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
|
||||
node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) || '');
|
||||
node.removeAttribute(TEMPORARY_ATTRIBUTE);
|
||||
if (node.getAttribute('target') === '_blank') {
|
||||
node.setAttribute('rel', 'noopener');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return sanitizedText;
|
||||
};
|
||||
|
||||
const sanitizeMore = (text: string, config: MermaidConfig) => {
|
||||
|
@@ -201,6 +201,13 @@ export const updateLinkInterpolate = function (positions, interp) {
|
||||
*/
|
||||
export const updateLink = function (positions, style) {
|
||||
positions.forEach(function (pos) {
|
||||
if (pos >= edges.length) {
|
||||
throw new Error(
|
||||
`The index ${pos} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${
|
||||
edges.length - 1
|
||||
}. (Help: Ensure that the index is within the range of existing edges.)`
|
||||
);
|
||||
}
|
||||
if (pos === 'default') {
|
||||
edges.defaultStyle = style;
|
||||
} else {
|
||||
@@ -425,7 +432,7 @@ const setupToolTips = function (element) {
|
||||
tooltipElem
|
||||
.text(el.attr('title'))
|
||||
.style('left', window.scrollX + rect.left + (rect.right - rect.left) / 2 + 'px')
|
||||
.style('top', window.scrollY + rect.top - 14 + document.body.scrollTop + 'px');
|
||||
.style('top', window.scrollY + rect.bottom + 'px');
|
||||
tooltipElem.html(tooltipElem.html().replace(/<br\/>/g, '<br/>'));
|
||||
el.classed('hover', true);
|
||||
})
|
||||
|
@@ -286,6 +286,30 @@ describe('[Style] when parsing', () => {
|
||||
expect(edges[0].type).toBe('arrow_point');
|
||||
});
|
||||
|
||||
it('should handle style definitions within number of edges', function () {
|
||||
expect(() =>
|
||||
flow.parser
|
||||
.parse(
|
||||
`graph TD
|
||||
A-->B
|
||||
linkStyle 1 stroke-width:1px;`
|
||||
)
|
||||
.toThrow(
|
||||
'The index 1 for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and 0. (Help: Ensure that the index is within the range of existing edges.)'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle style definitions within number of edges', function () {
|
||||
const res = flow.parser.parse(`graph TD
|
||||
A-->B
|
||||
linkStyle 0 stroke-width:1px;`);
|
||||
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(edges[0].style[0]).toBe('stroke-width:1px');
|
||||
});
|
||||
|
||||
it('should handle multi-numbered style definitions with more then 1 digit in a row', function () {
|
||||
const res = flow.parser.parse(
|
||||
'graph TD\n' +
|
||||
|
@@ -535,6 +535,10 @@ describe('[Text] when parsing', () => {
|
||||
expect(vert['A'].text).toBe('this is an ellipse');
|
||||
});
|
||||
|
||||
it('should not freeze when ellipse text has a `(`', function () {
|
||||
expect(() => flow.parser.parse('graph\nX(- My Text (')).toThrowError();
|
||||
});
|
||||
|
||||
it('should handle text in diamond vertices with space', function () {
|
||||
const res = flow.parser.parse('graph TD;A(chimpansen hoppar)-->C;');
|
||||
|
||||
|
@@ -134,7 +134,7 @@ that id.
|
||||
<*>\s*\~\~[\~]+\s* return 'LINK';
|
||||
|
||||
<ellipseText>[-/\)][\)] { this.popState(); return '-)'; }
|
||||
<ellipseText>[^\(\)\[\]\{\}]|-/!\)+ return "TEXT"
|
||||
<ellipseText>[^\(\)\[\]\{\}]|-\!\)+ return "TEXT"
|
||||
<*>"(-" { this.pushState("ellipseText"); return '(-'; }
|
||||
|
||||
<text>"])" { this.popState(); return 'STADIUMEND'; }
|
||||
|
@@ -1,9 +1,9 @@
|
||||
const getStyles = (options) =>
|
||||
`
|
||||
.mermaid-main-font {
|
||||
font-family: "trebuchet ms", verdana, arial, sans-serif;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
|
||||
}
|
||||
|
||||
.exclude-range {
|
||||
fill: ${options.excludeBkgColor};
|
||||
}
|
||||
@@ -45,11 +45,7 @@ const getStyles = (options) =>
|
||||
|
||||
.sectionTitle {
|
||||
text-anchor: start;
|
||||
// font-size: ${options.ganttFontSize};
|
||||
// text-height: 14px;
|
||||
font-family: 'trebuchet ms', verdana, arial, sans-serif;
|
||||
font-family: var(--mermaid-font-family);
|
||||
|
||||
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +55,11 @@ const getStyles = (options) =>
|
||||
stroke: ${options.gridColor};
|
||||
opacity: 0.8;
|
||||
shape-rendering: crispEdges;
|
||||
text {
|
||||
font-family: ${options.fontFamily};
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
}
|
||||
|
||||
.grid .tick text {
|
||||
font-family: ${options.fontFamily};
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
|
||||
.grid path {
|
||||
@@ -89,33 +86,27 @@ const getStyles = (options) =>
|
||||
|
||||
.taskText {
|
||||
text-anchor: middle;
|
||||
font-family: 'trebuchet ms', verdana, arial, sans-serif;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
|
||||
}
|
||||
|
||||
// .taskText:not([font-size]) {
|
||||
// font-size: ${options.ganttFontSize};
|
||||
// }
|
||||
|
||||
.taskTextOutsideRight {
|
||||
fill: ${options.taskTextDarkColor};
|
||||
text-anchor: start;
|
||||
// font-size: ${options.ganttFontSize};
|
||||
font-family: 'trebuchet ms', verdana, arial, sans-serif;
|
||||
font-family: var(--mermaid-font-family);
|
||||
|
||||
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
|
||||
}
|
||||
|
||||
.taskTextOutsideLeft {
|
||||
fill: ${options.taskTextDarkColor};
|
||||
text-anchor: end;
|
||||
// font-size: ${options.ganttFontSize};
|
||||
}
|
||||
|
||||
|
||||
/* Special case clickable */
|
||||
|
||||
.task.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.taskText.clickable {
|
||||
cursor: pointer;
|
||||
fill: ${options.taskTextClickableColor} !important;
|
||||
@@ -134,6 +125,7 @@ const getStyles = (options) =>
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Specific task settings for the sections*/
|
||||
|
||||
.taskText0,
|
||||
@@ -255,9 +247,8 @@ const getStyles = (options) =>
|
||||
.titleText {
|
||||
text-anchor: middle;
|
||||
font-size: 18px;
|
||||
fill: ${options.textColor} ;
|
||||
font-family: 'trebuchet ms', verdana, arial, sans-serif;
|
||||
font-family: var(--mermaid-font-family);
|
||||
fill: ${options.titleColor || options.textColor};
|
||||
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -338,26 +338,34 @@ const drawCommits = (svg, commits, modifyGraph) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect if there are other commits between commit1's x-position and commit2's x-position on the
|
||||
* same branch as commit2.
|
||||
* Detect if there are commits
|
||||
* between commitA's x-position
|
||||
* and commitB's x-position on the
|
||||
* same branch as commitA, where
|
||||
* commitA isn't main
|
||||
*
|
||||
* @param {any} commit1
|
||||
* @param {any} commit2
|
||||
* @param {any} commitA
|
||||
* @param {any} commitB
|
||||
* @param branchToGetCurve
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param allCommits
|
||||
* @returns {boolean} If there are commits between commit1's x-position and commit2's x-position
|
||||
* @returns {boolean}
|
||||
* If there are commits between
|
||||
* commitA's x-position
|
||||
* and commitB's x-position
|
||||
* on the source branch, where
|
||||
* source branch is not main
|
||||
* return true
|
||||
*/
|
||||
const hasOverlappingCommits = (commit1, commit2, allCommits) => {
|
||||
// Find commits on the same branch as commit2
|
||||
const keys = Object.keys(allCommits);
|
||||
const overlappingComits = keys.filter((key) => {
|
||||
return (
|
||||
allCommits[key].branch === commit2.branch &&
|
||||
allCommits[key].seq > commit1.seq &&
|
||||
allCommits[key].seq < commit2.seq
|
||||
);
|
||||
const shouldRerouteArrow = (commitA, commitB, p1, p2, allCommits) => {
|
||||
const commitBIsFurthest = dir === 'TB' ? p1.x < p2.x : p1.y < p2.y;
|
||||
const branchToGetCurve = commitBIsFurthest ? commitB.branch : commitA.branch;
|
||||
const isOnBranchToGetCurve = (x) => x.branch === branchToGetCurve;
|
||||
const isBetweenCommits = (x) => x.seq > commitA.seq && x.seq < commitB.seq;
|
||||
return Object.values(allCommits).some((commitX) => {
|
||||
return isBetweenCommits(commitX) && isOnBranchToGetCurve(commitX);
|
||||
});
|
||||
|
||||
return overlappingComits.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -388,49 +396,61 @@ const findLane = (y1, y2, depth = 0) => {
|
||||
* Draw the lines between the commits. They were arrows initially.
|
||||
*
|
||||
* @param {any} svg
|
||||
* @param {any} commit1
|
||||
* @param {any} commit2
|
||||
* @param {any} commitA
|
||||
* @param {any} commitB
|
||||
* @param {any} allCommits
|
||||
*/
|
||||
const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
const p1 = commitPos[commit1.id];
|
||||
const p2 = commitPos[commit2.id];
|
||||
const overlappingCommits = hasOverlappingCommits(commit1, commit2, allCommits);
|
||||
// log.debug('drawArrow', p1, p2, overlappingCommits, commit1.id, commit2.id);
|
||||
const drawArrow = (svg, commitA, commitB, allCommits) => {
|
||||
const p1 = commitPos[commitA.id]; // arrowStart
|
||||
const p2 = commitPos[commitB.id]; // arrowEnd
|
||||
const arrowNeedsRerouting = shouldRerouteArrow(commitA, commitB, p1, p2, allCommits);
|
||||
// log.debug('drawArrow', p1, p2, arrowNeedsRerouting, commitA.id, commitB.id);
|
||||
|
||||
// Lower-right quadrant logic; top-left is 0,0
|
||||
|
||||
let arc = '';
|
||||
let arc2 = '';
|
||||
let radius = 0;
|
||||
let offset = 0;
|
||||
let colorClassNum = branchPos[commit2.branch].index;
|
||||
let colorClassNum = branchPos[commitB.branch].index;
|
||||
let lineDef;
|
||||
if (overlappingCommits) {
|
||||
if (arrowNeedsRerouting) {
|
||||
arc = 'A 10 10, 0, 0, 0,';
|
||||
arc2 = 'A 10 10, 0, 0, 1,';
|
||||
radius = 10;
|
||||
offset = 10;
|
||||
// Figure out the color of the arrow,arrows going down take the color from the destination branch
|
||||
colorClassNum = branchPos[commit2.branch].index;
|
||||
|
||||
const lineY = p1.y < p2.y ? findLane(p1.y, p2.y) : findLane(p2.y, p1.y);
|
||||
const lineX = p1.x < p2.x ? findLane(p1.x, p2.x) : findLane(p2.x, p1.x);
|
||||
|
||||
if (dir === 'TB') {
|
||||
if (p1.x < p2.x) {
|
||||
// Source commit is on branch position left of destination commit
|
||||
// so render arrow rightward with colour of destination branch
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc2} ${lineX} ${
|
||||
p1.y + offset
|
||||
} L ${lineX} ${p2.y - radius} ${arc} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
// Source commit is on branch position right of destination commit
|
||||
// so render arrow leftward with colour of source branch
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc} ${lineX} ${
|
||||
p1.y + offset
|
||||
} L ${lineX} ${p2.y - radius} ${arc2} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
} else {
|
||||
if (p1.y < p2.y) {
|
||||
// Source commit is on branch positioned above destination commit
|
||||
// so render arrow downward with colour of destination branch
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY - radius} ${arc} ${
|
||||
p1.x + offset
|
||||
} ${lineY} L ${p2.x - radius} ${lineY} ${arc2} ${p2.x} ${lineY + offset} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
// Source commit is on branch positioned below destination commit
|
||||
// so render arrow upward with colour of source branch
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY + radius} ${arc2} ${
|
||||
p1.x + offset
|
||||
} ${lineY} L ${p2.x - radius} ${lineY} ${arc} ${p2.x} ${lineY - offset} L ${p2.x} ${p2.y}`;
|
||||
@@ -445,7 +465,7 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
offset = 20;
|
||||
|
||||
// Figure out the color of the arrow,arrows going down take the color from the destination branch
|
||||
colorClassNum = branchPos[commit2.branch].index;
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
|
||||
p1.y + offset
|
||||
@@ -458,14 +478,14 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
offset = 20;
|
||||
|
||||
// Arrows going up take the color from the source branch
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
|
||||
if (p1.x === p2.x) {
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x + radius} ${p1.y} ${arc} ${p1.x + offset} ${
|
||||
p2.y + radius
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
@@ -475,10 +495,8 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
|
||||
// Figure out the color of the arrow,arrows going down take the color from the destination branch
|
||||
colorClassNum = branchPos[commit2.branch].index;
|
||||
|
||||
// Arrows going up take the color from the target branch
|
||||
colorClassNum = branchPos[commitB.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${
|
||||
p2.x
|
||||
} ${p2.y}`;
|
||||
@@ -487,16 +505,15 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
|
||||
// Arrows going up take the color from the source branch
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${p1.y - offset} L ${
|
||||
p2.x
|
||||
} ${p2.y}`;
|
||||
}
|
||||
|
||||
if (p1.y === p2.y) {
|
||||
colorClassNum = branchPos[commit1.branch].index;
|
||||
colorClassNum = branchPos[commitA.branch].index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${
|
||||
p2.x
|
||||
} ${p2.y}`;
|
||||
|
@@ -829,6 +829,11 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos);
|
||||
}
|
||||
|
||||
log.debug('createdActors', createdActors);
|
||||
log.debug('destroyedActors', destroyedActors);
|
||||
|
||||
drawActors(diagram, actors, actorKeys, false);
|
||||
|
||||
// Draw the messages/signals
|
||||
let sequenceIndex = 1;
|
||||
let sequenceIndexStep = 1;
|
||||
@@ -1028,14 +1033,12 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
}
|
||||
});
|
||||
|
||||
log.debug('createdActors', createdActors);
|
||||
log.debug('destroyedActors', destroyedActors);
|
||||
|
||||
drawActors(diagram, actors, actorKeys, false);
|
||||
messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj));
|
||||
|
||||
if (conf.mirrorActors) {
|
||||
drawActors(diagram, actors, actorKeys, true);
|
||||
}
|
||||
|
||||
backgrounds.forEach((e) => svgDraw.drawBackgroundRect(diagram, e));
|
||||
fixLifeLineHeights(diagram, actors, actorKeys, conf);
|
||||
|
||||
|
@@ -324,7 +324,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
const center = actor.x + actor.width / 2;
|
||||
const centerY = actorY + 5;
|
||||
|
||||
const boxpluslineGroup = elem.append('g').lower();
|
||||
const boxpluslineGroup = elem.append('g');
|
||||
var g = boxpluslineGroup;
|
||||
|
||||
if (!isFooter) {
|
||||
|
@@ -4,7 +4,10 @@ import { defineConfig, MarkdownOptions } from 'vitepress';
|
||||
|
||||
const allMarkdownTransformers: MarkdownOptions = {
|
||||
// the shiki theme to highlight code blocks
|
||||
theme: 'github-dark',
|
||||
theme: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
config: async (md) => {
|
||||
await MermaidExample(md);
|
||||
},
|
||||
@@ -91,7 +94,7 @@ function nav() {
|
||||
items: [
|
||||
{
|
||||
text: 'Changelog',
|
||||
link: 'https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md',
|
||||
link: 'https://github.com/mermaid-js/mermaid/releases',
|
||||
},
|
||||
{
|
||||
text: 'Contributing',
|
||||
@@ -150,7 +153,7 @@ function sidebarSyntax() {
|
||||
{ text: 'Timeline 🔥', link: '/syntax/timeline' },
|
||||
{ text: 'Zenuml 🔥', link: '/syntax/zenuml' },
|
||||
{ text: 'Sankey 🔥', link: '/syntax/sankey' },
|
||||
{ text: 'XYChart 🔥', link: '/syntax/xychart' },
|
||||
{ text: 'XYChart 🔥', link: '/syntax/xyChart' },
|
||||
{ text: 'Other Examples', link: '/syntax/examples' },
|
||||
],
|
||||
},
|
||||
|
@@ -4,9 +4,8 @@
|
||||
|
||||
## First search to see if someone has already asked (and hopefully been answered) or suggested the same thing.
|
||||
|
||||
- Search in Discussions
|
||||
- Search in open Issues
|
||||
- Search in closed Issues
|
||||
- [Search in Discussions](https://github.com/orgs/mermaid-js/discussions)
|
||||
- [Search in Issues (Open & Closed)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue)
|
||||
|
||||
If you find an open issue or discussion thread that is similar to your question but isn't answered, you can let us know that you are also interested in it.
|
||||
Use the GitHub reactions to add a thumbs-up to the issue or discussion thread.
|
||||
|
@@ -4,8 +4,8 @@ When mermaid starts, configuration is extracted to determine a configuration to
|
||||
|
||||
- The default configuration
|
||||
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
|
||||
- Frontmatter (v10.5.0+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
|
||||
- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
- Frontmatter (v10.5.0+) - diagram authors can update selected configuration parameters in the frontmatter of the diagram. These are applied to the render config.
|
||||
- Directives (Deprecated by Frontmatter) - diagram authors can update selected configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
|
||||
**The render config** is configuration that is used when rendering by applying these configurations.
|
||||
|
||||
|
@@ -33,12 +33,19 @@ Below are a list of community plugins and integrations created with Mermaid.
|
||||
- [Notion](https://notion.so) ✅
|
||||
- [Observable](https://observablehq.com/@observablehq/mermaid) ✅
|
||||
- [Obsidian](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Diagram) ✅
|
||||
- [NotesHub](https://noteshub.app) ✅
|
||||
- [GitBook](https://gitbook.com)
|
||||
- [Mermaid Plugin](https://github.com/JozoVilcek/gitbook-plugin-mermaid)
|
||||
- [Markdown with Mermaid CLI](https://github.com/miao1007/gitbook-plugin-mermaid-cli)
|
||||
- [Mermaid plugin for GitBook](https://github.com/wwformat/gitbook-plugin-mermaid-pdf)
|
||||
- [LiveBook](https://livebook.dev) ✅
|
||||
- [Atlassian Products](https://www.atlassian.com)
|
||||
- [Mermaid for Confluence](https://marketplace.atlassian.com/apps/1224722/mermaid-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Mermaid Integration for Confluence](https://marketplace.atlassian.com/apps/1222792/mermaid-integration-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Mermaid Diagrams for Confluence](https://marketplace.atlassian.com/apps/1226945/mermaid-diagrams-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Mermaid Macro for Confluence](https://marketplace.atlassian.com/apps/1231150/mermaid-macro-for-confluence?hosting=cloud&tab=overview)
|
||||
- [EliteSoft Mermaid Charts and Diagrams](https://marketplace.atlassian.com/apps/1227286/elitesoft-mermaid-charts-and-diagrams?hosting=cloud&tab=overview)
|
||||
- [Mermaid for Jira Cloud - Draw UML diagrams easily](https://marketplace.atlassian.com/apps/1223053/mermaid-for-jira-cloud-draw-uml-diagrams-easily?hosting=cloud&tab=overview)
|
||||
- [Mermaid Charts & Diagrams for Confluence](https://marketplace.atlassian.com/apps/1222572/)
|
||||
- [Mermaid Charts & Diagrams for Jira](https://marketplace.atlassian.com/apps/1224537/)
|
||||
- [Mermaid Live Editor for Confluence Cloud](https://marketplace.atlassian.com/apps/1231571/mermaid-live-editor-for-confluence?hosting=cloud&tab=overview)
|
||||
@@ -97,6 +104,8 @@ Communication tools and platforms
|
||||
- [phpbb-ext-mermaid](https://github.com/AlfredoRamos/phpbb-ext-mermaid)
|
||||
- [NodeBB](https://nodebb.org)
|
||||
- [Mermaid Plugin](https://www.npmjs.com/package/nodebb-plugin-mermaid)
|
||||
- [Slack](https://slack.com)
|
||||
- [Mermaid for Slack](https://github.com/JackuB/mermaid-for-slack)
|
||||
|
||||
### Wikis
|
||||
|
||||
@@ -167,7 +176,8 @@ Communication tools and platforms
|
||||
|
||||
### Document Generation
|
||||
|
||||
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts)
|
||||
- [Docusaurus](https://docusaurus.io/docs/markdown-features/diagrams) ✅
|
||||
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-)
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/)
|
||||
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
|
||||
- [remark](https://remark.js.org/)
|
||||
|
@@ -97,7 +97,7 @@ To Deploy Mermaid:
|
||||
</script>
|
||||
```
|
||||
|
||||
**Doing so commands the mermaid parser to look for the `<div>` or `<pre>` tags with `class="mermaid"`. From these tags, mermaid tries read the diagram/chart definitions and render them into SVG charts.**
|
||||
**Doing so commands the mermaid parser to look for the `<div>` or `<pre>` tags with `class="mermaid"`. From these tags, mermaid tries to read the diagram/chart definitions and render them into SVG charts.**
|
||||
|
||||
**Examples can be found in** [Other examples](../syntax/examples.md)
|
||||
|
||||
|
@@ -1,17 +1,9 @@
|
||||
# Announcements
|
||||
|
||||
<br />
|
||||
Check out our latest blog posts below. See more blog posts [here](blog.md).
|
||||
|
||||
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
|
||||
|
||||
## Calling all fans of Mermaid and Mermaid Chart! 🎉
|
||||
14 November 2023 · 5 mins
|
||||
|
||||
We’ve officially made our Product Hunt debut, and would love any and all support from the community!
|
||||
|
||||
[Click here](https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart) to check out our Product Hunt launch.
|
||||
|
||||
Feel free to drop us a comment and let us know what you think. All new sign ups will receive a 30-day free trial of our Pro subscription, plus 25% off your first year.
|
||||
|
||||
We’re on a mission to make text-based diagramming fun again. And we need your help to make that happen.
|
||||
|
||||
Your support means the world to us. Thank you for being part of the diagramming movement.
|
||||
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.
|
||||
|
@@ -1,5 +1,23 @@
|
||||
# Blog
|
||||
|
||||
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
|
||||
|
||||
14 November 2023 · 5 mins
|
||||
|
||||
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.
|
||||
|
||||
## [How to Use Mermaid Chart as an AI Diagram Generator](https://www.mermaidchart.com/blog/posts/how-to-use-mermaid-chart-as-an-ai-diagram-generator/)
|
||||
|
||||
1 November 2023 · 5 mins
|
||||
|
||||
Would an AI diagram generator make your life easier?
|
||||
|
||||
## [Diagrams, Made Even Easier: Introducing “Code Snippets” in the Mermaid Chart Editor](https://www.mermaidchart.com/blog/posts/easier-diagram-editing-with-code-snippets/)
|
||||
|
||||
12 October 2023 · 4 mins
|
||||
|
||||
Mermaid Chart introduces Code Snippets in its editor, streamlining the diagramming process for developers and professionals.
|
||||
|
||||
## [How to Make a Git Graph with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-git-graph-with-mermaid-chart/)
|
||||
|
||||
22 September 2023 · 7 mins
|
||||
|
@@ -22,17 +22,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/carbon": "^1.1.16",
|
||||
"@unocss/reset": "^0.56.0",
|
||||
"@vite-pwa/vitepress": "^0.2.0",
|
||||
"@unocss/reset": "^0.57.0",
|
||||
"@vite-pwa/vitepress": "^0.3.0",
|
||||
"@vitejs/plugin-vue": "^4.2.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"https-localhost": "^4.7.1",
|
||||
"pathe": "^1.1.0",
|
||||
"unocss": "^0.56.0",
|
||||
"unocss": "^0.57.0",
|
||||
"unplugin-vue-components": "^0.25.0",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-pwa": "^0.16.0",
|
||||
"vitepress": "1.0.0-rc.22",
|
||||
"vite-plugin-pwa": "^0.17.0",
|
||||
"vitepress": "1.0.0-rc.29",
|
||||
"workbox-window": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
@@ -257,7 +257,7 @@ UpdateRelStyle(customerA, bankA, $offsetY="60")
|
||||
title Component diagram for Internet Banking System - API Application
|
||||
|
||||
Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.")
|
||||
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset ot the internet banking functionality to customers via their mobile mobile device.")
|
||||
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset to the internet banking functionality to customers via their mobile mobile device.")
|
||||
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
System_Ext(mbs, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
|
@@ -281,8 +281,6 @@ And `Link` can be one of:
|
||||
|
||||
A namespace groups classes.
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
|
@@ -293,7 +293,7 @@ flowchart TB
|
||||
A & B--> C & D
|
||||
```
|
||||
|
||||
If you describe the same diagram using the the basic syntax, it will take four lines. A
|
||||
If you describe the same diagram using the basic syntax, it will take four lines. A
|
||||
word of warning, one could go overboard with this making the flowchart harder to read in
|
||||
markdown form. The Swedish word `lagom` comes to mind. It means, not too much and not too little.
|
||||
This goes for expressive syntaxes as well.
|
||||
|
@@ -2,7 +2,7 @@ import mermaid from './mermaid.js';
|
||||
import { mermaidAPI } from './mermaidAPI.js';
|
||||
import './diagram-api/diagram-orchestration.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
import { beforeAll, describe, it, expect, vi } from 'vitest';
|
||||
import { beforeAll, describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import type { DiagramDefinition } from './diagram-api/types.js';
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -89,7 +89,7 @@ describe('when using mermaid and ', () => {
|
||||
).resolves.not.toThrow();
|
||||
// should still render, even if lazyLoadedDiagrams fails
|
||||
expect(mermaidAPI.render).toHaveBeenCalled();
|
||||
});
|
||||
}, 20_000);
|
||||
|
||||
it('should defer diagram load based on parameter', async () => {
|
||||
let loaded = false;
|
||||
|
@@ -38,8 +38,6 @@ import type { MermaidConfig } from './config.type.js';
|
||||
|
||||
import mermaidAPI, { removeExistingElements } from './mermaidAPI.js';
|
||||
import {
|
||||
encodeEntities,
|
||||
decodeEntities,
|
||||
createCssStyles,
|
||||
createUserStyles,
|
||||
appendDivSvgG,
|
||||
@@ -68,6 +66,7 @@ vi.mock('stylis', () => {
|
||||
};
|
||||
});
|
||||
import { compile, serialize } from 'stylis';
|
||||
import { decodeEntities, encodeEntities } from './utils.js';
|
||||
|
||||
/**
|
||||
* @see https://vitest.dev/guide/mocking.html Mock part of a module
|
||||
|
@@ -30,6 +30,7 @@ import isEmpty from 'lodash-es/isEmpty.js';
|
||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
|
||||
import type { DiagramStyleClassDef } from './diagram-api/types.js';
|
||||
import { preprocessDiagram } from './preprocess.js';
|
||||
import { decodeEntities } from './utils.js';
|
||||
|
||||
const MAX_TEXTLENGTH = 50_000;
|
||||
const MAX_TEXTLENGTH_EXCEEDED_MSG =
|
||||
@@ -110,43 +111,6 @@ async function parse(text: string, parseOptions?: ParseOptions): Promise<boolean
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text - text to be encoded
|
||||
* @returns
|
||||
*/
|
||||
export const encodeEntities = function (text: string): string {
|
||||
let txt = text;
|
||||
|
||||
txt = txt.replace(/style.*:\S*#.*;/g, function (s): string {
|
||||
return s.substring(0, s.length - 1);
|
||||
});
|
||||
txt = txt.replace(/classDef.*:\S*#.*;/g, function (s): string {
|
||||
return s.substring(0, s.length - 1);
|
||||
});
|
||||
|
||||
txt = txt.replace(/#\w+;/g, function (s) {
|
||||
const innerTxt = s.substring(1, s.length - 1);
|
||||
|
||||
const isInt = /^\+?\d+$/.test(innerTxt);
|
||||
if (isInt) {
|
||||
return 'fl°°' + innerTxt + '¶ß';
|
||||
} else {
|
||||
return 'fl°' + innerTxt + '¶ß';
|
||||
}
|
||||
});
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text - text to be decoded
|
||||
* @returns
|
||||
*/
|
||||
export const decodeEntities = function (text: string): string {
|
||||
return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';');
|
||||
};
|
||||
|
||||
// append !important; to each cssClass followed by a final !important, all enclosed in { }
|
||||
//
|
||||
/**
|
||||
@@ -436,8 +400,6 @@ const render = async function (
|
||||
appendDivSvgG(root, id, enclosingDivID);
|
||||
}
|
||||
|
||||
text = encodeEntities(text);
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Create the diagram
|
||||
|
||||
|
@@ -3,8 +3,8 @@
|
||||
import type { Group } from '../diagram-api/types.js';
|
||||
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
|
||||
import { log } from '../logger.js';
|
||||
import { decodeEntities } from '../mermaidAPI.js';
|
||||
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
|
||||
import { decodeEntities } from '../utils.js';
|
||||
import { splitLineToFitWidth } from './splitText.js';
|
||||
import type { MarkdownLine, MarkdownWord } from './types.js';
|
||||
|
||||
|
@@ -888,3 +888,40 @@ export default {
|
||||
parseFontSize,
|
||||
InitIDGenerator,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param text - text to be encoded
|
||||
* @returns
|
||||
*/
|
||||
export const encodeEntities = function (text: string): string {
|
||||
let txt = text;
|
||||
|
||||
txt = txt.replace(/style.*:\S*#.*;/g, function (s): string {
|
||||
return s.substring(0, s.length - 1);
|
||||
});
|
||||
txt = txt.replace(/classDef.*:\S*#.*;/g, function (s): string {
|
||||
return s.substring(0, s.length - 1);
|
||||
});
|
||||
|
||||
txt = txt.replace(/#\w+;/g, function (s) {
|
||||
const innerTxt = s.substring(1, s.length - 1);
|
||||
|
||||
const isInt = /^\+?\d+$/.test(innerTxt);
|
||||
if (isInt) {
|
||||
return 'fl°°' + innerTxt + '¶ß';
|
||||
} else {
|
||||
return 'fl°' + innerTxt + '¶ß';
|
||||
}
|
||||
});
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text - text to be decoded
|
||||
* @returns
|
||||
*/
|
||||
export const decodeEntities = function (text: string): string {
|
||||
return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';');
|
||||
};
|
||||
|
@@ -19,9 +19,12 @@ const markerOffsets = {
|
||||
* @returns The angle, deltaX and deltaY
|
||||
*/
|
||||
function calculateDeltaAndAngle(
|
||||
point1: Point | [number, number],
|
||||
point2: Point | [number, number]
|
||||
point1?: Point | [number, number],
|
||||
point2?: Point | [number, number]
|
||||
): { angle: number; deltaX: number; deltaY: number } {
|
||||
if (point1 === undefined || point2 === undefined) {
|
||||
return { angle: 0, deltaX: 0, deltaY: 0 };
|
||||
}
|
||||
point1 = pointTransformer(point1);
|
||||
point2 = pointTransformer(point2);
|
||||
const [x1, y1] = [point1.x, point1.y];
|
||||
@@ -90,3 +93,44 @@ export const getLineFunctionsWithOffset = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { it, expect, describe } = import.meta.vitest;
|
||||
describe('calculateDeltaAndAngle', () => {
|
||||
it('should calculate the angle and deltas between two points', () => {
|
||||
expect(calculateDeltaAndAngle([0, 0], [0, 1])).toStrictEqual({
|
||||
angle: 1.5707963267948966,
|
||||
deltaX: 0,
|
||||
deltaY: 1,
|
||||
});
|
||||
expect(calculateDeltaAndAngle([1, 0], [0, -1])).toStrictEqual({
|
||||
angle: 0.7853981633974483,
|
||||
deltaX: -1,
|
||||
deltaY: -1,
|
||||
});
|
||||
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, [0, -1])).toStrictEqual({
|
||||
angle: 0.7853981633974483,
|
||||
deltaX: -1,
|
||||
deltaY: -1,
|
||||
});
|
||||
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, { x: 1, y: 0 })).toStrictEqual({
|
||||
angle: NaN,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should calculate the angle and deltas if one point in undefined', () => {
|
||||
expect(calculateDeltaAndAngle(undefined, [0, 1])).toStrictEqual({
|
||||
angle: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
});
|
||||
expect(calculateDeltaAndAngle([0, 1], undefined)).toStrictEqual({
|
||||
angle: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -2,7 +2,8 @@
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./package.json"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user