@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 +21 -0
- package/README.md +77 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +2 -0
- package/package.json +55 -0
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
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|