diff --git a/.changeset/eleven-rocks-leave.md b/.changeset/eleven-rocks-leave.md new file mode 100644 index 0000000000..a8fcbae1f0 --- /dev/null +++ b/.changeset/eleven-rocks-leave.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Kanban diagrams will not render when adding a number as ticket id or assigned for a task diff --git a/.changeset/fresh-bears-doubt.md b/.changeset/fresh-bears-doubt.md new file mode 100644 index 0000000000..911ef74c1d --- /dev/null +++ b/.changeset/fresh-bears-doubt.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Intersection calculations for tilted cylinder/DAS when using handdrawn look. Some random seeds could cause the calculations to break. diff --git a/.changeset/neat-rabbits-bake.md b/.changeset/neat-rabbits-bake.md new file mode 100644 index 0000000000..6c66cc0066 --- /dev/null +++ b/.changeset/neat-rabbits-bake.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +Bump dompurify to `^3.2.1`. This removes the need for `@types/dompurify`. diff --git a/.changeset/nervous-beans-listen.md b/.changeset/nervous-beans-listen.md new file mode 100644 index 0000000000..45d796ac06 --- /dev/null +++ b/.changeset/nervous-beans-listen.md @@ -0,0 +1,5 @@ +--- +'@mermaid-js/layout-elk': patch +--- + +fix: Elk rendering of Diamond shape intersections diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js index b5caef9733..c3aba53ea4 100644 --- a/cypress/integration/rendering/flowchart-elk.spec.js +++ b/cypress/integration/rendering/flowchart-elk.spec.js @@ -1,6 +1,6 @@ import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; -describe.skip('Flowchart ELK', () => { +describe('Flowchart ELK', () => { it('1-elk: should render a simple flowchart', () => { imgSnapshotTest( `flowchart-elk TD @@ -857,6 +857,49 @@ flowchart LR D --> E A["A"] +`, + { flowchart: { titleTopMargin: 0 } } + ); + }); + it('6080: should handle diamond shape intersections', () => { + imgSnapshotTest( + `--- +config: + layout: elk +--- +flowchart LR + subgraph s1["Untitled subgraph"] + n1["Evaluate"] + n2["Option 1"] + n3["Option 2"] + n4["fa:fa-car Option 3"] + end + subgraph s2["Untitled subgraph"] + n5["Evaluate"] + n6["Option 1"] + n7["Option 2"] + n8["fa:fa-car Option 3"] + end + A["Start"] -- Some text --> B("Continue") + B --> C{"Evaluate"} + C -- One --> D["Option 1"] + C -- Two --> E["Option 2"] + C -- Three --> F["fa:fa-car Option 3"] + n1 -- One --> n2 + n1 -- Two --> n3 + n1 -- Three --> n4 + n5 -- One --> n6 + n5 -- Two --> n7 + n5 -- Three --> n8 + n1@{ shape: diam} + n2@{ shape: rect} + n3@{ shape: rect} + n4@{ shape: rect} + n5@{ shape: diam} + n6@{ shape: rect} + n7@{ shape: rect} + n8@{ shape: rect} + `, { flowchart: { titleTopMargin: 0 } } ); diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 66fc0f2d31..7ec666c1a9 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -88,71 +88,200 @@
++--- +config: + layout: elk +--- +flowchart LR + subgraph s1["Untitled subgraph"] + n1["Evaluate"] + n2["Option 1"] + n3["Option 2"] + n4["fa:fa-car Option 3"] + end + n1 -- One --> n2 + n1 -- Two --> n3 + n1 -- Three --> n4 + n5 + n1@{ shape: diam} + n2@{ shape: rect} + n3@{ shape: rect} + n4@{ shape: rect} + A["Start"] -- Some text --> B("Continue") + B --> C{"Evaluate"} + C -- One --> D["Option 1"] + C -- Two --> E["Option 2"] + C -- Three --> F["fa:fa-car Option 3"] + + +
+--- +config: + layout: elk +--- +flowchart LR + subgraph s1["Untitled subgraph"] + n1["Evaluate"] + n2["Option 1"] + n3["Option 2"] + n4["fa:fa-car Option 3"] + end + subgraph s2["Untitled subgraph"] + n5["Evaluate"] + n6["Option 1"] + n7["Option 2"] + n8["fa:fa-car Option 3"] + end + A["Start"] -- Some text --> B("Continue") + B --> C{"Evaluate"} + C -- One --> D["Option 1"] + C -- Two --> E["Option 2"] + C -- Three --> F["fa:fa-car Option 3"] + n1 -- One --> n2 + n1 -- Two --> n3 + n1 -- Three --> n4 + n5 -- One --> n6 + n5 -- Two --> n7 + n5 -- Three --> n8 + n1@{ shape: diam} + n2@{ shape: rect} + n3@{ shape: rect} + n4@{ shape: rect} + n5@{ shape: diam} + n6@{ shape: rect} + n7@{ shape: rect} + n8@{ shape: rect} + ++
+--- +config: + layout: elk +--- +flowchart LR + subgraph s1["Untitled subgraph"] + n1["Evaluate"] + n2["Option 1"] + end + n1 -- One --> n2 + + + + ++
+--- +config: + layout: elk +--- +flowchart LR + A{A} --> B & C ++
+--- +config: + layout: elk +--- +flowchart LR + n2@{ shape: rect} + n3@{ shape: rect} + n4@{ shape: rect} + A["Start"] -- Some text --> B("Continue") + B --> C{"Evaluate"} + C -- One --> D["Option 1"] + C -- Two --> E["Option 2"] + C -- Three --> F["fa:fa-car Option 3"] + %% C@{ shape: hexagon} + + ++
+--- +config: + kanban: + ticketBaseUrl: 'https://github.com/your-repo/issues/#TICKET#' +--- +kanban + Backlog + task1[📝 Define project requirements]@{ ticket: a101 } + To Do + task2[🔍 Research technologies]@{ ticket: a102 } + Review + task4[🔍 Code review for login feature]@{ ticket: a104 } + Done + task5[✅ Deploy initial version]@{ ticket: a105 } + In Progress + task3[💻 Develop login feature]@{ ticket: 103 } + ++
flowchart LR nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }-
+flowchart LR nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' } style A fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' } A:::AClass classDef AClass fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }-+flowchart LR nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }-+flowchart LR nA[Style] --> A@{ icon: 'fa:bell', form: 'square' } style A fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Class] --> A@{ icon: 'fa:bell', form: 'square' } A:::AClass classDef AClass fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }-+flowchart LR nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }-+flowchart LR nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' } style A fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' } A:::AClass classDef AClass fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' } A:::AClass classDef AClass fill:#f9f,stroke:#333,stroke-width:4px-+flowchart LR nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' } style A fill:#f9f,stroke:#333,stroke-width:4px diff --git a/docs/ecosystem/integrations-community.md b/docs/ecosystem/integrations-community.md index 71b02cd931..b12e148a94 100644 --- a/docs/ecosystem/integrations-community.md +++ b/docs/ecosystem/integrations-community.md @@ -185,8 +185,6 @@ Communication tools and platforms - [=Diagram block](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams) - [Standard Notes](https://standardnotes.com/) - [Mermaid Extension](https://github.com/nienow/sn-mermaid) -- [Sublime Text 3](https://sublimetext.com) - - [Mermaid Package](https://packagecontrol.io/packages/Mermaid) - [VS Code](https://code.visualstudio.com/) - [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor) - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export) diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index 60cdff8d62..1216b5dc82 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -484,6 +484,8 @@ export const render = async ( const r3 = a1 * q1.x + b1 * q1.y + c1; const r4 = a1 * q2.x + b1 * q2.y + c1; + const epsilon = 1e-6; + // Check signs of r3 and r4. If both point 3 and point 4 lie on // same side of line 1, the line segments do not intersect. if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) { @@ -502,7 +504,7 @@ export const render = async ( // Check signs of r1 and r2. If both point 1 and point 2 lie // on same side of second line segment, the line segments do // not intersect. - if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) { + if (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && sameSign(r1, r2)) { return /*DON'T_INTERSECT*/; } @@ -547,11 +549,11 @@ export const render = async ( { x: x1 - w / 2, y: y1 }, ]; log.debug( - `UIO diamondIntersection calc abc89: + `APA16 diamondIntersection calc abc89: outsidePoint: ${JSON.stringify(outsidePoint)} insidePoint : ${JSON.stringify(insidePoint)} - node : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`, - polyPoints + node-bounds : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`, + JSON.stringify(polyPoints) ); const intersections = []; @@ -564,8 +566,8 @@ export const render = async ( minY = Math.min(minY, entry.y); }); - // const left = x1 - w / 2; - // const top = y1 + h / 2; + const left = x1 - w / 2 - minX; + const top = y1 - h / 2 - minY; for (let i = 0; i < polyPoints.length; i++) { const p1 = polyPoints[i]; @@ -573,8 +575,8 @@ export const render = async ( const intersect = intersectLine( bounds, outsidePoint, - { x: p1.x, y: p1.y }, - { x: p2.x, y: p2.y } + { x: left + p1.x, y: top + p1.y }, + { x: left + p2.x, y: top + p2.y } ); if (intersect) { @@ -753,7 +755,6 @@ export const render = async ( } } }); - log.debug('returning points', points); return points; }; @@ -968,17 +969,17 @@ export const render = async ( startNode.innerHTML ); } - if (startNode.shape === 'diamond') { + if (startNode.shape === 'diamond' || startNode.shape === 'diam') { edge.points.unshift({ x: startNode.x + startNode.width / 2 + offset.x, y: startNode.y + startNode.height / 2 + offset.y, }); } - if (endNode.shape === 'diamond') { + if (endNode.shape === 'diamond' || endNode.shape === 'diam') { const x = endNode.x + endNode.width / 2 + offset.x; // Add a point at the center of the diamond if ( - Math.abs(edge.points[edge.points.length - 1].y - endNode.y - offset.y) > 0.001 || + Math.abs(edge.points[edge.points.length - 1].y - endNode.y - offset.y) > 0.01 || Math.abs(edge.points[edge.points.length - 1].x - x) > 0.001 ) { edge.points.push({ @@ -997,7 +998,7 @@ export const render = async ( height: startNode.height, padding: startNode.padding, }, - startNode.shape === 'diamond' + startNode.shape === 'diamond' || startNode.shape === 'diam' ).reverse(); edge.points = cutPathAtIntersect( @@ -1009,7 +1010,7 @@ export const render = async ( height: endNode.height, padding: endNode.padding, }, - endNode.shape === 'diamond' + endNode.shape === 'diamond' || endNode.shape === 'diam' ); const paths = insertEdge( diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 78bf583881..4300ca14bd 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -71,7 +71,6 @@ "@iconify/utils": "^2.1.32", "@mermaid-js/parser": "workspace:^", "@types/d3": "^7.4.3", - "@types/dompurify": "^3.0.5", "cytoscape": "^3.29.2", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", @@ -79,7 +78,7 @@ "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", "dayjs": "^1.11.10", - "dompurify": "^3.0.11 <3.1.7", + "dompurify": "^3.2.1", "katex": "^0.16.9", "khroma": "^2.1.0", "lodash-es": "^4.17.21", diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index e24c8e85c8..fd76d0a45d 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -32,14 +32,14 @@ const setupDompurifyHooksIfNotSetup = (() => { function setupDompurifyHooks() { const TEMPORARY_ATTRIBUTE = 'data-temp-href-target'; - DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => { - if (node.tagName === 'A' && node.hasAttribute('target')) { + DOMPurify.addHook('beforeSanitizeAttributes', (node) => { + if (node instanceof Element && node.tagName === 'A' && node.hasAttribute('target')) { node.setAttribute(TEMPORARY_ATTRIBUTE, node.getAttribute('target') ?? ''); } }); - DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => { - if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) { + DOMPurify.addHook('afterSanitizeAttributes', (node) => { + if (node instanceof Element && node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) { node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) ?? ''); node.removeAttribute(TEMPORARY_ATTRIBUTE); if (node.getAttribute('target') === '_blank') { @@ -83,7 +83,6 @@ export const sanitizeText = (text: string, config: MermaidConfig): string => { return text; } if (config.dompurifyConfig) { - // eslint-disable-next-line @typescript-eslint/no-base-to-string text = DOMPurify.sanitize(sanitizeMore(text, config), config.dompurifyConfig).toString(); } else { text = DOMPurify.sanitize(sanitizeMore(text, config), { diff --git a/packages/mermaid/src/diagrams/kanban/kanbanDb.ts b/packages/mermaid/src/diagrams/kanban/kanbanDb.ts index 478c378038..a9bcbb7b81 100644 --- a/packages/mermaid/src/diagrams/kanban/kanbanDb.ts +++ b/packages/mermaid/src/diagrams/kanban/kanbanDb.ts @@ -138,13 +138,13 @@ const addNode = (level: number, id: string, descr: string, type: number, shapeDa node.label = doc?.label; } if (doc?.icon) { - node.icon = doc?.icon; + node.icon = doc?.icon.toString(); } if (doc?.assigned) { - node.assigned = doc?.assigned; + node.assigned = doc?.assigned.toString(); } if (doc?.ticket) { - node.ticket = doc?.ticket; + node.ticket = doc?.ticket.toString(); } if (doc?.priority) { diff --git a/packages/mermaid/src/docs/ecosystem/integrations-community.md b/packages/mermaid/src/docs/ecosystem/integrations-community.md index 974cccc127..6cff12aac2 100644 --- a/packages/mermaid/src/docs/ecosystem/integrations-community.md +++ b/packages/mermaid/src/docs/ecosystem/integrations-community.md @@ -180,8 +180,6 @@ Communication tools and platforms - [=Diagram block](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams) - [Standard Notes](https://standardnotes.com/) - [Mermaid Extension](https://github.com/nienow/sn-mermaid) -- [Sublime Text 3](https://sublimetext.com) - - [Mermaid Package](https://packagecontrol.io/packages/Mermaid) - [VS Code](https://code.visualstudio.com/) - [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor) - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export) diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index c44161a520..910ecb5e88 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -455,6 +455,7 @@ const render = async function ( svgCode = DOMPurify.sanitize(svgCode, { ADD_TAGS: DOMPURIFY_TAGS, ADD_ATTR: DOMPURIFY_ATTR, + HTML_INTEGRATION_POINTS: { foreignobject: true }, }); } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts index f8a2fb52b2..29f2c267f5 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts @@ -125,7 +125,7 @@ export async function tiltedCylinder( ) { let x = rx * rx * (1 - (y * y) / (ry * ry)); if (x != 0) { - x = Math.sqrt(x); + x = Math.sqrt(Math.abs(x)); } x = rx - x; if (point.x - (node.x ?? 0) > 0) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a9bc7d758..7f3f4fa5f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,9 +226,6 @@ importers: '@types/d3': specifier: ^7.4.3 version: 7.4.3 - '@types/dompurify': - specifier: ^3.0.5 - version: 3.0.5 cytoscape: specifier: ^3.29.2 version: 3.30.2 @@ -251,8 +248,8 @@ importers: specifier: ^1.11.10 version: 1.11.13 dompurify: - specifier: ^3.0.11 <3.1.7 - version: 3.1.6 + specifier: ^3.2.1 + version: 3.2.1 katex: specifier: ^0.16.9 version: 0.16.11 @@ -2768,9 +2765,6 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/dompurify@3.0.5': - resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==} - '@types/estree@0.0.39': resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} @@ -4720,8 +4714,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.1.6: - resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==} + dompurify@3.2.1: + resolution: {integrity: sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==} domutils@3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} @@ -12125,10 +12119,6 @@ snapshots: dependencies: '@types/ms': 0.7.34 - '@types/dompurify@3.0.5': - dependencies: - '@types/trusted-types': 2.0.7 - '@types/estree@0.0.39': {} '@types/estree@1.0.6': {} @@ -12970,7 +12960,7 @@ snapshots: antlr4: 4.11.0 color-string: 1.9.1 dom-to-image-more: 2.16.0 - dompurify: 3.1.6 + dompurify: 3.2.1 file-saver: 2.0.5 highlight.js: 10.7.3 html-to-image: 1.11.11 @@ -14509,7 +14499,9 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.1.6: {} + dompurify@3.2.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 domutils@3.1.0: dependencies: diff --git a/scripts/tsc-check.ts b/scripts/tsc-check.ts index 7a5ff50a9b..2e5b3016d1 100644 --- a/scripts/tsc-check.ts +++ b/scripts/tsc-check.ts @@ -38,7 +38,6 @@ const SRC = { // to match the real `package.json` values 'type-fest': '*', '@types/d3': '^7.4.3', - '@types/dompurify': '^3.0.5', typescript: '*', }, },