diff --git a/cSpell.json b/cSpell.json index 4b8772347e..fc5eb6f9dc 100644 --- a/cSpell.json +++ b/cSpell.json @@ -28,6 +28,7 @@ "codedoc", "codemia", "colour", + "colours", "commitlint", "cpettitt", "customizability", diff --git a/package.json b/package.json index 82dcccd548..caf4e07e04 100644 --- a/package.json +++ b/package.json @@ -124,5 +124,10 @@ }, "nyc": { "report-dir": "coverage/cypress" + }, + "pnpm": { + "patchedDependencies": { + "cytoscape@3.28.1": "patches/cytoscape@3.28.1.patch" + } } } diff --git a/packages/mermaid-example-diagram/package.json b/packages/mermaid-example-diagram/package.json index dc468a6c73..63e8aadea9 100644 --- a/packages/mermaid-example-diagram/package.json +++ b/packages/mermaid-example-diagram/package.json @@ -39,15 +39,10 @@ }, "dependencies": { "@braintree/sanitize-url": "^6.0.1", - "cytoscape": "^3.23.0", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", "d3": "^7.0.0", - "khroma": "^2.0.0", - "non-layered-tidy-tree-layout": "^2.0.2" + "khroma": "^2.0.0" }, "devDependencies": { - "@types/cytoscape": "^3.19.9", "concurrently": "^8.0.0", "rimraf": "^5.0.0", "mermaid": "workspace:*" diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index babcc57f34..551e44ed93 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -62,9 +62,8 @@ "@braintree/sanitize-url": "^6.0.1", "@types/d3-scale": "^4.0.3", "@types/d3-scale-chromatic": "^3.0.0", - "cytoscape": "^3.23.0", + "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", diff --git a/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts b/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts index b352144359..66b44b4f9f 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts @@ -1,12 +1,13 @@ // @ts-ignore: JISON doesn't support types -import mindmapParser from './parser/mindmap.jison'; -import * as mindmapDb from './mindmapDb.js'; -import mindmapRenderer from './mindmapRenderer.js'; -import mindmapStyles from './styles.js'; +import parser from './parser/mindmap.jison'; +import db from './mindmapDb.js'; +import renderer from './mindmapRenderer.js'; +import styles from './styles.js'; +import type { DiagramDefinition } from '../../diagram-api/types.js'; -export const diagram = { - db: mindmapDb, - renderer: mindmapRenderer, - parser: mindmapParser, - styles: mindmapStyles, +export const diagram: DiagramDefinition = { + db, + renderer, + parser, + styles, }; diff --git a/packages/mermaid/src/diagrams/mindmap/mindmap.spec.js b/packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts similarity index 88% rename from packages/mermaid/src/diagrams/mindmap/mindmap.spec.js rename to packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts index c0b72060d9..2d67fc600a 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmap.spec.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmap.spec.ts @@ -1,5 +1,6 @@ +// @ts-expect-error No types available for JISON import { parser as mindmap } from './parser/mindmap.jison'; -import * as mindmapDB from './mindmapDb.js'; +import mindmapDB from './mindmapDb.js'; // Todo fix utils functions for tests import { setLogLevel } from '../../diagram-api/diagramAPI.js'; @@ -11,7 +12,7 @@ describe('when parsing a mindmap ', function () { }); describe('hiearchy', function () { it('MMP-1 should handle a simple root definition abc122', function () { - let str = `mindmap + const str = `mindmap root`; mindmap.parse(str); @@ -19,7 +20,7 @@ describe('when parsing a mindmap ', function () { expect(mindmap.yy.getMindmap().descr).toEqual('root'); }); it('MMP-2 should handle a hierachial mindmap definition', function () { - let str = `mindmap + const str = `mindmap root child1 child2 @@ -34,7 +35,7 @@ describe('when parsing a mindmap ', function () { }); it('3 should handle a simple root definition with a shape and without an id abc123', function () { - let str = `mindmap + const str = `mindmap (root)`; mindmap.parse(str); @@ -43,7 +44,7 @@ describe('when parsing a mindmap ', function () { }); it('MMP-4 should handle a deeper hierachial mindmap definition', function () { - let str = `mindmap + const str = `mindmap root child1 leaf1 @@ -58,40 +59,27 @@ describe('when parsing a mindmap ', function () { expect(mm.children[1].descr).toEqual('child2'); }); it('5 Multiple roots are illegal', function () { - let str = `mindmap + const str = `mindmap root fakeRoot`; - try { - mindmap.parse(str); - // Fail test if above expression doesn't throw anything. - expect(true).toBe(false); - } catch (e) { - expect(e.message).toBe( - 'There can be only one root. No parent could be found for ("fakeRoot")' - ); - } + expect(() => mindmap.parse(str)).toThrow( + 'There can be only one root. No parent could be found for ("fakeRoot")' + ); }); it('MMP-6 real root in wrong place', function () { - let str = `mindmap + const str = `mindmap root fakeRoot realRootWrongPlace`; - - try { - mindmap.parse(str); - // Fail test if above expression doesn't throw anything. - expect(true).toBe(false); - } catch (e) { - expect(e.message).toBe( - 'There can be only one root. No parent could be found for ("fakeRoot")' - ); - } + expect(() => mindmap.parse(str)).toThrow( + 'There can be only one root. No parent could be found for ("fakeRoot")' + ); }); }); describe('nodes', function () { it('MMP-7 should handle an id and type for a node definition', function () { - let str = `mindmap + const str = `mindmap root[The root] `; @@ -102,7 +90,7 @@ describe('when parsing a mindmap ', function () { expect(mm.type).toEqual(mindmap.yy.nodeType.RECT); }); it('MMP-8 should handle an id and type for a node definition', function () { - let str = `mindmap + const str = `mindmap root theId(child1)`; @@ -116,7 +104,7 @@ describe('when parsing a mindmap ', function () { expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT); }); it('MMP-9 should handle an id and type for a node definition', function () { - let str = `mindmap + const str = `mindmap root theId(child1)`; @@ -130,7 +118,7 @@ root expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT); }); it('MMP-10 multiple types (circle)', function () { - let str = `mindmap + const str = `mindmap root((the root)) `; @@ -142,7 +130,7 @@ root }); it('MMP-11 multiple types (cloud)', function () { - let str = `mindmap + const str = `mindmap root)the root( `; @@ -153,7 +141,7 @@ root expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD); }); it('MMP-12 multiple types (bang)', function () { - let str = `mindmap + const str = `mindmap root))the root(( `; @@ -165,7 +153,7 @@ root }); it('MMP-12-a multiple types (hexagon)', function () { - let str = `mindmap + const str = `mindmap root{{the root}} `; @@ -178,7 +166,7 @@ root }); describe('decorations', function () { it('MMP-13 should be possible to set an icon for the node', function () { - let str = `mindmap + const str = `mindmap root[The root] ::icon(bomb) `; @@ -192,7 +180,7 @@ root expect(mm.icon).toEqual('bomb'); }); it('MMP-14 should be possible to set classes for the node', function () { - let str = `mindmap + const str = `mindmap root[The root] :::m-4 p-8 `; @@ -206,7 +194,7 @@ root expect(mm.class).toEqual('m-4 p-8'); }); it('MMP-15 should be possible to set both classes and icon for the node', function () { - let str = `mindmap + const str = `mindmap root[The root] :::m-4 p-8 ::icon(bomb) @@ -222,7 +210,7 @@ root expect(mm.icon).toEqual('bomb'); }); it('MMP-16 should be possible to set both classes and icon for the node', function () { - let str = `mindmap + const str = `mindmap root[The root] ::icon(bomb) :::m-4 p-8 @@ -240,7 +228,7 @@ root }); describe('descriptions', function () { it('MMP-17 should be possible to use node syntax in the descriptions', function () { - let str = `mindmap + const str = `mindmap root["String containing []"] `; mindmap.parse(str); @@ -249,7 +237,7 @@ root expect(mm.descr).toEqual('String containing []'); }); it('MMP-18 should be possible to use node syntax in the descriptions in children', function () { - let str = `mindmap + const str = `mindmap root["String containing []"] child1["String containing ()"] `; @@ -261,7 +249,7 @@ root expect(mm.children[0].descr).toEqual('String containing ()'); }); it('MMP-19 should be possible to have a child after a class assignment', function () { - let str = `mindmap + const str = `mindmap root(Root) Child(Child) :::hot @@ -281,7 +269,7 @@ root }); }); it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () { - let str = `mindmap + const str = `mindmap root(Root) Child(Child) a(a) @@ -300,7 +288,7 @@ root expect(child.children[1].nodeId).toEqual('b'); }); it('MMP-21 should be possible to have comments in a mindmap', function () { - let str = `mindmap + const str = `mindmap root(Root) Child(Child) a(a) @@ -321,7 +309,7 @@ root }); it('MMP-22 should be possible to have comments at the end of a line', function () { - let str = `mindmap + const str = `mindmap root(Root) Child(Child) a(a) %% This is a comment @@ -339,7 +327,7 @@ root expect(child.children[1].nodeId).toEqual('b'); }); it('MMP-23 Rows with only spaces should not interfere', function () { - let str = 'mindmap\nroot\n A\n \n\n B'; + const str = 'mindmap\nroot\n A\n \n\n B'; mindmap.parse(str); const mm = mindmap.yy.getMindmap(); expect(mm.nodeId).toEqual('root'); @@ -351,7 +339,7 @@ root expect(child2.nodeId).toEqual('B'); }); it('MMP-24 Handle rows above the mindmap declarations', function () { - let str = '\n \nmindmap\nroot\n A\n \n\n B'; + const str = '\n \nmindmap\nroot\n A\n \n\n B'; mindmap.parse(str); const mm = mindmap.yy.getMindmap(); expect(mm.nodeId).toEqual('root'); @@ -363,7 +351,7 @@ root expect(child2.nodeId).toEqual('B'); }); it('MMP-25 Handle rows above the mindmap declarations, no space', function () { - let str = '\n\n\nmindmap\nroot\n A\n \n\n B'; + const str = '\n\n\nmindmap\nroot\n A\n \n\n B'; mindmap.parse(str); const mm = mindmap.yy.getMindmap(); expect(mm.nodeId).toEqual('root'); diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapDb.js b/packages/mermaid/src/diagrams/mindmap/mindmapDb.ts similarity index 56% rename from packages/mermaid/src/diagrams/mindmap/mindmapDb.js rename to packages/mermaid/src/diagrams/mindmap/mindmapDb.ts index 4206a4a260..1bb2e63375 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapDb.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmapDb.ts @@ -1,19 +1,21 @@ import { getConfig } from '../../diagram-api/diagramAPI.js'; -import { sanitizeText as _sanitizeText } from '../../diagrams/common/common.js'; +import type { D3Element } from '../../mermaidAPI.js'; +import { sanitizeText } from '../../diagrams/common/common.js'; import { log } from '../../logger.js'; +import type { MindmapNode } from './mindmapTypes.js'; +import defaultConfig from '../../defaultConfig.js'; -export const sanitizeText = (text) => _sanitizeText(text, getConfig()); - -let nodes = []; +let nodes: MindmapNode[] = []; let cnt = 0; -let elements = {}; -export const clear = () => { +let elements: Record = {}; + +const clear = () => { nodes = []; cnt = 0; elements = {}; }; -const getParent = function (level) { +const getParent = function (level: number) { for (let i = nodes.length - 1; i >= 0; i--) { if (nodes[i].level < level) { return nodes[i]; @@ -23,34 +25,32 @@ const getParent = function (level) { return null; }; -export const getMindmap = () => { +const getMindmap = () => { return nodes.length > 0 ? nodes[0] : null; }; -export const addNode = (level, id, descr, type) => { + +const addNode = (level: number, id: string, descr: string, type: number) => { log.info('addNode', level, id, descr, type); const conf = getConfig(); + let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding; + switch (type) { + case nodeType.ROUNDED_RECT: + case nodeType.RECT: + case nodeType.HEXAGON: + padding *= 2; + } + const node = { id: cnt++, - nodeId: sanitizeText(id), + nodeId: sanitizeText(id, conf), level, - descr: sanitizeText(descr), + descr: sanitizeText(descr, conf), type, children: [], - width: getConfig().mindmap.maxNodeWidth, - }; - switch (node.type) { - case nodeType.ROUNDED_RECT: - node.padding = 2 * conf.mindmap.padding; - break; - case nodeType.RECT: - node.padding = 2 * conf.mindmap.padding; - break; - case nodeType.HEXAGON: - node.padding = 2 * conf.mindmap.padding; - break; - default: - node.padding = conf.mindmap.padding; - } + width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth, + padding, + } satisfies MindmapNode; + const parent = getParent(level); if (parent) { parent.children.push(node); @@ -62,22 +62,14 @@ export const addNode = (level, id, descr, type) => { nodes.push(node); } else { // Syntax error ... there can only bee one root - let error = new Error( + throw new Error( 'There can be only one root. No parent could be found for ("' + node.descr + '")' ); - error.hash = { - text: 'branch ' + name, - token: 'branch ' + name, - line: '1', - loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, - expected: ['"checkout ' + name + '"'], - }; - throw error; } } }; -export const nodeType = { +const nodeType = { DEFAULT: 0, NO_BORDER: 0, ROUNDED_RECT: 1, @@ -88,7 +80,7 @@ export const nodeType = { HEXAGON: 6, }; -export const getType = (startStr, endStr) => { +const getType = (startStr: string, endStr: string): number => { log.debug('In get type', startStr, endStr); switch (startStr) { case '[': @@ -108,21 +100,25 @@ export const getType = (startStr, endStr) => { } }; -export const setElementForId = (id, element) => { +const setElementForId = (id: number, element: D3Element) => { elements[id] = element; }; -export const decorateNode = (decoration) => { +const decorateNode = (decoration?: { class?: string; icon?: string }) => { + if (!decoration) { + return; + } + const config = getConfig(); const node = nodes[nodes.length - 1]; - if (decoration && decoration.icon) { - node.icon = sanitizeText(decoration.icon); + if (decoration.icon) { + node.icon = sanitizeText(decoration.icon, config); } - if (decoration && decoration.class) { - node.class = sanitizeText(decoration.class); + if (decoration.class) { + node.class = sanitizeText(decoration.class, config); } }; -export const type2Str = (type) => { +const type2Str = (type: number) => { switch (type) { case nodeType.DEFAULT: return 'no-border'; @@ -143,13 +139,21 @@ export const type2Str = (type) => { } }; -export let parseError; -export const setErrorHandler = (handler) => { - parseError = handler; -}; - // Expose logger to grammar -export const getLogger = () => log; +const getLogger = () => log; +const getElementById = (id: number) => elements[id]; + +const db = { + clear, + addNode, + getMindmap, + nodeType, + getType, + setElementForId, + decorateNode, + type2Str, + getLogger, + getElementById, +} as const; -export const getNodeById = (id) => nodes[id]; -export const getElementById = (id) => elements[id]; +export default db; diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts similarity index 62% rename from packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js rename to packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts index 3fe9e1d510..9786479943 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts @@ -1,36 +1,53 @@ -/** Created by knut on 14-12-11. */ +import cytoscape from 'cytoscape'; +// @ts-expect-error No types available +import coseBilkent from 'cytoscape-cose-bilkent'; import { select } from 'd3'; -import { log } from '../../logger.js'; +import type { MermaidConfig } from '../../config.type.js'; import { getConfig } from '../../diagram-api/diagramAPI.js'; +import type { DrawDefinition } from '../../diagram-api/types.js'; +import { log } from '../../logger.js'; +import type { D3Element } from '../../mermaidAPI.js'; +import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js'; -import svgDraw from './svgDraw.js'; -import cytoscape from 'cytoscape/dist/cytoscape.umd.js'; -import coseBilkent from 'cytoscape-cose-bilkent'; -import * as db from './mindmapDb.js'; +import type { FilledMindMapNode, MindmapDB, MindmapNode } from './mindmapTypes.js'; +import { drawNode, positionNode } from './svgDraw.js'; +import defaultConfig from '../../defaultConfig.js'; // Inject the layout algorithm into cytoscape cytoscape.use(coseBilkent); -/** - * @param {any} svg The svg element to draw the diagram onto - * @param {object} mindmap The mindmap data and hierarchy - * @param section - * @param {object} conf The configuration object - */ -function drawNodes(svg, mindmap, section, conf) { - svgDraw.drawNode(svg, mindmap, section, conf); +function drawNodes( + db: MindmapDB, + svg: D3Element, + mindmap: FilledMindMapNode, + section: number, + conf: MermaidConfig +) { + drawNode(db, svg, mindmap, section, conf); if (mindmap.children) { mindmap.children.forEach((child, index) => { - drawNodes(svg, child, section < 0 ? index : section, conf); + drawNodes(db, svg, child, section < 0 ? index : section, conf); }); } } -/** - * @param edgesEl - * @param cy - */ -function drawEdges(edgesEl, cy) { +declare module 'cytoscape' { + interface EdgeSingular { + _private: { + bodyBounds: unknown; + rscratch: { + startX: number; + startY: number; + midX: number; + midY: number; + endX: number; + endY: number; + }; + }; + } +} + +function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) { cy.edges().map((edge, id) => { const data = edge.data(); if (edge[0]._private.bodyBounds) { @@ -47,17 +64,11 @@ function drawEdges(edgesEl, cy) { }); } -/** - * @param mindmap The mindmap data and hierarchy - * @param cy - * @param conf The configuration object - * @param level - */ -function addNodes(mindmap, cy, conf, level) { +function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) { cy.add({ group: 'nodes', data: { - id: mindmap.id, + id: mindmap.id.toString(), labelText: mindmap.descr, height: mindmap.height, width: mindmap.width, @@ -67,8 +78,8 @@ function addNodes(mindmap, cy, conf, level) { type: mindmap.type, }, position: { - x: mindmap.x, - y: mindmap.y, + x: mindmap.x!, + y: mindmap.y!, }, }); if (mindmap.children) { @@ -88,12 +99,7 @@ function addNodes(mindmap, cy, conf, level) { } } -/** - * @param node - * @param conf - * @param cy - */ -function layoutMindmap(node, conf) { +function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise { return new Promise((resolve) => { // Add temporary render element const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none'); @@ -122,8 +128,8 @@ function layoutMindmap(node, conf) { cy.layout({ name: 'cose-bilkent', + // @ts-ignore Types for cose-bilkent are not correct? quality: 'proof', - // headless: true, styleEnabled: false, animate: false, }).run(); @@ -133,18 +139,13 @@ function layoutMindmap(node, conf) { }); }); } -/** - * @param node - * @param cy - * @param positionedMindmap - * @param conf - */ -function positionNodes(cy) { + +function positionNodes(db: MindmapDB, cy: cytoscape.Core) { cy.nodes().map((node, id) => { const data = node.data(); data.x = node.position().x; data.y = node.position().y; - svgDraw.positionNode(data); + positionNode(db, data); const el = db.getElementById(data.nodeId); log.info('Id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data); el.attr( @@ -155,38 +156,19 @@ function positionNodes(cy) { }); } -/** - * Draws a an info picture in the tag with id: id based on the graph definition in text. - * - * @param {any} text - * @param {any} id - * @param {any} version - * @param diagObj - */ - -export const draw = async (text, id, version, diagObj) => { - const conf = getConfig(); - - conf.htmlLabels = false; - - log.debug('Rendering mindmap diagram\n' + text, diagObj.parser); +export const draw: DrawDefinition = async (text, id, _version, diagObj) => { + log.debug('Rendering mindmap diagram\n' + text); - const securityLevel = getConfig().securityLevel; - // Handle root and Document for when rendering in sandbox mode - let sandboxElement; - if (securityLevel === 'sandbox') { - sandboxElement = select('#i' + id); + const db = diagObj.db as MindmapDB; + const mm = db.getMindmap(); + if (!mm) { + return; } - const root = - securityLevel === 'sandbox' - ? select(sandboxElement.nodes()[0].contentDocument.body) - : select('body'); - // Parse the graph definition - const svg = root.select('#' + id); + const conf = getConfig(); + conf.htmlLabels = false; - svg.append('g'); - const mm = diagObj.db.getMindmap(); + const svg = selectSvgElement(id); // Draw the graph and start with drawing the nodes without proper position // this gives us the size of the nodes and we can set the positions later @@ -195,18 +177,23 @@ export const draw = async (text, id, version, diagObj) => { edgesElem.attr('class', 'mindmap-edges'); const nodesElem = svg.append('g'); nodesElem.attr('class', 'mindmap-nodes'); - drawNodes(nodesElem, mm, -1, conf); + drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf); // Next step is to layout the mindmap, giving each node a position const cy = await layoutMindmap(mm, conf); - // // After this we can draw, first the edges and the then nodes with the correct position - drawEdges(edgesElem, cy, conf); - positionNodes(cy, conf); + // After this we can draw, first the edges and the then nodes with the correct position + drawEdges(edgesElem, cy); + positionNodes(db, cy); // Setup the view box and size of the svg element - setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth); + setupGraphViewbox( + undefined, + svg, + conf.mindmap?.padding ?? defaultConfig.mindmap.padding, + conf.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth + ); }; export default { diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts b/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts new file mode 100644 index 0000000000..e8350477a9 --- /dev/null +++ b/packages/mermaid/src/diagrams/mindmap/mindmapTypes.ts @@ -0,0 +1,22 @@ +import type { RequiredDeep } from 'type-fest'; +import type mindmapDb from './mindmapDb.js'; + +export interface MindmapNode { + id: number; + nodeId: string; + level: number; + descr: string; + type: number; + children: MindmapNode[]; + width: number; + padding: number; + section?: number; + height?: number; + class?: string; + icon?: string; + x?: number; + y?: number; +} + +export type FilledMindMapNode = RequiredDeep; +export type MindmapDB = typeof mindmapDb; diff --git a/packages/mermaid/src/diagrams/mindmap/styles.js b/packages/mermaid/src/diagrams/mindmap/styles.ts similarity index 87% rename from packages/mermaid/src/diagrams/mindmap/styles.js rename to packages/mermaid/src/diagrams/mindmap/styles.ts index 863522fdf7..fffa6e4d9d 100644 --- a/packages/mermaid/src/diagrams/mindmap/styles.js +++ b/packages/mermaid/src/diagrams/mindmap/styles.ts @@ -1,6 +1,8 @@ +// @ts-expect-error Incorrect khroma types import { darken, lighten, isDark } from 'khroma'; +import type { DiagramStylesProvider } from '../../diagram-api/types.js'; -const genSections = (options) => { +const genSections: DiagramStylesProvider = (options) => { let sections = ''; for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { @@ -49,7 +51,8 @@ const genSections = (options) => { return sections; }; -const getStyles = (options) => +// TODO: These options seem incorrect. +const getStyles: DiagramStylesProvider = (options) => ` .edge { stroke-width: 3; diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts similarity index 64% rename from packages/mermaid/src/diagrams/mindmap/svgDraw.js rename to packages/mermaid/src/diagrams/mindmap/svgDraw.ts index 2a4fc4b671..3c7da86156 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts @@ -1,55 +1,20 @@ -import { select } from 'd3'; -import * as db from './mindmapDb.js'; +import type { D3Element } from '../../mermaidAPI.js'; import { createText } from '../../rendering-util/createText.js'; -const MAX_SECTIONS = 12; +import type { FilledMindMapNode, MindmapDB } from './mindmapTypes.js'; +import type { Point } from '../../types.js'; +import { parseFontSize } from '../../utils.js'; +import type { MermaidConfig } from '../../config.type.js'; -/** - * @param {string} text The text to be wrapped - * @param {number} width The max width of the text - */ -function wrap(text, width) { - text.each(function () { - var text = select(this), - words = text - .text() - .split(/(\s+|)/) - .reverse(), - word, - line = [], - lineHeight = 1.1, // ems - y = text.attr('y'), - dy = parseFloat(text.attr('dy')), - tspan = text - .text(null) - .append('tspan') - .attr('x', 0) - .attr('y', y) - .attr('dy', dy + 'em'); - for (let j = 0; j < words.length; j++) { - word = words[words.length - 1 - j]; - line.push(word); - tspan.text(line.join(' ').trim()); - if (tspan.node().getComputedTextLength() > width || word === '
') { - line.pop(); - tspan.text(line.join(' ').trim()); - if (word === '
') { - line = ['']; - } else { - line = [word]; - } +const MAX_SECTIONS = 12; - tspan = text - .append('tspan') - .attr('x', 0) - .attr('y', y) - .attr('dy', lineHeight + 'em') - .text(word); - } - } - }); -} +type ShapeFunction = ( + db: MindmapDB, + elem: D3Element, + node: FilledMindMapNode, + section?: number +) => void; -const defaultBkg = function (elem, node, section) { +const defaultBkg: ShapeFunction = function (db, elem, node, section) { const rd = 5; elem .append('path') @@ -71,7 +36,7 @@ const defaultBkg = function (elem, node, section) { .attr('y2', node.height); }; -const rectBkg = function (elem, node) { +const rectBkg: ShapeFunction = function (db, elem, node) { elem .append('rect') .attr('id', 'node-' + node.id) @@ -80,7 +45,7 @@ const rectBkg = function (elem, node) { .attr('width', node.width); }; -const cloudBkg = function (elem, node) { +const cloudBkg: ShapeFunction = function (db, elem, node) { const w = node.width; const h = node.height; const r1 = 0.15 * w; @@ -111,7 +76,7 @@ const cloudBkg = function (elem, node) { ); }; -const bangBkg = function (elem, node) { +const bangBkg: ShapeFunction = function (db, elem, node) { const w = node.width; const h = node.height; const r = 0.15 * w; @@ -143,7 +108,7 @@ const bangBkg = function (elem, node) { ); }; -const circleBkg = function (elem, node) { +const circleBkg: ShapeFunction = function (db, elem, node) { elem .append('circle') .attr('id', 'node-' + node.id) @@ -151,15 +116,13 @@ const circleBkg = function (elem, node) { .attr('r', node.width / 2); }; -/** - * - * @param parent - * @param w - * @param h - * @param points - * @param node - */ -function insertPolygonShape(parent, w, h, points, node) { +function insertPolygonShape( + parent: D3Element, + w: number, + h: number, + points: Point[], + node: FilledMindMapNode +) { return parent .insert('polygon', ':first-child') .attr( @@ -173,12 +136,16 @@ function insertPolygonShape(parent, w, h, points, node) { .attr('transform', 'translate(' + (node.width - w) / 2 + ', ' + h + ')'); } -const hexagonBkg = function (elem, node) { +const hexagonBkg: ShapeFunction = function ( + _db: MindmapDB, + elem: D3Element, + node: FilledMindMapNode +) { const h = node.height; const f = 4; const m = h / f; const w = node.width - node.padding + 2 * m; - const points = [ + const points: Point[] = [ { x: m, y: 0 }, { x: w - m, y: 0 }, { x: w, y: -h / 2 }, @@ -186,10 +153,10 @@ const hexagonBkg = function (elem, node) { { x: m, y: -h }, { x: 0, y: -h / 2 }, ]; - const shapeSvg = insertPolygonShape(elem, w, h, points, node); + insertPolygonShape(elem, w, h, points, node); }; -const roundedRectBkg = function (elem, node) { +const roundedRectBkg: ShapeFunction = function (db, elem, node) { elem .append('rect') .attr('id', 'node-' + node.id) @@ -201,13 +168,20 @@ const roundedRectBkg = function (elem, node) { }; /** - * @param {object} elem The D3 dom element in which the node is to be added - * @param {object} node The node to be added - * @param fullSection - * @param {object} conf The configuration object - * @returns {number} The height nodes dom element + * @param db - The database + * @param elem - The D3 dom element in which the node is to be added + * @param node - The node to be added + * @param fullSection - ? + * @param conf - The configuration object + * @returns The height nodes dom element */ -export const drawNode = function (elem, node, fullSection, conf) { +export const drawNode = function ( + db: MindmapDB, + elem: D3Element, + node: FilledMindMapNode, + fullSection: number, + conf: MermaidConfig +): number { const htmlLabels = conf.htmlLabels; const section = fullSection % (MAX_SECTIONS - 1); const nodeElem = elem.append('g'); @@ -235,10 +209,9 @@ export const drawNode = function (elem, node, fullSection, conf) { .attr('dominant-baseline', 'middle') .attr('text-anchor', 'middle'); } - // .call(wrap, node.width); const bbox = textElem.node().getBBox(); - const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; - node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding; + const [fontSize] = parseFontSize(conf.fontSize); + node.height = bbox.height + fontSize! * 1.1 * 0.5 + node.padding; node.width = bbox.width + 2 * node.padding; if (node.icon) { if (node.type === db.nodeType.CIRCLE) { @@ -294,60 +267,34 @@ export const drawNode = function (elem, node, fullSection, conf) { switch (node.type) { case db.nodeType.DEFAULT: - defaultBkg(bkgElem, node, section, conf); + defaultBkg(db, bkgElem, node, section); break; case db.nodeType.ROUNDED_RECT: - roundedRectBkg(bkgElem, node, section, conf); + roundedRectBkg(db, bkgElem, node, section); break; case db.nodeType.RECT: - rectBkg(bkgElem, node, section, conf); + rectBkg(db, bkgElem, node, section); break; case db.nodeType.CIRCLE: bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')'); - circleBkg(bkgElem, node, section, conf); + circleBkg(db, bkgElem, node, section); break; case db.nodeType.CLOUD: - cloudBkg(bkgElem, node, section, conf); + cloudBkg(db, bkgElem, node, section); break; case db.nodeType.BANG: - bangBkg(bkgElem, node, section, conf); + bangBkg(db, bkgElem, node, section); break; case db.nodeType.HEXAGON: - hexagonBkg(bkgElem, node, section, conf); + hexagonBkg(db, bkgElem, node, section); break; } - // Position the node to its coordinate - // if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') { - // nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')'); - // } db.setElementForId(node.id, nodeElem); return node.height; }; -export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) { - const section = fullSection % (MAX_SECTIONS - 1); - const sx = parent.x + parent.width / 2; - const sy = parent.y + parent.height / 2; - const ex = mindmap.x + mindmap.width / 2; - const ey = mindmap.y + mindmap.height / 2; - const mx = ex > sx ? sx + Math.abs(sx - ex) / 2 : sx - Math.abs(sx - ex) / 2; - const my = ey > sy ? sy + Math.abs(sy - ey) / 2 : sy - Math.abs(sy - ey) / 2; - const qx = ex > sx ? Math.abs(sx - mx) / 2 + sx : -Math.abs(sx - mx) / 2 + sx; - const qy = ey > sy ? Math.abs(sy - my) / 2 + sy : -Math.abs(sy - my) / 2 + sy; - - edgesElem - .append('path') - .attr( - 'd', - parent.direction === 'TB' || parent.direction === 'BT' - ? `M${sx},${sy} Q${sx},${qy} ${mx},${my} T${ex},${ey}` - : `M${sx},${sy} Q${qx},${sy} ${mx},${my} T${ex},${ey}` - ) - .attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth); -}; - -export const positionNode = function (node) { +export const positionNode = function (db: MindmapDB, node: FilledMindMapNode) { const nodeElem = db.getElementById(node.id); const x = node.x || 0; @@ -355,5 +302,3 @@ export const positionNode = function (node) { // Position the node to its coordinate nodeElem.attr('transform', 'translate(' + x + ',' + y + ')'); }; - -export default { drawNode, positionNode, drawEdge }; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index 7433f2b9ce..51f808ff44 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -1,6 +1,5 @@ import type { Diagram } from '../../Diagram.js'; import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js'; - import { select as d3select, scaleOrdinal as d3scaleOrdinal, diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index b90d33f473..06aca5ab27 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -925,3 +925,7 @@ export const encodeEntities = function (text: string): string { export const decodeEntities = function (text: string): string { return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';'); }; + +export const isString = (value: unknown): value is string => { + return typeof value === 'string'; +}; diff --git a/patches/cytoscape@3.28.1.patch b/patches/cytoscape@3.28.1.patch new file mode 100644 index 0000000000..d92163a318 --- /dev/null +++ b/patches/cytoscape@3.28.1.patch @@ -0,0 +1,20 @@ +diff --git a/package.json b/package.json +index f2f77fa79c99382b079f4051ed51eafe8d2379c8..0bfddf55394e86f3a386eb7ab681369d410bae07 100644 +--- a/package.json ++++ b/package.json +@@ -30,7 +30,15 @@ + "engines": { + "node": ">=0.10" + }, ++ "exports": { ++ ".": { ++ "import": "./dist/cytoscape.umd.js", ++ "default": "./dist/cytoscape.cjs.js" ++ }, ++ "./*": "./*" ++ }, + "main": "dist/cytoscape.cjs.js", ++ "module": "dist/cytoscape.umd.js", + "unpkg": "dist/cytoscape.min.js", + "jsdelivr": "dist/cytoscape.min.js", + "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b213ebfdd6..9b065ff49a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + cytoscape@3.28.1: + hash: claipxynndhyqyu2csninuoh5e + path: patches/cytoscape@3.28.1.patch + importers: .: @@ -201,14 +206,11 @@ importers: specifier: ^3.0.0 version: 3.0.0 cytoscape: - specifier: ^3.23.0 - version: 3.28.1 + specifier: ^3.28.1 + version: 3.28.1(patch_hash=claipxynndhyqyu2csninuoh5e) cytoscape-cose-bilkent: specifier: ^4.1.0 version: 4.1.0(cytoscape@3.28.1) - cytoscape-fcose: - specifier: ^2.1.0 - version: 2.2.0(cytoscape@3.28.1) d3: specifier: ^7.4.0 version: 7.4.0 @@ -384,28 +386,13 @@ importers: '@braintree/sanitize-url': specifier: ^6.0.1 version: 6.0.2 - cytoscape: - specifier: ^3.23.0 - version: 3.28.1 - cytoscape-cose-bilkent: - specifier: ^4.1.0 - version: 4.1.0(cytoscape@3.28.1) - cytoscape-fcose: - specifier: ^2.1.0 - version: 2.2.0(cytoscape@3.28.1) d3: specifier: ^7.0.0 version: 7.0.0 khroma: specifier: ^2.0.0 version: 2.0.0 - non-layered-tidy-tree-layout: - specifier: ^2.0.2 - version: 2.0.2 devDependencies: - '@types/cytoscape': - specifier: ^3.19.9 - version: 3.19.9 concurrently: specifier: ^8.0.0 version: 8.0.0 @@ -7607,12 +7594,6 @@ packages: layout-base: 1.0.2 dev: false - /cose-base@2.2.0: - resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - dependencies: - layout-base: 2.0.1 - dev: false - /cosmiconfig-typescript-loader@4.4.0(@types/node@20.4.7)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.1.6): resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==} engines: {node: '>=v14.21.3'} @@ -8008,25 +7989,17 @@ packages: cytoscape: ^3.2.0 dependencies: cose-base: 1.0.3 - cytoscape: 3.28.1 - dev: false - - /cytoscape-fcose@2.2.0(cytoscape@3.28.1): - resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} - peerDependencies: - cytoscape: ^3.2.0 - dependencies: - cose-base: 2.2.0 - cytoscape: 3.28.1 + cytoscape: 3.28.1(patch_hash=claipxynndhyqyu2csninuoh5e) dev: false - /cytoscape@3.28.1: + /cytoscape@3.28.1(patch_hash=claipxynndhyqyu2csninuoh5e): resolution: {integrity: sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==} engines: {node: '>=0.10'} dependencies: heap: 0.2.7 lodash: 4.17.21 dev: false + patched: true /d3-array@2.12.1: resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} @@ -11866,10 +11839,6 @@ packages: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} dev: false - /layout-base@2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - dev: false - /lazy-ass@1.6.0: resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} engines: {node: '> 0.8'}