├── .gitignore ├── tsconfig.json ├── .vscode └── settings.json ├── src ├── types.ts ├── main.ts └── ui.tsx ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | *.css.d.ts 4 | /build/ 5 | /manifest.json 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@create-figma-plugin/tsconfig", 3 | "compilerOptions": { 4 | "typeRoots": ["node_modules/@figma", "node_modules/@types"] 5 | }, 6 | "include": ["src/**/*.ts", "src/**/*.tsx"] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "json.schemas": [ 3 | { 4 | "fileMatch": [ 5 | "package.json" 6 | ], 7 | "url": "https://yuanqing.github.io/create-figma-plugin/figma-plugin.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { EventHandler } from "@create-figma-plugin/utilities"; 2 | 3 | export interface ConvertHandler extends EventHandler { 4 | name: "CONVERT"; 5 | handler: () => void; 6 | } 7 | 8 | export interface GraphHandler extends EventHandler { 9 | name: "graph"; 10 | handler: (graph: { 11 | edges: { 12 | start: string; 13 | end: string; 14 | text: string; 15 | arrowStart: ConnectorStrokeCap; 16 | arrowEnd: ConnectorStrokeCap; 17 | }[]; 18 | nodes: Record< 19 | string, 20 | { 21 | text: string; 22 | } 23 | >; 24 | }) => void; 25 | } 26 | 27 | export interface CloseHandler extends EventHandler { 28 | name: "CLOSE"; 29 | handler: () => void; 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@create-figma-plugin/ui": "^3.0.2", 4 | "@create-figma-plugin/utilities": "^3.0.2", 5 | "mermaid": "^10.6.1", 6 | "preact": ">=10" 7 | }, 8 | "devDependencies": { 9 | "@create-figma-plugin/build": "^3.0.2", 10 | "@create-figma-plugin/tsconfig": "^3.0.2", 11 | "@figma/plugin-typings": "1.79.0", 12 | "typescript": ">=4" 13 | }, 14 | "scripts": { 15 | "build": "build-figma-plugin --typecheck --minify", 16 | "watch": "build-figma-plugin --typecheck --watch" 17 | }, 18 | "figma-plugin": { 19 | "editorType": [ 20 | "figma" 21 | ], 22 | "id": "preact-rectangles", 23 | "name": "Preact Rectangles", 24 | "main": "src/main.ts", 25 | "ui": "src/ui.tsx" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { emit, once, showUI } from "@create-figma-plugin/utilities"; 2 | 3 | import { CloseHandler, ConvertHandler } from "./types"; 4 | 5 | export default function () { 6 | once("CONVERT", function () { 7 | const connectors = figma.root.findAll( 8 | (node) => node.type === "CONNECTOR", 9 | ) as ConnectorNode[]; 10 | 11 | let edges = []; 12 | let nodes: Record = {}; 13 | 14 | for (let connector of connectors) { 15 | const start = connector.connectorStart; 16 | const end = connector.connectorEnd; 17 | 18 | if ("endpointNodeId" in start && "endpointNodeId" in end) { 19 | const startNode = figma.getNodeById(start.endpointNodeId); 20 | const endNode = figma.getNodeById(end.endpointNodeId); 21 | 22 | if (startNode?.type === "STICKY") { 23 | nodes[startNode.id] = { text: startNode.text.characters }; 24 | } 25 | 26 | if (endNode?.type === "STICKY") { 27 | nodes[endNode.id] = { text: endNode.text.characters }; 28 | } 29 | 30 | edges.push({ 31 | start: start.endpointNodeId, 32 | end: end.endpointNodeId, 33 | text: connector.text.characters, 34 | arrowStart: connector.connectorStartStrokeCap, 35 | arrowEnd: connector.connectorEndStrokeCap, 36 | }); 37 | } 38 | 39 | emit("graph", { edges, nodes }); 40 | } 41 | }); 42 | once("CLOSE", function () { 43 | figma.closePlugin(); 44 | }); 45 | showUI({ 46 | height: 380, 47 | width: 360, 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Figjam to mermaid 2 | 3 | Quick plugin that translates from a Figjam document composed 4 | of Stickies and Connectors into a [mermaid](https://mermaid.js.org/syntax/flowchart.html) flowchart. 5 | 6 | - [x] Sticky connections 7 | - [x] Arrow styles (except for "filled triangle") 8 | - [ ] Fill & other styles 9 | - [ ] Support for any other kinds of elements not just stickies & connectors 10 | - [ ] Exporting based on a selection instead of the whole document. 11 | 12 | ## Development guide 13 | 14 | _This plugin is built with [Create Figma Plugin](https://yuanqing.github.io/create-figma-plugin/)._ 15 | 16 | ### Pre-requisites 17 | 18 | - [Node.js](https://nodejs.org) – v18 19 | - [Figma desktop app](https://figma.com/downloads/) 20 | 21 | ### Build the plugin 22 | 23 | To build the plugin: 24 | 25 | ``` 26 | $ npm run build 27 | ``` 28 | 29 | This will generate a [`manifest.json`](https://figma.com/plugin-docs/manifest/) file and a `build/` directory containing the JavaScript bundle(s) for the plugin. 30 | 31 | To watch for code changes and rebuild the plugin automatically: 32 | 33 | ``` 34 | $ npm run watch 35 | ``` 36 | 37 | ### Install the plugin 38 | 39 | 1. In the Figma desktop app, open a Figma document. 40 | 2. Search for and run `Import plugin from manifest…` via the Quick Actions search bar. 41 | 3. Select the `manifest.json` file that was generated by the `build` script. 42 | 43 | ### Debugging 44 | 45 | Use `console.log` statements to inspect values in your code. 46 | 47 | To open the developer console, search for and run `Open Console` via the Quick Actions search bar. 48 | 49 | ## See also 50 | 51 | - [Create Figma Plugin docs](https://yuanqing.github.io/create-figma-plugin/) 52 | - [`yuanqing/figma-plugins`](https://github.com/yuanqing/figma-plugins#readme) 53 | 54 | Official docs and code samples from Figma: 55 | 56 | - [Plugin API docs](https://figma.com/plugin-docs/) 57 | - [`figma/plugin-samples`](https://github.com/figma/plugin-samples#readme) 58 | -------------------------------------------------------------------------------- /src/ui.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Columns, 4 | Container, 5 | render, 6 | TextboxMultiline, 7 | VerticalSpace, 8 | } from "@create-figma-plugin/ui"; 9 | import { on, emit } from "@create-figma-plugin/utilities"; 10 | import { h } from "preact"; 11 | import { useCallback, useEffect, useState } from "preact/hooks"; 12 | 13 | import { CloseHandler, ConvertHandler, GraphHandler } from "./types"; 14 | 15 | function Plugin() { 16 | const [mermaid, setMermaid] = useState(""); 17 | useEffect(() => { 18 | emit("CONVERT"); 19 | }, []); 20 | const handleCloseButtonClick = useCallback(function () { 21 | emit("CLOSE"); 22 | }, []); 23 | 24 | on("graph", (graph) => { 25 | const lines = []; 26 | const described = new Set(); 27 | for (let edge of graph.edges) { 28 | // Mermaid reuses the first description of a node. 29 | // So just describe once. 30 | let startDesc = ""; 31 | if (!described.has(edge.start)) { 32 | startDesc = `[${JSON.stringify(graph.nodes[edge.start].text)}]`; 33 | described.add(edge.start); 34 | } 35 | 36 | let endDesc = ""; 37 | if (!described.has(edge.end)) { 38 | endDesc = `[${JSON.stringify(graph.nodes[edge.end].text)}]`; 39 | described.add(edge.end); 40 | } 41 | 42 | let connectorExtra = ""; 43 | if (edge.text) { 44 | connectorExtra = `|${edge.text}|`; 45 | } 46 | 47 | const caps: Record = { 48 | NONE: ["-", "-"], 49 | ARROW_LINES: ["<", ">"], 50 | ARROW_EQUILATERAL: ["<", ">"], 51 | CIRCLE_FILLED: ["o", "o"], 52 | DIAMOND_FILLED: ["-", "-"], 53 | TRIANGLE_FILLED: ["<", ">"], 54 | }; 55 | 56 | lines.push( 57 | ` ${edge.start}${startDesc} ${caps[edge.arrowStart][0]}--${ 58 | caps[edge.arrowEnd][1] 59 | }${connectorExtra} ${edge.end}${endDesc}`, 60 | ); 61 | } 62 | console.log(lines, graph); 63 | setMermaid(`flowchart LR\n${lines.join("\n")}`); 64 | }); 65 | 66 | return ( 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | ); 79 | } 80 | 81 | export default render(Plugin); 82 | --------------------------------------------------------------------------------