miro-export 1.0.1 → 1.1.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/.github/workflows/build.yml +2 -2
- package/.github/workflows/dependabot-merge.yml +3 -8
- package/.github/workflows/publish.yml +1 -1
- package/.github/workflows/test.yml +23 -0
- package/.vscode/settings.json +3 -0
- package/README.md +61 -9
- package/build/cli.d.ts +1 -0
- package/build/cli.js +122 -0
- package/build/index.d.ts +24 -0
- package/build/index.js +89 -92
- package/build/miro-types.d.ts +188 -0
- package/build/miro-types.js +1 -0
- package/eslint.config.mjs +35 -0
- package/package.json +29 -14
- package/src/cli.ts +86 -0
- package/src/index.ts +145 -0
- package/src/miro-runtime.d.ts +28 -0
- package/src/miro-types.ts +298 -0
- package/tests/api.test.ts +68 -0
- package/tests/board-object-types.ts +54 -0
- package/tsconfig.json +9 -6
- package/index.ts +0 -192
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
type TextAlignment = "left" | "center" | "right";
|
|
2
|
+
type TextVerticalAlignment = "top" | "middle" | "bottom";
|
|
3
|
+
|
|
4
|
+
export type BoardObjectType =
|
|
5
|
+
| "text"
|
|
6
|
+
| "sticky_note"
|
|
7
|
+
| "shape"
|
|
8
|
+
| "image"
|
|
9
|
+
| "frame"
|
|
10
|
+
| "preview"
|
|
11
|
+
| "card"
|
|
12
|
+
| "app_card"
|
|
13
|
+
| "usm"
|
|
14
|
+
| "kanban"
|
|
15
|
+
| "document"
|
|
16
|
+
| "mockup"
|
|
17
|
+
| "curve"
|
|
18
|
+
| "webscreen"
|
|
19
|
+
| "table"
|
|
20
|
+
| "svg"
|
|
21
|
+
| "emoji"
|
|
22
|
+
| "embed"
|
|
23
|
+
| "connector"
|
|
24
|
+
| "unsupported"
|
|
25
|
+
| "table_text"
|
|
26
|
+
| "rallycard"
|
|
27
|
+
| "stencil"
|
|
28
|
+
| "tag"
|
|
29
|
+
| "code"
|
|
30
|
+
| "red"
|
|
31
|
+
| "stamp"
|
|
32
|
+
| "pipmatrix"
|
|
33
|
+
| "demo_1d_layout"
|
|
34
|
+
| "page"
|
|
35
|
+
| "action_button"
|
|
36
|
+
| "external_diagram"
|
|
37
|
+
| "slide_container"
|
|
38
|
+
| "sdk_custom_widget"
|
|
39
|
+
| "group"
|
|
40
|
+
| "struct_doc"
|
|
41
|
+
| "mindmap";
|
|
42
|
+
|
|
43
|
+
export interface BoardItemBase {
|
|
44
|
+
id?: string;
|
|
45
|
+
type: BoardObjectType;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface BoardObjectBase {
|
|
49
|
+
origin: "center";
|
|
50
|
+
linkedTo?: string;
|
|
51
|
+
connectorIds?: string[];
|
|
52
|
+
groupId?: string;
|
|
53
|
+
relativeTo: "canvas_center" | "parent_top_left" | "parent_center";
|
|
54
|
+
parentId?: string | null;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
createdBy: string;
|
|
57
|
+
modifiedAt: string;
|
|
58
|
+
modifiedBy: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface BoardObjectWithCoordinates {
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface BoardObjectWithDimensions {
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PreviewBoardObject
|
|
72
|
+
extends BoardItemBase,
|
|
73
|
+
BoardObjectBase,
|
|
74
|
+
BoardObjectWithDimensions {
|
|
75
|
+
type: "preview";
|
|
76
|
+
url: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface FrameBoardObject
|
|
80
|
+
extends Omit<BoardItemBase, "connectorIds">,
|
|
81
|
+
BoardObjectBase,
|
|
82
|
+
BoardObjectWithDimensions,
|
|
83
|
+
BoardObjectWithCoordinates {
|
|
84
|
+
type: "frame";
|
|
85
|
+
title: string;
|
|
86
|
+
childrenIds: string[];
|
|
87
|
+
showContent: boolean;
|
|
88
|
+
style: {
|
|
89
|
+
fillColor: string;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface GroupBoardItem extends BoardItemBase {
|
|
94
|
+
type: "group";
|
|
95
|
+
itemsIds: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface StickyNoteBoardObject
|
|
99
|
+
extends BoardItemBase,
|
|
100
|
+
BoardObjectBase,
|
|
101
|
+
BoardObjectWithCoordinates,
|
|
102
|
+
BoardObjectWithDimensions {
|
|
103
|
+
type: "sticky_note";
|
|
104
|
+
tagIds: string[];
|
|
105
|
+
content: string;
|
|
106
|
+
shape: string;
|
|
107
|
+
style: {
|
|
108
|
+
fillColor: string;
|
|
109
|
+
textAlign: TextAlignment;
|
|
110
|
+
textAlignVertical: TextVerticalAlignment;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface TextBoardObject
|
|
115
|
+
extends BoardItemBase,
|
|
116
|
+
BoardObjectBase,
|
|
117
|
+
BoardObjectWithCoordinates,
|
|
118
|
+
BoardObjectWithDimensions {
|
|
119
|
+
type: "text";
|
|
120
|
+
content: string;
|
|
121
|
+
rotation: number;
|
|
122
|
+
style: {
|
|
123
|
+
fillColor: string;
|
|
124
|
+
fillOpacity: number;
|
|
125
|
+
fontFamily: string;
|
|
126
|
+
fontSize: number;
|
|
127
|
+
textAlign: TextAlignment;
|
|
128
|
+
color: string;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ImageBoardObject
|
|
133
|
+
extends BoardItemBase,
|
|
134
|
+
BoardObjectBase,
|
|
135
|
+
BoardObjectWithDimensions,
|
|
136
|
+
BoardObjectWithCoordinates {
|
|
137
|
+
type: "image";
|
|
138
|
+
rotation: number;
|
|
139
|
+
title: string;
|
|
140
|
+
url: string;
|
|
141
|
+
alt?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface TableStyle {
|
|
145
|
+
borderColor?: string;
|
|
146
|
+
backgroundColor?: string;
|
|
147
|
+
backgroundOpacity?: number;
|
|
148
|
+
textColor?: string;
|
|
149
|
+
textAlign: TextAlignment;
|
|
150
|
+
textAlignVertical: TextVerticalAlignment;
|
|
151
|
+
textBold?: boolean;
|
|
152
|
+
textItalic?: boolean;
|
|
153
|
+
textUnderline?: boolean;
|
|
154
|
+
textStrike?: boolean;
|
|
155
|
+
textHighlight: null | string;
|
|
156
|
+
fontFamily?: string;
|
|
157
|
+
fontSize?: number;
|
|
158
|
+
writingMode?: "sideways" | "horizontal";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface TableBoardObject
|
|
162
|
+
extends BoardItemBase,
|
|
163
|
+
BoardObjectWithDimensions,
|
|
164
|
+
BoardObjectWithCoordinates {
|
|
165
|
+
type: "image";
|
|
166
|
+
rotation: number;
|
|
167
|
+
cols?: number | { width: number }[];
|
|
168
|
+
rows?: number | { height: number }[];
|
|
169
|
+
cells?: {
|
|
170
|
+
text: string;
|
|
171
|
+
rowspan?: number;
|
|
172
|
+
colspan?: number;
|
|
173
|
+
style: TableStyle;
|
|
174
|
+
}[][];
|
|
175
|
+
style: TableStyle;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface StructDocBoardObject
|
|
179
|
+
extends BoardItemBase,
|
|
180
|
+
BoardObjectBase,
|
|
181
|
+
BoardObjectWithDimensions,
|
|
182
|
+
BoardObjectWithCoordinates {
|
|
183
|
+
type: "struct_doc";
|
|
184
|
+
title: string;
|
|
185
|
+
content: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface EmbedBoardObject
|
|
189
|
+
extends BoardItemBase,
|
|
190
|
+
BoardObjectBase,
|
|
191
|
+
BoardObjectWithCoordinates {
|
|
192
|
+
type: "struct_doc";
|
|
193
|
+
url: string;
|
|
194
|
+
previewUrl: string;
|
|
195
|
+
mode: "inline" | "modal";
|
|
196
|
+
width?: number;
|
|
197
|
+
height?: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface ShapeBoardObject
|
|
201
|
+
extends BoardItemBase,
|
|
202
|
+
BoardObjectBase,
|
|
203
|
+
BoardObjectWithCoordinates,
|
|
204
|
+
BoardObjectWithDimensions {
|
|
205
|
+
type: "shape";
|
|
206
|
+
content: string;
|
|
207
|
+
shape: string;
|
|
208
|
+
rotation: number;
|
|
209
|
+
style: {
|
|
210
|
+
fillColor: string;
|
|
211
|
+
fontFamily: string;
|
|
212
|
+
fontSize: number;
|
|
213
|
+
textAlign: TextAlignment;
|
|
214
|
+
textAlignVertical: TextVerticalAlignment;
|
|
215
|
+
borderStyle: "dashed" | "normal" | "dotted";
|
|
216
|
+
borderOpacity: number;
|
|
217
|
+
borderColor: string;
|
|
218
|
+
borderWidth: number;
|
|
219
|
+
fillOpacity: number;
|
|
220
|
+
color: string;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
interface CardBase extends BoardObjectBase, BoardObjectWithDimensions {
|
|
225
|
+
rotation: number;
|
|
226
|
+
title: string;
|
|
227
|
+
description: string;
|
|
228
|
+
fields: {
|
|
229
|
+
fillColor?: string;
|
|
230
|
+
textColor?: string;
|
|
231
|
+
iconUrl?: string;
|
|
232
|
+
iconShape?: "round" | "square";
|
|
233
|
+
tooltip?: string;
|
|
234
|
+
value: string;
|
|
235
|
+
}[];
|
|
236
|
+
style: {
|
|
237
|
+
cardTheme: string;
|
|
238
|
+
fillBackground: boolean;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/** coordinates may be null if card is part of a Kanban board */
|
|
242
|
+
x: number | null;
|
|
243
|
+
y: number | null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface CardBoardObject extends BoardItemBase, CardBase {
|
|
247
|
+
type: "card";
|
|
248
|
+
assignee?: { userId: string };
|
|
249
|
+
dueDate?: string;
|
|
250
|
+
startDate?: string;
|
|
251
|
+
taskStatus: "to-do" | "in-progress" | "done" | "none";
|
|
252
|
+
tagIds: string[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface AppCardBoardObject extends BoardItemBase, CardBase {
|
|
256
|
+
type: "app_card";
|
|
257
|
+
owned: boolean;
|
|
258
|
+
status: "disabled" | "disconnected" | "connected";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface TagBoardItem extends BoardItemBase {
|
|
262
|
+
type: "tag";
|
|
263
|
+
title: string;
|
|
264
|
+
color: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface KanbanBoardObject
|
|
268
|
+
extends BoardItemBase,
|
|
269
|
+
BoardObjectBase,
|
|
270
|
+
BoardObjectWithCoordinates,
|
|
271
|
+
BoardObjectWithDimensions {
|
|
272
|
+
type: "kanban";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface UnsupportedBoardObject
|
|
276
|
+
extends BoardItemBase,
|
|
277
|
+
BoardObjectBase,
|
|
278
|
+
BoardObjectWithCoordinates,
|
|
279
|
+
BoardObjectWithDimensions {
|
|
280
|
+
type: "unsupported";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export type BoardObject =
|
|
284
|
+
| PreviewBoardObject
|
|
285
|
+
| FrameBoardObject
|
|
286
|
+
| GroupBoardItem
|
|
287
|
+
| StickyNoteBoardObject
|
|
288
|
+
| TextBoardObject
|
|
289
|
+
| ImageBoardObject
|
|
290
|
+
| TableBoardObject
|
|
291
|
+
| StructDocBoardObject
|
|
292
|
+
| EmbedBoardObject
|
|
293
|
+
| ShapeBoardObject
|
|
294
|
+
| CardBoardObject
|
|
295
|
+
| AppCardBoardObject
|
|
296
|
+
| TagBoardItem
|
|
297
|
+
| KanbanBoardObject
|
|
298
|
+
| UnsupportedBoardObject;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { MiroBoard } from "../src";
|
|
4
|
+
|
|
5
|
+
const boardId = process.env.TEST_BOARD_ID;
|
|
6
|
+
|
|
7
|
+
if (!boardId) {
|
|
8
|
+
console.error("TEST_BOARD_ID environment variable is required.");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("Miro integration", async () => {
|
|
13
|
+
const miroBoard = new MiroBoard({ boardId });
|
|
14
|
+
|
|
15
|
+
after(async () => {
|
|
16
|
+
await miroBoard.dispose();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await it("should get all objects", async () => {
|
|
20
|
+
const objects = await miroBoard.getBoardObjects({});
|
|
21
|
+
|
|
22
|
+
assert.ok(
|
|
23
|
+
objects.find(
|
|
24
|
+
(obj) => obj.type === "sticky_note" && obj.content === "<p>Test 1</p>"
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await it("should filter objects by type = 'frame'", async () => {
|
|
30
|
+
const frames = await miroBoard.getBoardObjects({ type: "frame" });
|
|
31
|
+
|
|
32
|
+
assert.ok(frames.length > 0);
|
|
33
|
+
assert.ok(frames.every((frame) => frame.type === "frame"));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await it("should filter objects by title = 'Frame 1'", async () => {
|
|
37
|
+
const objects = await miroBoard.getBoardObjects({}, { title: "Frame 1" });
|
|
38
|
+
|
|
39
|
+
assert.ok(
|
|
40
|
+
objects.find((obj) => obj.type === "frame" && obj.title === "Frame 1")
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await it("should get SVG for entire board", async () => {
|
|
45
|
+
const svg = await miroBoard.getSvg();
|
|
46
|
+
|
|
47
|
+
assert.ok(svg.includes("<svg"));
|
|
48
|
+
assert.ok(svg.includes("Card text"));
|
|
49
|
+
assert.ok(svg.includes("STAR"));
|
|
50
|
+
assert.ok(svg.length > 50_000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await it("should get SVG for specific frame", async () => {
|
|
54
|
+
const frames = await miroBoard.getBoardObjects(
|
|
55
|
+
{ type: "frame" },
|
|
56
|
+
{ title: "Frame 2" }
|
|
57
|
+
);
|
|
58
|
+
const frameId = frames[0].id;
|
|
59
|
+
assert.ok(frameId);
|
|
60
|
+
|
|
61
|
+
const svg = await miroBoard.getSvg([frameId]);
|
|
62
|
+
|
|
63
|
+
assert.ok(svg.includes("<svg"));
|
|
64
|
+
assert.ok(!svg.includes("Card text"));
|
|
65
|
+
assert.ok(svg.includes("STAR"));
|
|
66
|
+
assert.ok(svg.length > 10_000);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { MiroBoard } from "../src";
|
|
3
|
+
import * as tsj from "ts-json-schema-generator";
|
|
4
|
+
import Ajv from "ajv";
|
|
5
|
+
|
|
6
|
+
(async () => {
|
|
7
|
+
const config = {
|
|
8
|
+
path: resolve(import.meta.dirname, "../src/miro-types.ts"),
|
|
9
|
+
tsconfig: resolve(import.meta.dirname, "../tsconfig.json"),
|
|
10
|
+
type: "BoardObject"
|
|
11
|
+
};
|
|
12
|
+
const schema = tsj.createGenerator(config).createSchema(config.type);
|
|
13
|
+
|
|
14
|
+
const boardId = process.env.TEST_BOARD_ID;
|
|
15
|
+
|
|
16
|
+
if (!boardId) {
|
|
17
|
+
console.error("TEST_BOARD_ID environment variable is required.");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const miroBoard = new MiroBoard({ boardId });
|
|
22
|
+
const objects = await miroBoard.getBoardObjects({});
|
|
23
|
+
await miroBoard.dispose();
|
|
24
|
+
|
|
25
|
+
for (const object of objects) {
|
|
26
|
+
const ajv = new Ajv();
|
|
27
|
+
if (!ajv.validate(schema, object)) {
|
|
28
|
+
console.log(
|
|
29
|
+
"Failed to validate object: ",
|
|
30
|
+
JSON.stringify(
|
|
31
|
+
object,
|
|
32
|
+
process.env.CI
|
|
33
|
+
? (key, value) => {
|
|
34
|
+
if (key === "type") {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
return "string";
|
|
40
|
+
} else if (typeof value === "number") {
|
|
41
|
+
return 0;
|
|
42
|
+
} else {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
2
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
console.error(ajv.errors);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})();
|
package/tsconfig.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"lib": ["DOM"],
|
|
5
|
-
"module": "
|
|
6
|
-
"
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"lib": ["DOM", "ESNext"],
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "nodenext",
|
|
7
|
+
"declaration": true,
|
|
7
8
|
"outDir": "./build",
|
|
8
9
|
"esModuleInterop": true,
|
|
9
10
|
"forceConsistentCasingInFileNames": true,
|
|
10
11
|
"strict": true,
|
|
11
|
-
"skipLibCheck": true
|
|
12
|
-
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"verbatimModuleSyntax": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src", "src/miro-runtime.d.ts"]
|
|
13
16
|
}
|
package/index.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { writeFile } from "fs/promises";
|
|
2
|
-
import puppeteer from "puppeteer";
|
|
3
|
-
import { program } from "@commander-js/extra-typings";
|
|
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
|
-
|
|
32
|
-
declare global {
|
|
33
|
-
interface Window {
|
|
34
|
-
miro: {
|
|
35
|
-
board: {
|
|
36
|
-
get(opts: {
|
|
37
|
-
type?: BoardObjectType[];
|
|
38
|
-
id?: string[];
|
|
39
|
-
}): Promise<BoardObject[]>;
|
|
40
|
-
select(opts: { id: string }): Promise<void>;
|
|
41
|
-
deselect(): Promise<void>;
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
cmd: {
|
|
45
|
-
board: {
|
|
46
|
-
api: {
|
|
47
|
-
export: {
|
|
48
|
-
makeVector: () => Promise<string>;
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const { token, boardId, frameNames, outputFile, exportFormat } = program
|
|
57
|
-
.requiredOption("-t, --token <token>", "Miro token")
|
|
58
|
-
.requiredOption("-b, --board-id <boardId>", "The board ID")
|
|
59
|
-
.option(
|
|
60
|
-
"-f, --frame-names <frameNames...>",
|
|
61
|
-
"The frame name(s), leave empty to export entire board"
|
|
62
|
-
)
|
|
63
|
-
.option(
|
|
64
|
-
"-o, --output-file <filename>",
|
|
65
|
-
"A file to output the SVG to (stdout if not supplied)"
|
|
66
|
-
)
|
|
67
|
-
.option("-e, --export-format <format>", "'svg' or 'json' (default: 'svg')")
|
|
68
|
-
.parse()
|
|
69
|
-
.opts();
|
|
70
|
-
|
|
71
|
-
(async () => {
|
|
72
|
-
const browser = await puppeteer.launch({ headless: true });
|
|
73
|
-
|
|
74
|
-
const page = await browser.newPage();
|
|
75
|
-
|
|
76
|
-
await page.setCookie({
|
|
77
|
-
name: "token",
|
|
78
|
-
value: token,
|
|
79
|
-
domain: "miro.com"
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
await page.setViewport({ width: 1080, height: 1024 });
|
|
83
|
-
|
|
84
|
-
await page.goto(`https://miro.com/app/board/${boardId}/`, {
|
|
85
|
-
waitUntil: "domcontentloaded"
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await page.evaluate(
|
|
89
|
-
() =>
|
|
90
|
-
new Promise<void>((resolve) => {
|
|
91
|
-
const interval = setInterval(() => {
|
|
92
|
-
try {
|
|
93
|
-
if (typeof window.miro?.board !== "undefined") {
|
|
94
|
-
resolve();
|
|
95
|
-
clearInterval(interval);
|
|
96
|
-
}
|
|
97
|
-
} catch (e) {
|
|
98
|
-
// ignored
|
|
99
|
-
}
|
|
100
|
-
}, 100);
|
|
101
|
-
})
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const getSvgForFrames = (frameNames: string[] | undefined) =>
|
|
105
|
-
page.evaluate(async (frameNames) => {
|
|
106
|
-
if (frameNames) {
|
|
107
|
-
const frames = await window.miro.board.get({ type: ["frame"] });
|
|
108
|
-
|
|
109
|
-
const selectedFrames = frames.filter((frame) =>
|
|
110
|
-
frameNames.includes(frame.title)
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (selectedFrames.length !== frameNames.length) {
|
|
114
|
-
throw Error(
|
|
115
|
-
`${
|
|
116
|
-
frameNames.length - selectedFrames.length
|
|
117
|
-
} frame(s) could not be found on the board.`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
await window.miro.board.deselect();
|
|
122
|
-
|
|
123
|
-
for (const { id } of selectedFrames) {
|
|
124
|
-
await window.miro.board.select({ id });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return await window.cmd.board.api.export.makeVector();
|
|
129
|
-
}, frameNames);
|
|
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
|
-
|
|
170
|
-
if (outputFile?.includes("{frameName}")) {
|
|
171
|
-
if (!frameNames) {
|
|
172
|
-
throw Error(
|
|
173
|
-
"Expected frame names to be given when the output file name format expects a frame name."
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
for (const frameName of frameNames) {
|
|
178
|
-
const svg = await getFn([frameName]);
|
|
179
|
-
await writeFile(outputFile.replace("{frameName}", frameName), svg);
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
const svg = await getFn(frameNames);
|
|
183
|
-
if (outputFile) {
|
|
184
|
-
await writeFile(outputFile, svg);
|
|
185
|
-
} else {
|
|
186
|
-
process.stdout.write(svg);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await page.close();
|
|
191
|
-
await browser.close();
|
|
192
|
-
})();
|