diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index 5ca2f55f81..9ec7422613 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -1,5 +1,5 @@ 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'; export const drawRect = function (elem, rectData) { diff --git a/packages/mermaid/src/diagrams/common/common.spec.js b/packages/mermaid/src/diagrams/common/common.spec.ts similarity index 76% rename from packages/mermaid/src/diagrams/common/common.spec.js rename to packages/mermaid/src/diagrams/common/common.spec.ts index d1c68e8926..cfef5d58d3 100644 --- a/packages/mermaid/src/diagrams/common/common.spec.js +++ b/packages/mermaid/src/diagrams/common/common.spec.ts @@ -1,15 +1,15 @@ 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 {string} result The expected sanitized text + * @param original - The original text + * @param result - The expected sanitized text */ - function compareRemoveScript(original, result) { + function compareRemoveScript(original: string, result: string) { 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 Act1: Hello 11 Act2: @@ -25,7 +25,7 @@ describe('when securityLevel is antiscript, all script must be removed', functio compareRemoveScript(labelString, exactlyString); }); - it('should remove all javascript urls', function () { + it('should remove all javascript urls', () => { compareRemoveScript( `This is a clean link + clean link and me too`, @@ -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(``, ``); }); - it('should detect iframes', function () { + it('should detect iframes', () => { compareRemoveScript( ` `, @@ -47,8 +47,8 @@ describe('when securityLevel is antiscript, all script must be removed', functio }); }); -describe('Sanitize text', function () { - it('should remove script tag', function () { +describe('Sanitize text', () => { + it('should remove script tag', () => { const maliciousStr = 'javajavascript:script:alert(1)'; const result = sanitizeText(maliciousStr, { securityLevel: 'strict', @@ -58,8 +58,8 @@ describe('Sanitize text', function () { }); }); -describe('generic parser', function () { - it('should parse generic types', function () { +describe('generic parser', () => { + it('should parse generic types', () => { expect(parseGenericTypes('test~T~')).toEqual('test'); expect(parseGenericTypes('test~Array~Array~string~~~')).toEqual('test>>'); expect(parseGenericTypes('test~Array~Array~string[]~~~')).toEqual( diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 243c0cbf25..24591642b2 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -1,6 +1,7 @@ import DOMPurify from 'dompurify'; import { MermaidConfig } from '../../config.type.js'; +// Remove and ignore br:s export const lineBreakRegex = //gi; /** diff --git a/packages/mermaid/src/diagrams/common/commonTypes.ts b/packages/mermaid/src/diagrams/common/commonTypes.ts new file mode 100644 index 0000000000..84c26db6e1 --- /dev/null +++ b/packages/mermaid/src/diagrams/common/commonTypes.ts @@ -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; + 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; + +export type D3UseElement = d3.Selection; + +export type D3ImageElement = d3.Selection; + +export type D3TextElement = d3.Selection; + +export type D3TSpanElement = d3.Selection; diff --git a/packages/mermaid/src/diagrams/common/svgDrawCommon.js b/packages/mermaid/src/diagrams/common/svgDrawCommon.js deleted file mode 100644 index 9a4ce8aa2b..0000000000 --- a/packages/mermaid/src/diagrams/common/svgDrawCommon.js +++ /dev/null @@ -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(//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, - }; -}; diff --git a/packages/mermaid/src/diagrams/common/svgDrawCommon.ts b/packages/mermaid/src/diagrams/common/svgDrawCommon.ts new file mode 100644 index 0000000000..706d43ab9d --- /dev/null +++ b/packages/mermaid/src/diagrams/common/svgDrawCommon.ts @@ -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; +}; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index f6fde5001d..feee7157f7 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -3,7 +3,7 @@ import { select, selectAll } from 'd3'; import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js'; import { log } from '../../logger.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 assignWithDepth from '../../assignWithDepth.js'; import utils from '../../utils.js'; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 0c7bc64213..e0aaa1eb93 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -1,5 +1,5 @@ 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 { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js'; import { sanitizeUrl } from '@braintree/sanitize-url'; diff --git a/packages/mermaid/src/diagrams/user-journey/svgDraw.js b/packages/mermaid/src/diagrams/user-journey/svgDraw.js index 108f4b2f96..7a8f791fac 100644 --- a/packages/mermaid/src/diagrams/user-journey/svgDraw.js +++ b/packages/mermaid/src/diagrams/user-journey/svgDraw.js @@ -1,5 +1,5 @@ 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) { return svgDrawCommon.drawRect(elem, rectData);