Merge branch 'next' into sidv/tinyMermaid

* next:
  chore: Move SVG import to comment.
  build docs
  Remove whitespace on empty line
  chore: Fix minify
  Documentation for #2509
  Update all minor dependencies
  Update all patch dependencies
  make more `RectData` required and remove optional assignment
  use lineBreakRegex in `svgDrawCommon`
  fix svgDrawCommon import by adding `.js`
  add types to `svgDrawCommon.ts`
  convert `svgDrawCommon` to TS
This commit is contained in:
Sidharth Vinod
2023-08-17 12:41:11 +05:30
17 changed files with 4122 additions and 3356 deletions

View File

@@ -60,7 +60,7 @@ const getFileName = (
}; };
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => { export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
const { core, entryName, metafile, format, includeLargeDiagrams } = options; const { core, entryName, metafile, format, includeLargeDiagrams, minify } = options;
const external: string[] = ['require', 'fs', 'path']; const external: string[] = ['require', 'fs', 'path'];
const { name, file, packageName } = packageOptions[entryName]; const { name, file, packageName } = packageOptions[entryName];
const outFileName = getFileName(name, options); const outFileName = getFileName(name, options);
@@ -70,6 +70,7 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
[outFileName]: `src/${file}`, [outFileName]: `src/${file}`,
}, },
metafile, metafile,
minify,
logLevel: 'info', logLevel: 'info',
chunkNames: `chunks/${outFileName}/[name]-[hash]`, chunkNames: `chunks/${outFileName}/[name]-[hash]`,
define: { define: {

View File

@@ -1,7 +1,7 @@
version: '3.9' version: '3.9'
services: services:
mermaid: mermaid:
image: node:18.17.0-alpine3.18 image: node:18.17.1-alpine3.18
stdin_open: true stdin_open: true
tty: true tty: true
working_dir: /mermaid working_dir: /mermaid
@@ -17,7 +17,7 @@ services:
- 9000:9000 - 9000:9000
- 3333:3333 - 3333:3333
cypress: cypress:
image: cypress/included:12.17.2 image: cypress/included:12.17.3
stdin_open: true stdin_open: true
tty: true tty: true
working_dir: /mermaid working_dir: /mermaid

View File

@@ -748,6 +748,48 @@ flowchart LR
B1 --> B2 B1 --> B2
``` ```
#### Limitation
If any of a subgraph's nodes are linked to the outside, subgraph direction will be ignored. Instead the subgraph will inherit the direction of the parent graph:
```mermaid-example
flowchart LR
subgraph subgraph1
direction TB
top1[top] --> bottom1[bottom]
end
subgraph subgraph2
direction TB
top2[top] --> bottom2[bottom]
end
%% ^ These subgraphs are identical, except for the links to them:
%% Link *to* subgraph1: subgraph1 direction is mantained
outside --> subgraph1
%% Link *within* subgraph2:
%% subgraph2 inherits the direction of the top-level graph (LR)
outside ---> top2
```
```mermaid
flowchart LR
subgraph subgraph1
direction TB
top1[top] --> bottom1[bottom]
end
subgraph subgraph2
direction TB
top2[top] --> bottom2[bottom]
end
%% ^ These subgraphs are identical, except for the links to them:
%% Link *to* subgraph1: subgraph1 direction is mantained
outside --> subgraph1
%% Link *within* subgraph2:
%% subgraph2 inherits the direction of the top-level graph (LR)
outside ---> top2
```
## Markdown Strings ## Markdown Strings
The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels. The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.

View File

@@ -4,7 +4,7 @@
"version": "10.2.4", "version": "10.2.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module", "type": "module",
"packageManager": "pnpm@8.6.11", "packageManager": "pnpm@8.6.12",
"keywords": [ "keywords": [
"diagram", "diagram",
"markdown", "markdown",
@@ -78,9 +78,9 @@
"@types/rollup-plugin-visualizer": "^4.2.1", "@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0", "@typescript-eslint/parser": "^5.59.0",
"@vitest/coverage-v8": "^0.33.0", "@vitest/coverage-v8": "^0.34.0",
"@vitest/spy": "^0.33.0", "@vitest/spy": "^0.34.0",
"@vitest/ui": "^0.33.0", "@vitest/ui": "^0.34.0",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"concurrently": "^8.0.1", "concurrently": "^8.0.1",
@@ -120,10 +120,10 @@
"typescript": "^5.1.3", "typescript": "^5.1.3",
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-istanbul": "^4.1.0", "vite-plugin-istanbul": "^4.1.0",
"vitest": "^0.33.0" "vitest": "^0.34.0"
}, },
"volta": { "volta": {
"node": "18.17.0" "node": "18.17.1"
}, },
"nyc": { "nyc": {
"report-dir": "coverage/cypress" "report-dir": "coverage/cypress"

View File

@@ -1,5 +1,5 @@
import common from '../common/common.js'; import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon'; import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';
export const drawRect = function (elem, rectData) { export const drawRect = function (elem, rectData) {

View File

@@ -1,15 +1,15 @@
import { sanitizeText, removeScript, parseGenericTypes } from './common.js'; import { sanitizeText, removeScript, parseGenericTypes } from './common.js';
describe('when securityLevel is antiscript, all script must be removed', function () { describe('when securityLevel is antiscript, all script must be removed', () => {
/** /**
* @param {string} original The original text * @param original - The original text
* @param {string} result The expected sanitized text * @param result - The expected sanitized text
*/ */
function compareRemoveScript(original, result) { function compareRemoveScript(original: string, result: string) {
expect(removeScript(original).trim()).toEqual(result); expect(removeScript(original).trim()).toEqual(result);
} }
it('should remove all script block, script inline.', function () { it('should remove all script block, script inline.', () => {
const labelString = `1 const labelString = `1
Act1: Hello 1<script src="http://abc.com/script1.js"></script>1 Act1: Hello 1<script src="http://abc.com/script1.js"></script>1
<b>Act2</b>: <b>Act2</b>:
@@ -25,7 +25,7 @@ describe('when securityLevel is antiscript, all script must be removed', functio
compareRemoveScript(labelString, exactlyString); compareRemoveScript(labelString, exactlyString);
}); });
it('should remove all javascript urls', function () { it('should remove all javascript urls', () => {
compareRemoveScript( compareRemoveScript(
`This is a <a href="javascript:runHijackingScript();">clean link</a> + <a href="javascript:runHijackingScript();">clean link</a> `This is a <a href="javascript:runHijackingScript();">clean link</a> + <a href="javascript:runHijackingScript();">clean link</a>
and <a href="javascript&colon;bipassedMining();">me too</a>`, and <a href="javascript&colon;bipassedMining();">me too</a>`,
@@ -34,11 +34,11 @@ describe('when securityLevel is antiscript, all script must be removed', functio
); );
}); });
it('should detect malicious images', function () { it('should detect malicious images', () => {
compareRemoveScript(`<img onerror="alert('hello');">`, `<img>`); compareRemoveScript(`<img onerror="alert('hello');">`, `<img>`);
}); });
it('should detect iframes', function () { it('should detect iframes', () => {
compareRemoveScript( compareRemoveScript(
`<iframe src="http://abc.com/script1.js"></iframe> `<iframe src="http://abc.com/script1.js"></iframe>
<iframe src="http://example.com/iframeexample"></iframe>`, <iframe src="http://example.com/iframeexample"></iframe>`,
@@ -47,8 +47,8 @@ describe('when securityLevel is antiscript, all script must be removed', functio
}); });
}); });
describe('Sanitize text', function () { describe('Sanitize text', () => {
it('should remove script tag', function () { it('should remove script tag', () => {
const maliciousStr = 'javajavascript:script:alert(1)'; const maliciousStr = 'javajavascript:script:alert(1)';
const result = sanitizeText(maliciousStr, { const result = sanitizeText(maliciousStr, {
securityLevel: 'strict', securityLevel: 'strict',
@@ -58,8 +58,8 @@ describe('Sanitize text', function () {
}); });
}); });
describe('generic parser', function () { describe('generic parser', () => {
it('should parse generic types', function () { it('should parse generic types', () => {
expect(parseGenericTypes('test~T~')).toEqual('test<T>'); expect(parseGenericTypes('test~T~')).toEqual('test<T>');
expect(parseGenericTypes('test~Array~Array~string~~~')).toEqual('test<Array<Array<string>>>'); expect(parseGenericTypes('test~Array~Array~string~~~')).toEqual('test<Array<Array<string>>>');
expect(parseGenericTypes('test~Array~Array~string[]~~~')).toEqual( expect(parseGenericTypes('test~Array~Array~string[]~~~')).toEqual(

View File

@@ -1,6 +1,7 @@
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { MermaidConfig } from '../../config.type.js'; import { MermaidConfig } from '../../config.type.js';
// Remove and ignore br:s
export const lineBreakRegex = /<br\s*\/?>/gi; export const lineBreakRegex = /<br\s*\/?>/gi;
/** /**

View File

@@ -0,0 +1,58 @@
export interface RectData {
x: number;
y: number;
fill: string;
width: number;
height: number;
stroke: string;
class?: string;
color?: string;
rx?: number;
ry?: number;
attrs?: Record<string, string | number>;
anchor?: string;
}
export interface Bound {
startx: number;
stopx: number;
starty: number;
stopy: number;
fill: string;
stroke: string;
}
export interface TextData {
x: number;
y: number;
anchor: string;
text: string;
textMargin: number;
class?: string;
}
export interface TextObject {
x: number;
y: number;
width: number;
height: number;
fill?: string;
anchor?: string;
'text-anchor': string;
style: string;
textMargin: number;
rx: number;
ry: number;
tspan: boolean;
valign?: string;
}
export type D3RectElement = d3.Selection<SVGRectElement, unknown, Element | null, unknown>;
export type D3UseElement = d3.Selection<SVGUseElement, unknown, Element | null, unknown>;
export type D3ImageElement = d3.Selection<SVGImageElement, unknown, Element | null, unknown>;
export type D3TextElement = d3.Selection<SVGTextElement, unknown, Element | null, unknown>;
export type D3TSpanElement = d3.Selection<SVGTSpanElement, unknown, Element | null, unknown>;

View File

@@ -1,114 +0,0 @@
import { sanitizeUrl } from '@braintree/sanitize-url';
export const drawRect = function (elem, rectData) {
const rectElem = elem.append('rect');
rectElem.attr('x', rectData.x);
rectElem.attr('y', rectData.y);
rectElem.attr('fill', rectData.fill);
rectElem.attr('stroke', rectData.stroke);
rectElem.attr('width', rectData.width);
rectElem.attr('height', rectData.height);
rectElem.attr('rx', rectData.rx);
rectElem.attr('ry', rectData.ry);
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
for (let attrKey in rectData.attrs) {
rectElem.attr(attrKey, rectData.attrs[attrKey]);
}
}
if (rectData.class !== 'undefined') {
rectElem.attr('class', rectData.class);
}
return rectElem;
};
/**
* Draws a background rectangle
*
* @param {any} elem Diagram (reference for bounds)
* @param {any} bounds Shape of the rectangle
*/
export const drawBackgroundRect = function (elem, bounds) {
const rectElem = drawRect(elem, {
x: bounds.startx,
y: bounds.starty,
width: bounds.stopx - bounds.startx,
height: bounds.stopy - bounds.starty,
fill: bounds.fill,
stroke: bounds.stroke,
class: 'rect',
});
rectElem.lower();
};
export const drawText = function (elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.attr('class', 'legend');
textElem.style('text-anchor', textData.anchor);
if (textData.class !== undefined) {
textElem.attr('class', textData.class);
}
const span = textElem.append('tspan');
span.attr('x', textData.x + textData.textMargin * 2);
span.text(nText);
return textElem;
};
export const drawImage = function (elem, x, y, link) {
const imageElem = elem.append('image');
imageElem.attr('x', x);
imageElem.attr('y', y);
var sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', sanitizedLink);
};
export const drawEmbeddedImage = function (elem, x, y, link) {
const imageElem = elem.append('use');
imageElem.attr('x', x);
imageElem.attr('y', y);
const sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', '#' + sanitizedLink);
};
export const getNoteRect = function () {
return {
x: 0,
y: 0,
width: 100,
height: 100,
fill: '#EDF2AE',
stroke: '#666',
anchor: 'start',
rx: 0,
ry: 0,
};
};
export const getTextObj = function () {
return {
x: 0,
y: 0,
width: 100,
height: 100,
fill: undefined,
anchor: undefined,
'text-anchor': 'start',
style: '#666',
textMargin: 0,
rx: 0,
ry: 0,
tspan: true,
valign: undefined,
};
};

View File

@@ -0,0 +1,126 @@
import { sanitizeUrl } from '@braintree/sanitize-url';
import type { Group, SVG } from '../../diagram-api/types.js';
import type {
Bound,
D3ImageElement,
D3RectElement,
D3TSpanElement,
D3TextElement,
D3UseElement,
RectData,
TextData,
TextObject,
} from './commonTypes.js';
import { lineBreakRegex } from './common.js';
export const drawRect = (element: SVG | Group, rectData: RectData): D3RectElement => {
const rectElement: D3RectElement = element.append('rect');
rectElement.attr('x', rectData.x);
rectElement.attr('y', rectData.y);
rectElement.attr('fill', rectData.fill);
rectElement.attr('stroke', rectData.stroke);
rectElement.attr('width', rectData.width);
rectElement.attr('height', rectData.height);
rectData.rx !== undefined && rectElement.attr('rx', rectData.rx);
rectData.ry !== undefined && rectElement.attr('ry', rectData.ry);
if (rectData.attrs !== undefined) {
for (const attrKey in rectData.attrs) {
rectElement.attr(attrKey, rectData.attrs[attrKey]);
}
}
rectData.class !== undefined && rectElement.attr('class', rectData.class);
return rectElement;
};
/**
* Draws a background rectangle
*
* @param element - Diagram (reference for bounds)
* @param bounds - Shape of the rectangle
*/
export const drawBackgroundRect = (element: SVG | Group, bounds: Bound): void => {
const rectData: RectData = {
x: bounds.startx,
y: bounds.starty,
width: bounds.stopx - bounds.startx,
height: bounds.stopy - bounds.starty,
fill: bounds.fill,
stroke: bounds.stroke,
class: 'rect',
};
const rectElement: D3RectElement = drawRect(element, rectData);
rectElement.lower();
};
export const drawText = (element: SVG | Group, textData: TextData): D3TextElement => {
const nText: string = textData.text.replace(lineBreakRegex, ' ');
const textElem: D3TextElement = element.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.attr('class', 'legend');
textElem.style('text-anchor', textData.anchor);
textData.class !== undefined && textElem.attr('class', textData.class);
const tspan: D3TSpanElement = textElem.append('tspan');
tspan.attr('x', textData.x + textData.textMargin * 2);
tspan.text(nText);
return textElem;
};
export const drawImage = (elem: SVG | Group, x: number, y: number, link: string): void => {
const imageElement: D3ImageElement = elem.append('image');
imageElement.attr('x', x);
imageElement.attr('y', y);
const sanitizedLink: string = sanitizeUrl(link);
imageElement.attr('xlink:href', sanitizedLink);
};
export const drawEmbeddedImage = (
element: SVG | Group,
x: number,
y: number,
link: string
): void => {
const imageElement: D3UseElement = element.append('use');
imageElement.attr('x', x);
imageElement.attr('y', y);
const sanitizedLink: string = sanitizeUrl(link);
imageElement.attr('xlink:href', `#${sanitizedLink}`);
};
export const getNoteRect = (): RectData => {
const noteRectData: RectData = {
x: 0,
y: 0,
width: 100,
height: 100,
fill: '#EDF2AE',
stroke: '#666',
anchor: 'start',
rx: 0,
ry: 0,
};
return noteRectData;
};
export const getTextObj = (): TextObject => {
const testObject: TextObject = {
x: 0,
y: 0,
width: 100,
height: 100,
'text-anchor': 'start',
style: '#666',
textMargin: 0,
rx: 0,
ry: 0,
tspan: true,
};
return testObject;
};

View File

@@ -3,7 +3,7 @@ import { select, selectAll } from 'd3';
import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js'; import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import common from '../common/common.js'; import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon'; import * as svgDrawCommon from '../common/svgDrawCommon.js';
import * as configApi from '../../config.js'; import * as configApi from '../../config.js';
import assignWithDepth from '../../assignWithDepth.js'; import assignWithDepth from '../../assignWithDepth.js';
import utils from '../../utils.js'; import utils from '../../utils.js';

View File

@@ -1,5 +1,5 @@
import common from '../common/common.js'; import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon'; import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { addFunction } from '../../interactionDb.js'; import { addFunction } from '../../interactionDb.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';

View File

@@ -1,5 +1,5 @@
import { arc as d3arc } from 'd3'; import { arc as d3arc } from 'd3';
import * as svgDrawCommon from '../common/svgDrawCommon'; import * as svgDrawCommon from '../common/svgDrawCommon.js';
export const drawRect = function (elem, rectData) { export const drawRect = function (elem, rectData) {
return svgDrawCommon.drawRect(elem, rectData); return svgDrawCommon.drawRect(elem, rectData);

View File

@@ -21,17 +21,17 @@
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/carbon": "^1.1.16", "@iconify-json/carbon": "^1.1.16",
"@unocss/reset": "^0.54.0", "@unocss/reset": "^0.55.0",
"@vite-pwa/vitepress": "^0.2.0", "@vite-pwa/vitepress": "^0.2.0",
"@vitejs/plugin-vue": "^4.2.1", "@vitejs/plugin-vue": "^4.2.1",
"fast-glob": "^3.2.12", "fast-glob": "^3.2.12",
"https-localhost": "^4.7.1", "https-localhost": "^4.7.1",
"pathe": "^1.1.0", "pathe": "^1.1.0",
"unocss": "^0.54.0", "unocss": "^0.55.0",
"unplugin-vue-components": "^0.25.0", "unplugin-vue-components": "^0.25.0",
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-pwa": "^0.16.0", "vite-plugin-pwa": "^0.16.0",
"vitepress": "1.0.0-beta.7", "vitepress": "1.0.0-rc.4",
"workbox-window": "^7.0.0" "workbox-window": "^7.0.0"
} }
} }

View File

@@ -471,6 +471,29 @@ flowchart LR
B1 --> B2 B1 --> B2
``` ```
#### Limitation
If any of a subgraph's nodes are linked to the outside, subgraph direction will be ignored. Instead the subgraph will inherit the direction of the parent graph:
```mermaid-example
flowchart LR
subgraph subgraph1
direction TB
top1[top] --> bottom1[bottom]
end
subgraph subgraph2
direction TB
top2[top] --> bottom2[bottom]
end
%% ^ These subgraphs are identical, except for the links to them:
%% Link *to* subgraph1: subgraph1 direction is mantained
outside --> subgraph1
%% Link *within* subgraph2:
%% subgraph2 inherits the direction of the top-level graph (LR)
outside ---> top2
```
## Markdown Strings ## Markdown Strings
The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels. The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.

View File

@@ -1,5 +1,4 @@
import { log } from './logger.js'; import { log } from './logger.js';
import { SVG } from './diagram-api/types.js';
/** /**
* Applies d3 attributes * Applies d3 attributes
@@ -36,7 +35,7 @@ export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
/** /**
* Applies attributes from `calculateSvgSizeAttrs` * Applies attributes from `calculateSvgSizeAttrs`
* *
* @param {SVG} svgElem The SVG Element to configure * @param {import('./diagram-api/types.js').SVG} svgElem The SVG Element to configure
* @param {number} height The height of the SVG * @param {number} height The height of the SVG
* @param {number} width The width of the SVG * @param {number} width The width of the SVG
* @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100% * @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%

7054
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff