miro-export 1.0.0 → 1.0.1
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/README.md +37 -2
- package/build/index.js +24 -3
- package/index.ts +73 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Miro board exporter
|
|
2
2
|
|
|
3
|
-
Exports Miro frames as full-detail SVGs using a headless Puppeteer browser. Requires a personal Miro token.
|
|
3
|
+
Exports Miro frames as full-detail SVGs or JSON using a headless Puppeteer browser. Requires a personal Miro token.
|
|
4
4
|
|
|
5
5
|
## Getting the Miro token
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ Options:
|
|
|
14
14
|
-b, --board-id <boardId> The board ID
|
|
15
15
|
-f, --frame-names <frameNames...> The frame name(s), leave empty to export entire board
|
|
16
16
|
-o, --output-file <filename> A file to output the SVG to (stdout if not supplied)
|
|
17
|
+
-e, --export-format <format> 'svg' or 'json' (default: 'svg')
|
|
17
18
|
-h, --help display help for command
|
|
18
19
|
```
|
|
19
20
|
|
|
@@ -28,8 +29,42 @@ miro-export -t XYZ -b uMoVLkx8gIc=
|
|
|
28
29
|
|
|
29
30
|
# export "Frame 2" and "Frame 3" to "Frame 2.svg" and "Frame 3.svg" respectively
|
|
30
31
|
miro-export -t XYZ -b uMoVLkx8gIc= -f "Frame 2" "Frame 3" -o "{frameName}.svg"
|
|
32
|
+
|
|
33
|
+
# export JSON representation of "Frame 2"
|
|
34
|
+
miro-export -t XYZ -b uMoVLkx8gIc= -f "Frame 2" -e json
|
|
31
35
|
```
|
|
32
36
|
|
|
33
37
|
## Capturing multiple frames at once
|
|
34
38
|
|
|
35
|
-
It is possible to supply multiple frames to the `-f` switch, e.g., `-f "Frame 2" "Frame 3"`. However, this will capture all content that is within the outer bounding box when all frames have been selected, so content between the frames will be captured as well. If you want separate SVGs for each frame, use the output file switch with `{frameName}` in the file name, e.g., `-o "Export - {frameName}.svg"`. It is not possible to export separate SVGs without the output file specified (i.e., to stdout).
|
|
39
|
+
It is possible to supply multiple frames to the `-f` switch, e.g., `-f "Frame 2" "Frame 3"`. However, for SVG export, this will capture all content that is within the outer bounding box when all frames have been selected, so content between the frames will be captured as well. If you want separate SVGs for each frame, use the output file switch with `{frameName}` in the file name, e.g., `-o "Export - {frameName}.svg"`. It is not possible to export separate SVGs without the output file specified (i.e., to stdout).
|
|
40
|
+
|
|
41
|
+
## JSON export
|
|
42
|
+
|
|
43
|
+
The JSON export format is a Miro-internal representation of all the board objects. It is not a documented format, but it is quite easy to understand. The exported format is always an array of objects that have the field `type` as a discriminator. Depending on the type, fields change, but there is always at least an `id` field. For example, a `sticky_note` object could look like this:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"type": "sticky_note",
|
|
48
|
+
"shape": "square",
|
|
49
|
+
"content": "<p>Test content</p>",
|
|
50
|
+
"style": {
|
|
51
|
+
"fillColor": "cyan",
|
|
52
|
+
"textAlign": "center",
|
|
53
|
+
"textAlignVertical": "middle"
|
|
54
|
+
},
|
|
55
|
+
"tagIds": [],
|
|
56
|
+
"id": "3458764564249021457",
|
|
57
|
+
"parentId": "3458764564247784511",
|
|
58
|
+
"origin": "center",
|
|
59
|
+
"relativeTo": "parent_top_left",
|
|
60
|
+
"createdAt": "2023-09-11T12:45:00.041Z",
|
|
61
|
+
"createdBy": "3458764537906310005",
|
|
62
|
+
"modifiedAt": "2023-09-11T12:46:01.041Z",
|
|
63
|
+
"modifiedBy": "3458764537906310005",
|
|
64
|
+
"connectorIds": [],
|
|
65
|
+
"x": 129.29101113436059,
|
|
66
|
+
"y": 201.25587788616645,
|
|
67
|
+
"width": 101.46000000000001,
|
|
68
|
+
"height": 125.12
|
|
69
|
+
}
|
|
70
|
+
```
|
package/build/index.js
CHANGED
|
@@ -7,11 +7,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const promises_1 = require("fs/promises");
|
|
8
8
|
const puppeteer_1 = __importDefault(require("puppeteer"));
|
|
9
9
|
const extra_typings_1 = require("@commander-js/extra-typings");
|
|
10
|
-
const { token, boardId, frameNames, outputFile } = extra_typings_1.program
|
|
10
|
+
const { token, boardId, frameNames, outputFile, exportFormat } = extra_typings_1.program
|
|
11
11
|
.requiredOption("-t, --token <token>", "Miro token")
|
|
12
12
|
.requiredOption("-b, --board-id <boardId>", "The board ID")
|
|
13
13
|
.option("-f, --frame-names <frameNames...>", "The frame name(s), leave empty to export entire board")
|
|
14
14
|
.option("-o, --output-file <filename>", "A file to output the SVG to (stdout if not supplied)")
|
|
15
|
+
.option("-e, --export-format <format>", "'svg' or 'json' (default: 'svg')")
|
|
15
16
|
.parse()
|
|
16
17
|
.opts();
|
|
17
18
|
(async () => {
|
|
@@ -53,17 +54,37 @@ const { token, boardId, frameNames, outputFile } = extra_typings_1.program
|
|
|
53
54
|
}
|
|
54
55
|
return await window.cmd.board.api.export.makeVector();
|
|
55
56
|
}, frameNames);
|
|
57
|
+
const getJsonForFrames = (frameNames) => page.evaluate(async (frameNames) => {
|
|
58
|
+
if (frameNames) {
|
|
59
|
+
const frames = await window.miro.board.get({ type: ["frame"] });
|
|
60
|
+
const selectedFrames = frames.filter((frame) => frameNames.includes(frame.title));
|
|
61
|
+
if (selectedFrames.length !== frameNames.length) {
|
|
62
|
+
throw Error(`${frameNames.length - selectedFrames.length} frame(s) could not be found on the board.`);
|
|
63
|
+
}
|
|
64
|
+
const children = await window.miro.board.get({
|
|
65
|
+
id: selectedFrames.flatMap((frame) => frame.childrenIds)
|
|
66
|
+
});
|
|
67
|
+
const groupChildren = await window.miro.board.get({
|
|
68
|
+
id: children
|
|
69
|
+
.filter((child) => child.type === "group")
|
|
70
|
+
.flatMap((child) => child.itemsIds)
|
|
71
|
+
});
|
|
72
|
+
return JSON.stringify([...frames, ...children, ...groupChildren]);
|
|
73
|
+
}
|
|
74
|
+
return JSON.stringify(await window.miro.board.get({}));
|
|
75
|
+
}, frameNames);
|
|
76
|
+
const getFn = exportFormat === "json" ? getJsonForFrames : getSvgForFrames;
|
|
56
77
|
if (outputFile?.includes("{frameName}")) {
|
|
57
78
|
if (!frameNames) {
|
|
58
79
|
throw Error("Expected frame names to be given when the output file name format expects a frame name.");
|
|
59
80
|
}
|
|
60
81
|
for (const frameName of frameNames) {
|
|
61
|
-
const svg = await
|
|
82
|
+
const svg = await getFn([frameName]);
|
|
62
83
|
await (0, promises_1.writeFile)(outputFile.replace("{frameName}", frameName), svg);
|
|
63
84
|
}
|
|
64
85
|
}
|
|
65
86
|
else {
|
|
66
|
-
const svg = await
|
|
87
|
+
const svg = await getFn(frameNames);
|
|
67
88
|
if (outputFile) {
|
|
68
89
|
await (0, promises_1.writeFile)(outputFile, svg);
|
|
69
90
|
}
|
package/index.ts
CHANGED
|
@@ -2,13 +2,41 @@ import { writeFile } from "fs/promises";
|
|
|
2
2
|
import puppeteer from "puppeteer";
|
|
3
3
|
import { program } from "@commander-js/extra-typings";
|
|
4
4
|
|
|
5
|
+
type BoardObjectType = "frame" | "group" | "sticky_note" | "text";
|
|
6
|
+
interface BoardObjectBase {
|
|
7
|
+
title: string;
|
|
8
|
+
id: string;
|
|
9
|
+
type: BoardObjectType;
|
|
10
|
+
}
|
|
11
|
+
interface FrameBoardObject extends BoardObjectBase {
|
|
12
|
+
type: "frame";
|
|
13
|
+
title: string;
|
|
14
|
+
childrenIds: string[];
|
|
15
|
+
}
|
|
16
|
+
interface GroupBoardObject extends BoardObjectBase {
|
|
17
|
+
type: "group";
|
|
18
|
+
itemsIds: string[];
|
|
19
|
+
}
|
|
20
|
+
interface StickyNoteBoardObject extends BoardObjectBase {
|
|
21
|
+
type: "sticky_note";
|
|
22
|
+
}
|
|
23
|
+
interface TextBoardObject extends BoardObjectBase {
|
|
24
|
+
type: "text";
|
|
25
|
+
}
|
|
26
|
+
type BoardObject =
|
|
27
|
+
| FrameBoardObject
|
|
28
|
+
| GroupBoardObject
|
|
29
|
+
| StickyNoteBoardObject
|
|
30
|
+
| TextBoardObject;
|
|
31
|
+
|
|
5
32
|
declare global {
|
|
6
33
|
interface Window {
|
|
7
34
|
miro: {
|
|
8
35
|
board: {
|
|
9
36
|
get(opts: {
|
|
10
|
-
type
|
|
11
|
-
|
|
37
|
+
type?: BoardObjectType[];
|
|
38
|
+
id?: string[];
|
|
39
|
+
}): Promise<BoardObject[]>;
|
|
12
40
|
select(opts: { id: string }): Promise<void>;
|
|
13
41
|
deselect(): Promise<void>;
|
|
14
42
|
};
|
|
@@ -25,7 +53,7 @@ declare global {
|
|
|
25
53
|
}
|
|
26
54
|
}
|
|
27
55
|
|
|
28
|
-
const { token, boardId, frameNames, outputFile } = program
|
|
56
|
+
const { token, boardId, frameNames, outputFile, exportFormat } = program
|
|
29
57
|
.requiredOption("-t, --token <token>", "Miro token")
|
|
30
58
|
.requiredOption("-b, --board-id <boardId>", "The board ID")
|
|
31
59
|
.option(
|
|
@@ -36,6 +64,7 @@ const { token, boardId, frameNames, outputFile } = program
|
|
|
36
64
|
"-o, --output-file <filename>",
|
|
37
65
|
"A file to output the SVG to (stdout if not supplied)"
|
|
38
66
|
)
|
|
67
|
+
.option("-e, --export-format <format>", "'svg' or 'json' (default: 'svg')")
|
|
39
68
|
.parse()
|
|
40
69
|
.opts();
|
|
41
70
|
|
|
@@ -99,6 +128,45 @@ const { token, boardId, frameNames, outputFile } = program
|
|
|
99
128
|
return await window.cmd.board.api.export.makeVector();
|
|
100
129
|
}, frameNames);
|
|
101
130
|
|
|
131
|
+
const getJsonForFrames = (frameNames: string[] | undefined) =>
|
|
132
|
+
page.evaluate(async (frameNames) => {
|
|
133
|
+
if (frameNames) {
|
|
134
|
+
const frames = await window.miro.board.get({ type: ["frame"] });
|
|
135
|
+
|
|
136
|
+
const selectedFrames = frames.filter((frame) =>
|
|
137
|
+
frameNames.includes(frame.title)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (selectedFrames.length !== frameNames.length) {
|
|
141
|
+
throw Error(
|
|
142
|
+
`${
|
|
143
|
+
frameNames.length - selectedFrames.length
|
|
144
|
+
} frame(s) could not be found on the board.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const children = await window.miro.board.get({
|
|
149
|
+
id: selectedFrames.flatMap(
|
|
150
|
+
(frame) => (frame as FrameBoardObject).childrenIds
|
|
151
|
+
)
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const groupChildren = await window.miro.board.get({
|
|
155
|
+
id: children
|
|
156
|
+
.filter(
|
|
157
|
+
(child): child is GroupBoardObject => child.type === "group"
|
|
158
|
+
)
|
|
159
|
+
.flatMap((child) => child.itemsIds)
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return JSON.stringify([...frames, ...children, ...groupChildren]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return JSON.stringify(await window.miro.board.get({}));
|
|
166
|
+
}, frameNames);
|
|
167
|
+
|
|
168
|
+
const getFn = exportFormat === "json" ? getJsonForFrames : getSvgForFrames;
|
|
169
|
+
|
|
102
170
|
if (outputFile?.includes("{frameName}")) {
|
|
103
171
|
if (!frameNames) {
|
|
104
172
|
throw Error(
|
|
@@ -107,11 +175,11 @@ const { token, boardId, frameNames, outputFile } = program
|
|
|
107
175
|
}
|
|
108
176
|
|
|
109
177
|
for (const frameName of frameNames) {
|
|
110
|
-
const svg = await
|
|
178
|
+
const svg = await getFn([frameName]);
|
|
111
179
|
await writeFile(outputFile.replace("{frameName}", frameName), svg);
|
|
112
180
|
}
|
|
113
181
|
} else {
|
|
114
|
-
const svg = await
|
|
182
|
+
const svg = await getFn(frameNames);
|
|
115
183
|
if (outputFile) {
|
|
116
184
|
await writeFile(outputFile, svg);
|
|
117
185
|
} else {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miro-export",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"author": "jolle <npm-contact@jolle.io>",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"description": "Export Miro boards or frames as
|
|
6
|
+
"description": "Export Miro boards and/or frames as SVG or JSON",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"miro",
|
|
9
9
|
"svg",
|