@brrock/excalidraw-mermaid 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Excalidraw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @brrock/excalidraw-mermaid
2
+
3
+ Convert Mermaid diagrams to Excalidraw elements.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @brrock/excalidraw-mermaid
9
+ # or
10
+ npm install @brrock/excalidraw-mermaid
11
+ ```
12
+
13
+ **Note:** `mermaid` is a peer dependency and must be installed separately.
14
+
15
+ ```bash
16
+ bun add mermaid
17
+ # or
18
+ npm install mermaid
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { parseMermaidToExcalidraw } from "@brrock/excalidraw-mermaid";
25
+
26
+ const diagramDefinition = `
27
+ graph TD
28
+ A[Start] --> B[End]
29
+ `;
30
+
31
+ try {
32
+ const { elements, files } = await parseMermaidToExcalidraw(diagramDefinition);
33
+ // Use elements and files in Excalidraw
34
+ } catch (e) {
35
+ // Parse error
36
+ }
37
+ ```
38
+
39
+ ### API
40
+
41
+ #### `parseMermaidToExcalidraw(definition, config?)`
42
+
43
+ Converts a Mermaid diagram definition to Excalidraw elements.
44
+
45
+ - `definition` - The Mermaid diagram string
46
+ - `config` - Optional Mermaid config object
47
+
48
+ ```ts
49
+ interface MermaidConfig {
50
+ startOnLoad?: boolean;
51
+ flowchart?: {
52
+ curve?: "linear" | "basis";
53
+ };
54
+ themeVariables?: {
55
+ fontSize?: string;
56
+ };
57
+ maxEdges?: number;
58
+ maxTextSize?: number;
59
+ }
60
+ ```
61
+
62
+ #### `validateMermaid(mermaidStr)`
63
+
64
+ Validates a Mermaid diagram string.
65
+
66
+ ### Supported Diagram Types
67
+
68
+ - Flowchart
69
+ - Sequence
70
+ - Class
71
+
72
+ ## Building
73
+
74
+ ```bash
75
+ bun install
76
+ bun run build
77
+ ```
@@ -0,0 +1,117 @@
1
+ //#region src/types/excalidraw.d.ts
2
+ type RoundnessType = 1 | 2 | 3 | 4;
3
+ interface Roundness {
4
+ type: RoundnessType;
5
+ }
6
+ type StrokeStyle = "solid" | "dashed" | "dotted";
7
+ type Arrowhead = "arrow" | "bar" | "dot" | "triangle" | "triangle_outline" | "diamond" | "diamond_outline";
8
+ type VerticalAlign = "top" | "middle" | "bottom";
9
+ type TextAlign = "left" | "center" | "right";
10
+ type ElementStatus = "pending" | "saved" | "error";
11
+ type FileId = string & {
12
+ _brand: "FileId";
13
+ };
14
+ interface BinaryFileData {
15
+ id: FileId;
16
+ mimeType: string;
17
+ dataURL: string;
18
+ created?: number;
19
+ lastRetrieved?: number;
20
+ status?: ElementStatus;
21
+ }
22
+ type BinaryFiles = Record<FileId, BinaryFileData>;
23
+ interface ElementLabel {
24
+ text: string;
25
+ fontSize?: number;
26
+ fontFamily?: number;
27
+ textAlign?: TextAlign;
28
+ verticalAlign?: VerticalAlign;
29
+ groupIds?: string[];
30
+ strokeColor?: string;
31
+ }
32
+ interface ExcalidrawElementSkeleton {
33
+ id?: string;
34
+ type: "rectangle" | "diamond" | "ellipse" | "arrow" | "line" | "text" | "image" | "frame";
35
+ x?: number;
36
+ y?: number;
37
+ width?: number;
38
+ height?: number;
39
+ angle?: number;
40
+ groupIds?: string[];
41
+ roundness?: Roundness;
42
+ strokeWidth?: number;
43
+ strokeColor?: string;
44
+ backgroundColor?: string;
45
+ strokeStyle?: StrokeStyle;
46
+ fillStyle?: string;
47
+ startArrowhead?: Arrowhead | null;
48
+ endArrowhead?: Arrowhead | null;
49
+ points?: number[][];
50
+ label?: ElementLabel;
51
+ text?: string;
52
+ fontSize?: number;
53
+ fontFamily?: number;
54
+ textAlign?: TextAlign;
55
+ verticalAlign?: VerticalAlign;
56
+ link?: string | null;
57
+ fileId?: FileId;
58
+ status?: ElementStatus;
59
+ name?: string;
60
+ children?: string[];
61
+ start?: {
62
+ id?: string;
63
+ type?: string;
64
+ };
65
+ end?: {
66
+ id?: string;
67
+ type?: string;
68
+ };
69
+ }
70
+ //#endregion
71
+ //#region src/interfaces.d.ts
72
+ interface MermaidToExcalidrawResult {
73
+ elements: ExcalidrawElementSkeleton[];
74
+ files?: BinaryFiles;
75
+ }
76
+ //#endregion
77
+ //#region src/validateMermaid.d.ts
78
+ declare function validateMermaid(mermaidStr: string): Promise<boolean | void>;
79
+ //#endregion
80
+ //#region src/index.d.ts
81
+ interface MermaidConfig {
82
+ /**
83
+ * Whether to start the diagram automatically when the page loads.
84
+ * @default false
85
+ */
86
+ startOnLoad?: boolean;
87
+ /**
88
+ * The flowchart curve style.
89
+ * @default "linear"
90
+ */
91
+ flowchart?: {
92
+ curve?: "linear" | "basis";
93
+ };
94
+ /**
95
+ * Theme variables
96
+ * @default { fontSize: "25px" }
97
+ */
98
+ themeVariables?: {
99
+ fontSize?: string;
100
+ };
101
+ /**
102
+ * Maximum number of edges to be rendered.
103
+ * @default 1000
104
+ */
105
+ maxEdges?: number;
106
+ /**
107
+ * Maximum number of characters to be rendered.
108
+ * @default 1000
109
+ */
110
+ maxTextSize?: number;
111
+ }
112
+ interface ExcalidrawConfig {
113
+ fontSize?: number;
114
+ }
115
+ declare const parseMermaidToExcalidraw: (definition: string, config?: MermaidConfig) => Promise<MermaidToExcalidrawResult>;
116
+ //#endregion
117
+ export { ExcalidrawConfig, MermaidConfig, parseMermaidToExcalidraw, validateMermaid };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import e from"mermaid";const t={rect:`rectangle`,circle:`ellipse`},n={startOnLoad:!1,flowchart:{curve:`linear`},themeVariables:{fontSize:`${20*1.25}px`},maxEdges:500,maxTextSize:5e4};var r=class{constructor({converter:e}){this.convert=(e,t)=>this.converter(e,{...t,fontSize:t.fontSize||20}),this.converter=e}};let i=function(e){return e.ROUND=`round`,e.STADIUM=`stadium`,e.DOUBLECIRCLE=`doublecircle`,e.CIRCLE=`circle`,e.DIAMOND=`diamond`,e}({}),a=function(e){return e.COLOR=`color`,e}({}),o=function(e){return e.FILL=`fill`,e.STROKE=`stroke`,e.STROKE_WIDTH=`stroke-width`,e.STROKE_DASHARRAY=`stroke-dasharray`,e}({});const s=[[/\*\*\*(.*?)\*\*\*/g,`$1`],[/\*\*(.*?)\*\*/g,`$1`],[/\*(.*?)\*/g,`$1`],[/__(.*?)__/g,`$1`],[/_(.*?)_/g,`$1`],[/~~(.*?)~~/g,`$1`],[/~~(.*?)~~/g,`$1`],[/`(.*?)`/g,`$1`],[/\[(.*?)\]\(.*?\)/g,`$1`],[/!\[.*?\]\(.*?\)/g,``],[/^#+\s+/gm,``],[/^[-*+]\s+/gm,``],[/^\d+\.\s+/gm,``],[/^>\s+/gm,``]],c=e=>{let t=e;for(let[e,n]of s)t=t.replace(e,n);return t.trim()},l={arrow_circle:{endArrowhead:`dot`},arrow_cross:{endArrowhead:`bar`},arrow_open:{endArrowhead:null,startArrowhead:null},double_arrow_circle:{endArrowhead:`dot`,startArrowhead:`dot`},double_arrow_cross:{endArrowhead:`bar`,startArrowhead:`bar`},double_arrow_point:{endArrowhead:`arrow`,startArrowhead:`arrow`}},u=e=>l[e],d=e=>{let t=e.text;return e.labelType===`markdown`&&(t=c(e.text)),f(t)},f=e=>e.replace(/\s?(fa|fab):[a-zA-Z0-9-]+/g,``),p=e=>{let t={};return Object.keys(e).forEach(n=>{switch(n){case o.FILL:t.backgroundColor=e[n],t.fillStyle=`solid`;break;case o.STROKE:t.strokeColor=e[n];break;case o.STROKE_WIDTH:t.strokeWidth=Number(e[n]?.split(`px`)[0]);break;case o.STROKE_DASHARRAY:t.strokeStyle=`dashed`;break}}),t},m=e=>{let t={};return Object.keys(e).forEach(n=>{switch(n){case a.COLOR:t.strokeColor=e[n];break}}),t},h=e=>{let t={};e.subGraphs.map(n=>{n.nodeIds.forEach(r=>{t[n.id]={id:n.id,parent:null,isLeaf:!1},t[r]={id:r,parent:n.id,isLeaf:e.vertices[r]!==void 0}})});let n={};return[...Object.keys(e.vertices),...e.subGraphs.map(e=>e.id)].forEach(e=>{if(!t[e])return;let r=t[e],i=[];for(r.isLeaf||i.push(`subgraph_group_${r.id}`);r.parent;)i.push(`subgraph_group_${r.parent}`),r=t[r.parent];n[e]=i}),{getGroupIds:e=>n[e]||[],getParentId:e=>t[e]?t[e].parent:null}},g=new r({converter:(e,t)=>{let n=[],r=t.fontSize,{getGroupIds:a,getParentId:o}=h(e);return e.subGraphs.reverse().forEach(e=>{let t=a(e.id),i={id:e.id,type:`rectangle`,groupIds:t,x:e.x,y:e.y,width:e.width,height:e.height,label:{groupIds:t,text:d(e),fontSize:r,verticalAlign:`top`}};n.push(i)}),Object.values(e.vertices).forEach(e=>{if(!e)return;let t=a(e.id),o=p(e.containerStyle),s=m(e.labelStyle),c={id:e.id,type:`rectangle`,groupIds:t,x:e.x,y:e.y,width:e.width,height:e.height,strokeWidth:2,label:{groupIds:t,text:d(e),fontSize:r,...s},link:e.link||null,...o};switch(e.type){case i.STADIUM:c={...c,roundness:{type:3}};break;case i.ROUND:c={...c,roundness:{type:3}};break;case i.DOUBLECIRCLE:{t.push(`doublecircle_${e.id}}`);let i={type:`ellipse`,groupIds:t,x:e.x+5,y:e.y+5,width:e.width-10,height:e.height-10,strokeWidth:2,roundness:{type:3},label:{groupIds:t,text:d(e),fontSize:r}};c={...c,groupIds:t,type:`ellipse`},n.push(i);break}case i.CIRCLE:c.type=`ellipse`;break;case i.DIAMOND:c.type=`diamond`;break}n.push(c)}),e.edges.forEach(e=>{let t=[],i=o(e.start),s=o(e.end);i&&i===s&&(t=a(i));let{startX:c,startY:l,reflectionPoints:f}=e,p=f.map(e=>[e.x-f[0].x,e.y-f[0].y]),m=u(e.type),h={id:`${e.start}_${e.end}`,type:`arrow`,groupIds:t,x:c,y:l,strokeWidth:e.stroke===`thick`?4:2,strokeStyle:e.stroke===`dotted`?`dashed`:void 0,points:p,...e.text?{label:{text:d(e),fontSize:r,groupIds:t}}:{},roundness:{type:2},...m},g=n.find(t=>t.id===e.start),_=n.find(t=>t.id===e.end);!g||!_||(h.start={id:g.id||``},h.end={id:_.id||``},n.push(h))}),{elements:n}}});function _(){return Math.random().toString(36).substring(2,9)}const v=new r({converter:e=>{let t=_(),{width:n,height:r}=e,i={type:`image`,x:0,y:0,width:n,height:r,status:`saved`,fileId:t};return{files:{[t]:{id:t,mimeType:e.mimeType,dataURL:e.dataURL}},elements:[i]}}}),y=e=>e.replace(/\\n/g,`
2
+ `),b=e=>{let t={type:`line`,x:e.startX,y:e.startY,points:[[0,0],[e.endX-e.startX,e.endY-e.startY]],width:e.endX-e.startX,height:e.endY-e.startY,strokeStyle:e.strokeStyle||`solid`,strokeColor:e.strokeColor||`#000`,strokeWidth:e.strokeWidth||1};return e.groupId&&Object.assign(t,{groupIds:[e.groupId]}),e.id&&Object.assign(t,{id:e.id}),t},x=e=>{let t={type:`text`,x:e.x,y:e.y,width:e.width,height:e.height,text:y(e.text)||``,fontSize:e.fontSize,verticalAlign:`middle`};return e.groupId&&Object.assign(t,{groupIds:[e.groupId]}),e.id&&Object.assign(t,{id:e.id}),t},S=e=>{let t={};e.type===`rectangle`&&e.subtype===`activation`&&(t={backgroundColor:`#e9ecef`,fillStyle:`solid`});let n={id:e.id,type:e.type,x:e.x,y:e.y,width:e.width,height:e.height,label:{text:y(e?.label?.text||``),fontSize:e?.label?.fontSize,verticalAlign:e.label?.verticalAlign||`middle`,strokeColor:e.label?.color||`#000`,groupIds:e.groupId?[e.groupId]:[]},strokeStyle:e?.strokeStyle,strokeWidth:e?.strokeWidth,strokeColor:e?.strokeColor,backgroundColor:e?.bgColor,fillStyle:`solid`,...t};return e.groupId&&Object.assign(n,{groupIds:[e.groupId]}),n},C=e=>{let t={type:`arrow`,x:e.startX,y:e.startY,points:e.points||[[0,0],[e.endX-e.startX,e.endY-e.startY]],width:e.endX-e.startX,height:e.endY-e.startY,strokeStyle:e?.strokeStyle||`solid`,endArrowhead:e?.endArrowhead||null,startArrowhead:e?.startArrowhead||null,label:{text:y(e?.label?.text||``),fontSize:16},roundness:{type:2},start:e.start,end:e.end};return e.groupId&&Object.assign(t,{groupIds:[e.groupId]}),t},w=new r({converter:e=>{let t=[],n=[];if(Object.values(e.nodes).forEach(e=>{!e||!e.length||e.forEach(e=>{let r;switch(e.type){case`line`:r=b(e);break;case`rectangle`:case`ellipse`:r=S(e);break;case`text`:r=x(e);break;default:throw`unknown type ${e.type}`}e.type===`rectangle`&&e?.subtype===`activation`?n.push(r):t.push(r)})}),Object.values(e.lines).forEach(e=>{e&&t.push(b(e))}),Object.values(e.arrows).forEach(e=>{e&&(t.push(C(e)),e.sequenceNumber&&t.push(S(e.sequenceNumber)))}),t.push(...n),e.loops){let{lines:n,texts:r,nodes:i}=e.loops;n.forEach(e=>{t.push(b(e))}),r.forEach(e=>{t.push(x(e))}),i.forEach(e=>{t.push(S(e))})}return e.groups&&e.groups.forEach(e=>{let{actorKeys:n,name:r}=e,i=1/0,a=1/0,o=0,s=0;if(!n.length)return;t.filter(e=>{if(e.id){let t=e.id.indexOf(`-`),r=e.id.substring(0,t);return n.includes(r)}}).forEach(e=>{if(e.x===void 0||e.y===void 0||e.width===void 0||e.height===void 0)throw Error(`Actor attributes missing ${e}`);i=Math.min(i,e.x),a=Math.min(a,e.y),o=Math.max(o,e.x+e.width),s=Math.max(s,e.y+e.height)});let c=i-10,l=a-10,u=o-i+20,d=s-a+20,f=_(),p=S({type:`rectangle`,x:c,y:l,width:u,height:d,bgColor:e.fill,id:f});t.unshift(p);let m=_(),h=[f];t.forEach(e=>{if(e.type!==`frame`){if(e.x===void 0||e.y===void 0||e.width===void 0||e.height===void 0)throw Error(`Element attributes missing ${e}`);if(e.x>=i&&e.x+e.width<=o&&e.y>=a&&e.y+e.height<=s){let t=e.id||_();e.id||Object.assign(e,{id:t}),h.push(t)}}});let g={type:`frame`,id:m,name:r,children:h};t.push(g)}),{elements:t}}}),ee=new r({converter:e=>{let t=[];return Object.values(e.nodes).forEach(e=>{!e||!e.length||e.forEach(e=>{let n;switch(e.type){case`line`:n=b(e);break;case`rectangle`:case`ellipse`:n=S(e);break;case`text`:n=x(e);break;default:throw`unknown type ${e.type}`}t.push(n)})}),Object.values(e.lines).forEach(e=>{e&&t.push(b(e))}),Object.values(e.arrows).forEach(e=>{if(!e)return;let n=C(e);t.push(n)}),Object.values(e.text).forEach(e=>{let n=x(e);t.push(n)}),Object.values(e.namespaces).forEach(n=>{let r=n.classes||{},i=Object.keys(r),a=[...i],o=[...e.lines,...e.arrows,...e.text];i.forEach(e=>{let t=o.filter(t=>t.metadata&&t.metadata.classId===e).map(e=>e.id);t.length&&a.push(...t)});let s={type:`frame`,id:_(),name:n.id,children:a};t.push(s)}),{elements:t}}}),T=(e,t={})=>{switch(e.type){case`graphImage`:return v.convert(e,t);case`flowchart`:return g.convert(e,t);case`sequence`:return w.convert(e,t);case`class`:return ee.convert(e,t);default:throw Error(`graphToExcalidraw: unknown graph type "${e.type}, only flowcharts are supported!"`)}},E=e=>{e=O(e);let t=e.replace(/#(\d+);/g,`&#$1;`).replace(/#([a-z]+);/g,`&$1;`),n=document.createElement(`textarea`);return n.innerHTML=t,n.value},D=e=>{let t=e.getAttribute(`transform`)?.match(/translate\(([ \d.-]+),\s*([\d.-]+)\)/),n=0,r=0;return t&&(n=Number(t[1]),r=Number(t[2])),{transformX:n,transformY:r}},te=e=>{let t=e;return t=t.replace(/style.*:\S*#.*;/g,e=>e.substring(0,e.length-1)),t=t.replace(/classDef.*:\S*#.*;/g,e=>e.substring(0,e.length-1)),t=t.replace(/#\w+;/g,e=>{let t=e.substring(1,e.length-1);return/^\+?\d+$/.test(t)?`fl°°${t}¶ß`:`fl°${t}¶ß`}),t},O=function(e){return e.replace(/fl°°/g,`#`).replace(/fl°/g,`&`).replace(/¶ß/g,`;`)},k=(e,t={x:0,y:0})=>{if(e.tagName.toLowerCase()!==`path`)throw Error(`Invalid input: Expected an HTMLElement of tag "path", got ${e.tagName}`);let n=e.getAttribute(`d`);if(!n)throw Error(`Path element does not contain a "d" attribute`);let r=n.split(/(?=[LM])/),i=r[0].substring(1).split(`,`).map(e=>parseFloat(e)),a=r[r.length-1].substring(1).split(`,`).map(e=>parseFloat(e)),o=r.map(e=>{let t=e.substring(1).split(`,`).map(e=>parseFloat(e));return{x:t[0],y:t[1]}}).filter((e,t,n)=>{if(t===0||t===n.length-1)return!0;if(e.x===n[t-1].x&&e.y===n[t-1].y)return!1;if(t===n.length-2&&(n[t-1].x===e.x||n[t-1].y===e.y)){let t=n[n.length-1];return Math.hypot(t.x-e.x,t.y-e.y)>20}return e.x!==n[t-1].x||e.y!==n[t-1].y}).map(e=>({x:e.x+t.x,y:e.y+t.y}));return{startX:i[0]+t.x,startY:i[1]+t.y,endX:a[0]+t.x,endY:a[1]+t.y,reflectionPoints:o}},A=(e,t)=>{let n=e.nodes.map(e=>e.startsWith(`flowchart-`)?e.split(`-`)[1]:e),r=t.querySelector(`[id='${e.id}']`);if(!r)throw Error(`SubGraph element not found`);let i=N(r,t),a=r.getBBox(),o={width:a.width,height:a.height};return e.classes=void 0,e.dir=void 0,{...e,nodeIds:n,...i,...o,text:E(e.title)}},j=(e,t,n)=>{let r=t.querySelector(`[id*="flowchart-${e.id}-"]`);if(!r)return;let i;r.parentElement?.tagName.toLowerCase()===`a`&&(i=r.parentElement.getAttribute(`xlink:href`));let a=N(i?r.parentElement:r,t),o=r.getBBox(),s={width:o.width,height:o.height},c=r.querySelector(`.label-container`)?.getAttribute(`style`),l=r.querySelector(`.label`)?.getAttribute(`style`),u={};c?.split(`;`).forEach(e=>{if(!e)return;let t=e.split(`:`)[0].trim();u[t]=e.split(`:`)[1].trim()});let d={};if(l?.split(`;`).forEach(e=>{if(!e)return;let t=e.split(`:`)[0].trim();d[t]=e.split(`:`)[1].trim()}),e.classes){let t=n[e.classes];t&&(t.styles?.forEach(e=>{let[t,n]=e.split(`:`);u[t.trim()]=n.trim()}),t.textStyles?.forEach(e=>{let[t,n]=e.split(`:`);d[t.trim()]=n.trim()}))}return{id:e.id,labelType:e.labelType,text:E(e.text),type:e.type,link:i||void 0,...a,...s,containerStyle:u,labelStyle:d}},M=(e,t,n)=>{let r=n.querySelector(`[id*="L-${e.start}-${e.end}-${t}"]`);if(!r)throw Error(`Edge element not found`);let i=k(r,N(r,n));return e.length=void 0,{...e,...i,text:E(e.text)}},N=(e,t)=>{if(!e)throw Error(`Element not found`);let n=e.parentElement?.parentElement,r=e.childNodes[0],i={x:0,y:0};if(r){let{transformX:e,transformY:t}=D(r),n=r.getBBox();i={x:Number(r.getAttribute(`x`))||e+n.x||0,y:Number(r.getAttribute(`y`))||t+n.y||0}}let{transformX:a,transformY:o}=D(e),s={x:a+i.x,y:o+i.y};for(;n&&n.id!==t.id;){if(n.classList.value===`root`&&n.hasAttribute(`transform`)){let{transformX:e,transformY:t}=D(n);s.x+=e,s.y+=t}n=n.parentElement}return s},ne=(e,t)=>{e.parse();let n=e.parser.yy,r=n.getVertices(),i=n.getClasses();Object.keys(r).forEach(e=>{r[e]=j(r[e],t,i)});let a=new Map,o=n.getEdges().filter(e=>t.querySelector(`[id*="L-${e.start}-${e.end}"]`)).map(e=>{let n=`${e.start}-${e.end}`,r=a.get(n)||0;return a.set(n,r+1),M(e,r,t)});return{type:`flowchart`,subGraphs:n.getSubGraphs().map(e=>A(e,t)),vertices:r,edges:o}},re=(e,t)=>{let n={};t?.label&&(n.label={text:E(t.label),fontSize:16});let r=e.tagName;if(r===`line`)n.startX=Number(e.getAttribute(`x1`)),n.startY=Number(e.getAttribute(`y1`)),n.endX=Number(e.getAttribute(`x2`)),n.endY=Number(e.getAttribute(`y2`));else if(r===`path`){let t=e.getAttribute(`d`);if(!t)throw Error(`Path element does not contain a "d" attribute`);let r=t.split(/(?=[LC])/),i=r[0].substring(1).split(`,`).map(e=>parseFloat(e)),a=[];r.forEach(e=>{let t=e.substring(1).trim().split(` `).map(e=>{let[t,n]=e.split(`,`);return[parseFloat(t)-i[0],parseFloat(n)-i[1]]});a.push(...t)});let o=a[a.length-1];n.startX=i[0],n.startY=i[1],n.endX=o[0],n.endY=o[1],n.points=a}return t?.label&&(n.startY-=10,n.endY-=10),n.strokeColor=e.getAttribute(`stroke`),n.strokeWidth=Number(e.getAttribute(`stroke-width`)),n.type=`arrow`,n.strokeStyle=t?.strokeStyle||`solid`,n.startArrowhead=t?.startArrowhead||null,n.endArrowhead=t?.endArrowhead||null,n},P=(e,t,n,r,i)=>{let a={};return a.type=`arrow`,a.startX=e,a.startY=t,a.endX=n,a.endY=r,Object.assign(a,{...i}),a},F=(e,t,n,r)=>({type:`text`,x:e,y:t,text:n,width:r?.width||20,height:r?.height||20,fontSize:r?.fontSize||20,id:r?.id,groupId:r?.groupId,metadata:r?.metadata}),I=(e,t,n)=>{let r={},i=Number(e.getAttribute(`x`)),a=Number(e.getAttribute(`y`));r.type=`text`,r.text=E(t),n?.id&&(r.id=n.id),n?.groupId&&(r.groupId=n.groupId);let o=e.getBBox();return r.width=o.width,r.height=o.height,r.x=i-o.width/2,r.y=a,r.fontSize=parseInt(getComputedStyle(e).fontSize),r},L=(e,t,n={})=>{let r={};r.type=t;let{label:i,subtype:a,id:o,groupId:s}=n;r.id=o,s&&(r.groupId=s),i&&(r.label={text:E(i.text),fontSize:16,verticalAlign:i?.verticalAlign});let c=e.getBBox();switch(r.x=c.x,r.y=c.y,r.width=c.width,r.height=c.height,r.subtype=a,a){case`highlight`:let t=e.getAttribute(`fill`);t&&(r.bgColor=t);break;case`note`:r.strokeStyle=`dashed`;break}return r},R=(e,t,n,r,i,a)=>{let o={};return o.startX=t,o.startY=n,o.endX=r,a?.groupId&&(o.groupId=a.groupId),a?.id&&(o.id=a.id),o.endY=i,o.strokeColor=e.getAttribute(`stroke`),o.strokeWidth=Number(e.getAttribute(`stroke-width`)),o.type=`line`,o},z={0:`SOLID`,1:`DOTTED`,3:`SOLID_CROSS`,4:`DOTTED_CROSS`,5:`SOLID_OPEN`,6:`DOTTED_OPEN`,24:`SOLID_POINT`,25:`DOTTED_POINT`},B={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23,SOLID_POINT:24,DOTTED_POINT:25,AUTONUMBER:26,CRITICAL_START:27,CRITICAL_OPTION:28,CRITICAL_END:29,BREAK_START:30,BREAK_END:31,PAR_OVER_START:32},V=e=>{let t;switch(e){case B.SOLID:case B.SOLID_CROSS:case B.SOLID_OPEN:case B.SOLID_POINT:t=`solid`;break;case B.DOTTED:case B.DOTTED_CROSS:case B.DOTTED_OPEN:case B.DOTTED_POINT:t=`dotted`;break;default:t=`solid`;break}return t},H=(e,t)=>{if(e.nextElementSibling?.classList.contains(`sequenceNumber`)){let n=e.nextElementSibling?.textContent;if(!n)throw Error(`sequence number not present`);let r={type:`rectangle`,x:t.startX-10,y:t.startY-15,label:{text:n,fontSize:14},bgColor:`#e9ecef`,height:30,subtype:`sequence`};Object.assign(t,{sequenceNumber:r})}},U=(e,n,r)=>{if(!e)throw`root node not found`;let i=_(),a=Array.from(e.children),o=[];return a.forEach((e,a)=>{let s=`${r?.id}-${a}`,c;switch(e.tagName){case`line`:c=R(e,Number(e.getAttribute(`x1`)),Number(e.getAttribute(`y1`)),Number(e.getAttribute(`x2`)),Number(e.getAttribute(`y2`)),{groupId:i,id:s});break;case`text`:c=I(e,n,{groupId:i,id:s});break;case`circle`:c=L(e,`ellipse`,{label:e.textContent?{text:e.textContent}:void 0,groupId:i,id:s});default:c=L(e,t[e.tagName],{label:e.textContent?{text:e.textContent}:void 0,groupId:i,id:s})}o.push(c)}),o},W=(e,t)=>{let n=Array.from(t.querySelectorAll(`.actor-top`)),r=Array.from(t.querySelectorAll(`.actor-bottom`)),i=[],a=[];return Object.values(e).forEach((e,t)=>{let o=n.find(t=>t.getAttribute(`name`)===e.name),s=r.find(t=>t.getAttribute(`name`)===e.name);if(!o||!s)throw`root not found`;let c=e.description;if(e.type===`participant`){let t=L(o,`rectangle`,{id:`${e.name}-top`,label:{text:c},subtype:`actor`});if(!t)throw`Top Node element not found!`;i.push([t]);let n=L(s,`rectangle`,{id:`${e.name}-bottom`,label:{text:c},subtype:`actor`});i.push([n]);let r=o?.parentElement?.previousElementSibling;if(r?.tagName!==`line`)throw`Line not found`;let l=Number(r.getAttribute(`x1`));if(!t.height)throw`Top node element height is null`;let u=t.y+t.height,d=n.y,f=R(r,l,u,Number(r.getAttribute(`x2`)),d);a.push(f)}else if(e.type===`actor`){let t=U(o,c,{id:`${e.name}-top`});i.push(t);let n=U(s,c,{id:`${e.name}-bottom`});i.push(n);let r=o.previousElementSibling;if(r?.tagName!==`line`)throw`Line not found`;let l=Number(r.getAttribute(`x1`)),u=Number(r.getAttribute(`y1`)),d=Number(r.getAttribute(`x2`)),f=n.find(e=>e.type===`ellipse`);if(f){let e=f.y,t=R(r,l,u,d,e);a.push(t)}}}),{nodes:i,lines:a}},G=(e,t)=>{let n=[],r=Array.from(t.querySelectorAll(`[class*="messageLine"]`)),i=Object.keys(z),a=e.filter(e=>i.includes(e.type.toString()));return r.forEach((e,t)=>{let r=a[t],i=z[r.type],o=re(e,{label:r?.message,strokeStyle:V(r.type),endArrowhead:i===`SOLID_OPEN`||i===`DOTTED_OPEN`?null:`arrow`});H(e,o),n.push(o)}),n},K=(e,t)=>{let n=Array.from(t.querySelectorAll(`.note`)).map(e=>e.parentElement),r=e.filter(e=>e.type===B.NOTE),i=[];return n.forEach((e,t)=>{if(!e)return;let n=e.firstChild,a=r[t].message,o=L(n,`rectangle`,{label:{text:a},subtype:`note`});i.push(o)}),i},q=e=>{let t=Array.from(e.querySelectorAll(`[class*=activation]`)),n=[];return t.forEach(e=>{let t=L(e,`rectangle`,{label:{text:``},subtype:`activation`});n.push(t)}),n},J=(e,t)=>{let n=Array.from(t.querySelectorAll(`.loopLine`)),r=[],i=[],a=[];n.forEach(e=>{let t=R(e,Number(e.getAttribute(`x1`)),Number(e.getAttribute(`y1`)),Number(e.getAttribute(`x2`)),Number(e.getAttribute(`y2`)));t.strokeStyle=`dotted`,t.strokeColor=`#adb5bd`,t.strokeWidth=2,r.push(t)});let o=Array.from(t.querySelectorAll(`.loopText`)),s=e.filter(e=>e.type===B.CRITICAL_START).map(e=>e.message);o.forEach(e=>{let t=e.textContent||``,n=I(e,t),r=t.match(/\[(.*?)\]/)?.[1]||``;s.includes(r)&&(n.x+=16),i.push(n)});let c=Array.from(t?.querySelectorAll(`.labelBox`)),l=Array.from(t?.querySelectorAll(`.labelText`));return c.forEach((e,t)=>{let n=L(e,`rectangle`,{label:{text:l[t]?.textContent||``}});n.strokeColor=`#adb5bd`,n.bgColor=`#e9ecef`,n.width=void 0,a.push(n)}),{lines:r,texts:i,nodes:a}},Y=e=>{let t=Array.from(e.querySelectorAll(`.rect`)).filter(e=>e.parentElement?.tagName!==`g`),n=[];return t.forEach(e=>{let t=L(e,`rectangle`,{label:{text:``},subtype:`highlight`});n.push(t)}),n},ie=(e,t)=>{e.parse();let n=e.parser.yy,r=[],i=n.getBoxes(),a=Y(t),{nodes:o,lines:s}=W(n.getActors(),t),c=n.getMessages(),l=G(c,t),u=K(c,t),d=q(t),f=J(c,t);return r.push(a),r.push(...o),r.push(u),r.push(d),{type:`sequence`,lines:s,arrows:l,nodes:r,loops:f,groups:i}},X={AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3,LOLLIPOP:4},Z={LINE:0,DOTTED_LINE:1},ae=e=>{let t;switch(e){case Z.LINE:t=`solid`;break;case Z.DOTTED_LINE:t=`dotted`;break;default:t=`solid`}return t},Q=e=>{let t;switch(e){case X.AGGREGATION:t=`diamond_outline`;break;case X.COMPOSITION:t=`diamond`;break;case X.EXTENSION:t=`triangle_outline`;break;case`none`:t=null;break;case X.DEPENDENCY:default:t=`arrow`;break}return t},oe=(e,t)=>{let n=[],r=[],i=[];return Object.values(e).forEach(e=>{let{domId:a,id:o}=e,s=_(),c=t.querySelector(`[data-id=${o}]`);if(!c)throw Error(`DOM Node with id ${a} not found`);let{transformX:l,transformY:u}=D(c),d=L(c.firstChild,`rectangle`,{id:o,groupId:s});d.x+=l,d.y+=u,d.metadata={classId:o},n.push(d),Array.from(c.querySelectorAll(`.divider`)).forEach(e=>{let t=R(e,Number(e.getAttribute(`x1`)),Number(e.getAttribute(`y1`)),Number(e.getAttribute(`x2`)),Number(e.getAttribute(`y2`)),{groupId:s,id:_()});t.startX+=l,t.startY+=u,t.endX+=l,t.endY+=u,t.metadata={classId:o},r.push(t)});let f=c.querySelector(`.label`)?.children;if(!f)throw`label nodes not found`;Array.from(f).forEach(e=>{let t=e.textContent;if(!t)return;let n=_(),{transformX:r,transformY:a}=D(e),c=e.getBBox(),d=F(l+r,u+a+10,t,{width:c.width,height:c.height,id:n,groupId:s,metadata:{classId:o}});i.push(d)})}),{nodes:n,lines:r,text:i}},se=(e,t)=>{let n=[`triangle_outline`,`diamond`,`diamond_outline`],r=t.startArrowhead&&n.includes(t.startArrowhead),i=t.endArrowhead&&n.includes(t.endArrowhead);return!i&&!r?t:(r&&(e===`LR`?t.startX-=16:e===`RL`?t.startX+=16:e===`TB`?t.startY-=16:e===`BT`&&(t.startY+=16)),i&&(e===`LR`?t.endX+=16:e===`RL`?t.endX-=16:e===`TB`?t.endY+=16:e===`BT`&&(t.endY-=16)),t)},ce=(e,t,n,r)=>{let i=n.querySelector(`.edgePaths`)?.children;if(!i)throw Error(`No Edges found!`);let a=[],o=[];return e.forEach((e,n)=>{let{id1:s,id2:c,relation:l}=e,u=t.find(e=>e.id===s),d=t.find(e=>e.id===c),f=ae(l.lineType),p=Q(l.type1),m=Q(l.type2),h=k(i[n]),g=se(r,P(h.startX,h.startY,h.endX,h.endY,{strokeStyle:f,startArrowhead:p,endArrowhead:m,label:e.title?{text:e.title}:void 0,start:{type:`rectangle`,id:u.id},end:{type:`rectangle`,id:d.id}}));a.push(g);let{relationTitle1:_,relationTitle2:v}=e,y,b;if(_&&_!==`none`){switch(r){case`TB`:y=g.startX-20,g.endX<g.startX&&(y-=15),b=g.startY+15;break;case`BT`:y=g.startX+20,g.endX>g.startX&&(y+=15),b=g.startY-15;break;case`LR`:y=g.startX+20,b=g.startY+15,g.endY>g.startY&&(b+=15);break;case`RL`:y=g.startX-20,b=g.startY-15,g.startY>g.endY&&(b-=15);break;default:y=g.startX-20,b=g.startY+15}let e=F(y,b,_,{fontSize:16});o.push(e)}if(v&&v!==`none`){switch(r){case`TB`:y=g.endX+20,g.endX<g.startX&&(y+=15),b=g.endY-15;break;case`BT`:y=g.endX-20,g.endX>g.startX&&(y-=15),b=g.endY+15;break;case`LR`:y=g.endX-20,b=g.endY-15,g.endY>g.startY&&(b-=15);break;case`RL`:y=g.endX+20,b=g.endY+15,g.startY>g.endY&&(b+=15);break;default:y=g.endX+20,b=g.endY-15}let e=F(y,b,v,{fontSize:16});o.push(e)}}),{arrows:a,text:o}},le=(e,t,n)=>{let r=[],i=[];return e.forEach(e=>{let{id:a,text:o,class:s}=e,c=t.querySelector(`#${a}`);if(!c)throw Error(`Node with id ${a} not found!`);let{transformX:l,transformY:u}=D(c),d=c.firstChild,f=L(d,`rectangle`,{id:a,subtype:`note`,label:{text:o}});if(Object.assign(f,{x:f.x+l,y:f.y+u}),r.push(f),s){let e=n.find(e=>e.id===s);if(!e)throw Error(`class node with id ${s} not found!`);let t=f.x+(f.width||0)/2,r=f.y+(f.height||0),a=t,o=e.y,c=P(t,r,a,o,{strokeStyle:`dotted`,startArrowhead:null,endArrowhead:null,start:{id:f.id,type:`rectangle`},end:{id:e.id,type:`rectangle`}});i.push(c)}}),{notes:r,connectors:i}},$=(e,t)=>{e.parse();let n=e.parser.yy,r=n.getDirection(),i=[],a=[],o=[],s=[],c=n.getNamespaces(),l=n.getClasses();if(Object.keys(l).length){let e=oe(l,t);i.push(e.nodes),a.push(...e.lines),o.push(...e.text),s.push(...e.nodes)}let{arrows:u,text:d}=ce(n.getRelations(),s,t,r),{notes:f,connectors:p}=le(n.getNotes(),t,s);return i.push(f),u.push(...p),o.push(...d),{type:`class`,nodes:i,lines:a,arrows:u,text:o,namespaces:c}},ue=e=>{let t=e.querySelector(`svg`);if(!t)throw Error(`SVG element not found`);let n=t.getBoundingClientRect(),r=n.width,i=n.height;t.setAttribute(`width`,`${r}`),t.setAttribute(`height`,`${i}`);let a=unescape(encodeURIComponent(t.outerHTML));return{type:`graphImage`,mimeType:`image/svg+xml`,dataURL:`data:image/svg+xml;base64,${btoa(a)}`,width:r,height:i}},de=async(t,r=n)=>{e.initialize({...n,...r});let i=await e.mermaidAPI.getDiagramFromText(te(t)),{svg:a}=await e.render(`mermaid-to-excalidraw`,t),o=document.createElement(`div`);o.setAttribute(`style`,`opacity: 0; position: relative; z-index: -1;`),o.innerHTML=a,o.id=`mermaid-diagram`,document.body.appendChild(o);let s;switch(i.type){case`flowchart-v2`:s=ne(i,o);break;case`sequence`:s=ie(i,o);break;case`classDiagram`:s=$(i,o);break;default:s=ue(o)}return o.remove(),s};async function fe(t){return e.parse(t,{suppressErrors:!0})}const pe=async(e,t)=>{let n=t||{},r=parseInt(n.themeVariables?.fontSize??``)||20;return T(await de(e,{...n,themeVariables:{...n.themeVariables,fontSize:`${r*1.25}px`}}),{fontSize:r})};export{pe as parseMermaidToExcalidraw,fe as validateMermaid};
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@brrock/excalidraw-mermaid",
3
+ "version": "1.1.4",
4
+ "description": "A parser to convert Mermaid diagrams to Excalidraw format",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsdown",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "lint": "eslint src",
22
+ "format": "prettier --write src",
23
+ "prepublishOnly": "bun run build"
24
+ },
25
+ "keywords": [
26
+ "excalidraw",
27
+ "mermaid",
28
+ "diagram",
29
+ "parser"
30
+ ],
31
+ "author": "Excalidraw",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/excalidraw/mermaid-to-excalidraw"
36
+ },
37
+ "peerDependencies": {
38
+ "mermaid": "^10.9.0 || ^11.0.0"
39
+ },
40
+ "dependencies": {
41
+ "nanoid": "^5.1.6"
42
+ },
43
+ "devDependencies": {
44
+ "@excalidraw/eslint-config": "^1.0.3",
45
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
46
+ "@typescript-eslint/parser": "^8.56.0",
47
+ "eslint": "^8.42.0",
48
+ "eslint-config-prettier": "^8.8.0",
49
+ "eslint-plugin-prettier": "^4.2.1",
50
+ "prettier": "^2.8.8",
51
+ "tsdown": "^0.20.3",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^3.0.0"
54
+ }
55
+ }