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,188 @@
|
|
|
1
|
+
type TextAlignment = "left" | "center" | "right";
|
|
2
|
+
type TextVerticalAlignment = "top" | "middle" | "bottom";
|
|
3
|
+
export type BoardObjectType = "text" | "sticky_note" | "shape" | "image" | "frame" | "preview" | "card" | "app_card" | "usm" | "kanban" | "document" | "mockup" | "curve" | "webscreen" | "table" | "svg" | "emoji" | "embed" | "connector" | "unsupported" | "table_text" | "rallycard" | "stencil" | "tag" | "code" | "red" | "stamp" | "pipmatrix" | "demo_1d_layout" | "page" | "action_button" | "external_diagram" | "slide_container" | "sdk_custom_widget" | "group" | "struct_doc" | "mindmap";
|
|
4
|
+
export interface BoardItemBase {
|
|
5
|
+
id?: string;
|
|
6
|
+
type: BoardObjectType;
|
|
7
|
+
}
|
|
8
|
+
interface BoardObjectBase {
|
|
9
|
+
origin: "center";
|
|
10
|
+
linkedTo?: string;
|
|
11
|
+
connectorIds?: string[];
|
|
12
|
+
groupId?: string;
|
|
13
|
+
relativeTo: "canvas_center" | "parent_top_left" | "parent_center";
|
|
14
|
+
parentId?: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
createdBy: string;
|
|
17
|
+
modifiedAt: string;
|
|
18
|
+
modifiedBy: string;
|
|
19
|
+
}
|
|
20
|
+
interface BoardObjectWithCoordinates {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}
|
|
24
|
+
interface BoardObjectWithDimensions {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PreviewBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithDimensions {
|
|
29
|
+
type: "preview";
|
|
30
|
+
url: string;
|
|
31
|
+
}
|
|
32
|
+
export interface FrameBoardObject extends Omit<BoardItemBase, "connectorIds">, BoardObjectBase, BoardObjectWithDimensions, BoardObjectWithCoordinates {
|
|
33
|
+
type: "frame";
|
|
34
|
+
title: string;
|
|
35
|
+
childrenIds: string[];
|
|
36
|
+
showContent: boolean;
|
|
37
|
+
style: {
|
|
38
|
+
fillColor: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface GroupBoardItem extends BoardItemBase {
|
|
42
|
+
type: "group";
|
|
43
|
+
itemsIds: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface StickyNoteBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithCoordinates, BoardObjectWithDimensions {
|
|
46
|
+
type: "sticky_note";
|
|
47
|
+
tagIds: string[];
|
|
48
|
+
content: string;
|
|
49
|
+
shape: string;
|
|
50
|
+
style: {
|
|
51
|
+
fillColor: string;
|
|
52
|
+
textAlign: TextAlignment;
|
|
53
|
+
textAlignVertical: TextVerticalAlignment;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface TextBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithCoordinates, BoardObjectWithDimensions {
|
|
57
|
+
type: "text";
|
|
58
|
+
content: string;
|
|
59
|
+
rotation: number;
|
|
60
|
+
style: {
|
|
61
|
+
fillColor: string;
|
|
62
|
+
fillOpacity: number;
|
|
63
|
+
fontFamily: string;
|
|
64
|
+
fontSize: number;
|
|
65
|
+
textAlign: TextAlignment;
|
|
66
|
+
color: string;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export interface ImageBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithDimensions, BoardObjectWithCoordinates {
|
|
70
|
+
type: "image";
|
|
71
|
+
rotation: number;
|
|
72
|
+
title: string;
|
|
73
|
+
url: string;
|
|
74
|
+
alt?: string;
|
|
75
|
+
}
|
|
76
|
+
interface TableStyle {
|
|
77
|
+
borderColor?: string;
|
|
78
|
+
backgroundColor?: string;
|
|
79
|
+
backgroundOpacity?: number;
|
|
80
|
+
textColor?: string;
|
|
81
|
+
textAlign: TextAlignment;
|
|
82
|
+
textAlignVertical: TextVerticalAlignment;
|
|
83
|
+
textBold?: boolean;
|
|
84
|
+
textItalic?: boolean;
|
|
85
|
+
textUnderline?: boolean;
|
|
86
|
+
textStrike?: boolean;
|
|
87
|
+
textHighlight: null | string;
|
|
88
|
+
fontFamily?: string;
|
|
89
|
+
fontSize?: number;
|
|
90
|
+
writingMode?: "sideways" | "horizontal";
|
|
91
|
+
}
|
|
92
|
+
export interface TableBoardObject extends BoardItemBase, BoardObjectWithDimensions, BoardObjectWithCoordinates {
|
|
93
|
+
type: "image";
|
|
94
|
+
rotation: number;
|
|
95
|
+
cols?: number | {
|
|
96
|
+
width: number;
|
|
97
|
+
}[];
|
|
98
|
+
rows?: number | {
|
|
99
|
+
height: number;
|
|
100
|
+
}[];
|
|
101
|
+
cells?: {
|
|
102
|
+
text: string;
|
|
103
|
+
rowspan?: number;
|
|
104
|
+
colspan?: number;
|
|
105
|
+
style: TableStyle;
|
|
106
|
+
}[][];
|
|
107
|
+
style: TableStyle;
|
|
108
|
+
}
|
|
109
|
+
export interface StructDocBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithDimensions, BoardObjectWithCoordinates {
|
|
110
|
+
type: "struct_doc";
|
|
111
|
+
title: string;
|
|
112
|
+
content: string;
|
|
113
|
+
}
|
|
114
|
+
export interface EmbedBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithCoordinates {
|
|
115
|
+
type: "struct_doc";
|
|
116
|
+
url: string;
|
|
117
|
+
previewUrl: string;
|
|
118
|
+
mode: "inline" | "modal";
|
|
119
|
+
width?: number;
|
|
120
|
+
height?: number;
|
|
121
|
+
}
|
|
122
|
+
export interface ShapeBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithCoordinates, BoardObjectWithDimensions {
|
|
123
|
+
type: "shape";
|
|
124
|
+
content: string;
|
|
125
|
+
shape: string;
|
|
126
|
+
rotation: number;
|
|
127
|
+
style: {
|
|
128
|
+
fillColor: string;
|
|
129
|
+
fontFamily: string;
|
|
130
|
+
fontSize: number;
|
|
131
|
+
textAlign: TextAlignment;
|
|
132
|
+
textAlignVertical: TextVerticalAlignment;
|
|
133
|
+
borderStyle: "dashed" | "normal" | "dotted";
|
|
134
|
+
borderOpacity: number;
|
|
135
|
+
borderColor: string;
|
|
136
|
+
borderWidth: number;
|
|
137
|
+
fillOpacity: number;
|
|
138
|
+
color: string;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
interface CardBase extends BoardObjectBase, BoardObjectWithDimensions {
|
|
142
|
+
rotation: number;
|
|
143
|
+
title: string;
|
|
144
|
+
description: string;
|
|
145
|
+
fields: {
|
|
146
|
+
fillColor?: string;
|
|
147
|
+
textColor?: string;
|
|
148
|
+
iconUrl?: string;
|
|
149
|
+
iconShape?: "round" | "square";
|
|
150
|
+
tooltip?: string;
|
|
151
|
+
value: string;
|
|
152
|
+
}[];
|
|
153
|
+
style: {
|
|
154
|
+
cardTheme: string;
|
|
155
|
+
fillBackground: boolean;
|
|
156
|
+
};
|
|
157
|
+
/** coordinates may be null if card is part of a Kanban board */
|
|
158
|
+
x: number | null;
|
|
159
|
+
y: number | null;
|
|
160
|
+
}
|
|
161
|
+
export interface CardBoardObject extends BoardItemBase, CardBase {
|
|
162
|
+
type: "card";
|
|
163
|
+
assignee?: {
|
|
164
|
+
userId: string;
|
|
165
|
+
};
|
|
166
|
+
dueDate?: string;
|
|
167
|
+
startDate?: string;
|
|
168
|
+
taskStatus: "to-do" | "in-progress" | "done" | "none";
|
|
169
|
+
tagIds: string[];
|
|
170
|
+
}
|
|
171
|
+
export interface AppCardBoardObject extends BoardItemBase, CardBase {
|
|
172
|
+
type: "app_card";
|
|
173
|
+
owned: boolean;
|
|
174
|
+
status: "disabled" | "disconnected" | "connected";
|
|
175
|
+
}
|
|
176
|
+
export interface TagBoardItem extends BoardItemBase {
|
|
177
|
+
type: "tag";
|
|
178
|
+
title: string;
|
|
179
|
+
color: string;
|
|
180
|
+
}
|
|
181
|
+
export interface KanbanBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithCoordinates, BoardObjectWithDimensions {
|
|
182
|
+
type: "kanban";
|
|
183
|
+
}
|
|
184
|
+
export interface UnsupportedBoardObject extends BoardItemBase, BoardObjectBase, BoardObjectWithCoordinates, BoardObjectWithDimensions {
|
|
185
|
+
type: "unsupported";
|
|
186
|
+
}
|
|
187
|
+
export type BoardObject = PreviewBoardObject | FrameBoardObject | GroupBoardItem | StickyNoteBoardObject | TextBoardObject | ImageBoardObject | TableBoardObject | StructDocBoardObject | EmbedBoardObject | ShapeBoardObject | CardBoardObject | AppCardBoardObject | TagBoardItem | KanbanBoardObject | UnsupportedBoardObject;
|
|
188
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import tseslint from "typescript-eslint";
|
|
2
|
+
import promise from "eslint-plugin-promise";
|
|
3
|
+
import tsParser from "@typescript-eslint/parser";
|
|
4
|
+
import js from "@eslint/js";
|
|
5
|
+
import prettierRecommended from "eslint-plugin-prettier/recommended";
|
|
6
|
+
import stylisticTs from "@stylistic/eslint-plugin-ts";
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
js.configs.recommended,
|
|
10
|
+
...tseslint.configs.recommended,
|
|
11
|
+
promise.configs["flat/recommended"],
|
|
12
|
+
prettierRecommended,
|
|
13
|
+
{
|
|
14
|
+
ignores: ["**/build"],
|
|
15
|
+
|
|
16
|
+
plugins: {
|
|
17
|
+
"@stylistic/ts": stylisticTs
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
languageOptions: {
|
|
21
|
+
parser: tsParser,
|
|
22
|
+
ecmaVersion: 6,
|
|
23
|
+
sourceType: "module"
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
rules: {
|
|
27
|
+
"@typescript-eslint/naming-convention": "warn",
|
|
28
|
+
"@stylistic/ts/semi": "warn",
|
|
29
|
+
curly: "warn",
|
|
30
|
+
eqeqeq: "warn",
|
|
31
|
+
"no-throw-literal": "warn",
|
|
32
|
+
semi: "off"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
];
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miro-export",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"author": "jolle <npm-contact@jolle.io>",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"description": "Export Miro boards and/or frames as SVG or JSON",
|
|
7
8
|
"keywords": [
|
|
8
9
|
"miro",
|
|
@@ -18,24 +19,38 @@
|
|
|
18
19
|
},
|
|
19
20
|
"homepage": "https://github.com/jolle/miro-export",
|
|
20
21
|
"main": "./build/index.js",
|
|
21
|
-
"bin":
|
|
22
|
+
"bin": {
|
|
23
|
+
"miro-export": "./build/cli.js"
|
|
24
|
+
},
|
|
22
25
|
"devDependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"eslint": "^
|
|
26
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
27
|
+
"@eslint/js": "^9.17.0",
|
|
28
|
+
"@stylistic/eslint-plugin-ts": "^2.12.1",
|
|
29
|
+
"@types/node": "^22.10.2",
|
|
30
|
+
"ajv": "^8.17.1",
|
|
31
|
+
"eslint": "^9.17.0",
|
|
25
32
|
"eslint-config-prettier": "^9.1.0",
|
|
26
|
-
"eslint-plugin-prettier": "^5.1
|
|
27
|
-
"eslint-plugin-promise": "^
|
|
28
|
-
"prettier": "^3.2
|
|
29
|
-
"
|
|
30
|
-
"
|
|
33
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
34
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
35
|
+
"prettier": "^3.4.2",
|
|
36
|
+
"ts-json-schema-generator": "^2.3.0",
|
|
37
|
+
"tsx": "^4.19.2",
|
|
38
|
+
"typescript": "^5.7.2",
|
|
39
|
+
"typescript-eslint": "^8.18.1"
|
|
31
40
|
},
|
|
32
41
|
"dependencies": {
|
|
33
|
-
"@commander-js/extra-typings": "^12.
|
|
34
|
-
"commander": "^12.
|
|
35
|
-
"puppeteer": "^
|
|
42
|
+
"@commander-js/extra-typings": "^12.1.0",
|
|
43
|
+
"commander": "^12.1.0",
|
|
44
|
+
"puppeteer": "^23.11.1"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.0.0"
|
|
36
48
|
},
|
|
37
49
|
"scripts": {
|
|
38
|
-
"lint": "eslint
|
|
39
|
-
"build": "tsc && echo \"#!/usr/bin/env node\n$(cat ./build/
|
|
50
|
+
"lint": "eslint src/*.ts && prettier -c src/*.ts",
|
|
51
|
+
"build": "tsc && echo \"#!/usr/bin/env node\n$(cat ./build/cli.js)\" > ./build/cli.js",
|
|
52
|
+
"test": "npm run test:board-object-types && npm run test:api",
|
|
53
|
+
"test:board-object-types": "tsx tests/board-object-types.ts",
|
|
54
|
+
"test:api": "tsx tests/api.test.ts"
|
|
40
55
|
}
|
|
41
56
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { writeFile } from "fs/promises";
|
|
2
|
+
import { program } from "@commander-js/extra-typings";
|
|
3
|
+
import { MiroBoard } from "./index.js";
|
|
4
|
+
import type { FrameBoardObject } from "./miro-types.ts";
|
|
5
|
+
|
|
6
|
+
const { token, boardId, frameNames, outputFile, exportFormat } = program
|
|
7
|
+
.option("-t, --token <token>", "Miro token")
|
|
8
|
+
.requiredOption("-b, --board-id <boardId>", "The board ID")
|
|
9
|
+
.option(
|
|
10
|
+
"-f, --frame-names <frameNames...>",
|
|
11
|
+
"The frame name(s), leave empty to export entire board"
|
|
12
|
+
)
|
|
13
|
+
.option(
|
|
14
|
+
"-o, --output-file <filename>",
|
|
15
|
+
"A file to output the SVG to (stdout if not supplied)"
|
|
16
|
+
)
|
|
17
|
+
.option("-e, --export-format <format>", "'svg' or 'json' (default: 'svg')")
|
|
18
|
+
.parse()
|
|
19
|
+
.opts();
|
|
20
|
+
|
|
21
|
+
(async () => {
|
|
22
|
+
await using miroBoard = new MiroBoard({ token, boardId });
|
|
23
|
+
|
|
24
|
+
async function getFrames(frameNames: string[]) {
|
|
25
|
+
const frames = await miroBoard.getBoardObjects(
|
|
26
|
+
{ type: "frame" as const },
|
|
27
|
+
{ title: frameNames }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (frames && frames.length !== frameNames.length) {
|
|
31
|
+
throw Error(
|
|
32
|
+
`${
|
|
33
|
+
frameNames.length - frames.length
|
|
34
|
+
} frame(s) could not be found on the board.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return frames;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function getSvg(frames?: FrameBoardObject[]) {
|
|
42
|
+
return await miroBoard.getSvg(
|
|
43
|
+
frames?.map(({ id }) => id).filter((id): id is string => !!id)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function getJson(frames?: FrameBoardObject[]) {
|
|
48
|
+
if (frames) {
|
|
49
|
+
const frameChildren = await miroBoard.getBoardObjects({
|
|
50
|
+
id: frames.flatMap((frame) => frame.childrenIds)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const groupChildren = await miroBoard.getBoardObjects({
|
|
54
|
+
id: frameChildren
|
|
55
|
+
.filter((child) => child.type === "group")
|
|
56
|
+
.flatMap((child) => child.itemsIds)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return JSON.stringify([...frames, ...frameChildren, ...groupChildren]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return JSON.stringify(await miroBoard.getBoardObjects({}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const getFn = exportFormat === "json" ? getJson : getSvg;
|
|
66
|
+
|
|
67
|
+
if (outputFile?.includes("{frameName}")) {
|
|
68
|
+
if (!frameNames) {
|
|
69
|
+
throw Error(
|
|
70
|
+
"Expected frame names to be given when the output file name format expects a frame name."
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const frameName of frameNames) {
|
|
75
|
+
const output = await getFn(await getFrames([frameName]));
|
|
76
|
+
await writeFile(outputFile.replace("{frameName}", frameName), output);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
const svg = await getFn(frameNames && (await getFrames(frameNames)));
|
|
80
|
+
if (outputFile) {
|
|
81
|
+
await writeFile(outputFile, svg);
|
|
82
|
+
} else {
|
|
83
|
+
process.stdout.write(svg);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import puppeteer, { Browser, Page } from "puppeteer";
|
|
2
|
+
import type { BoardObject } from "./miro-types.ts";
|
|
3
|
+
import type { GetBoardsFilter } from "./miro-runtime.ts";
|
|
4
|
+
|
|
5
|
+
interface InitialMiroBoardOptions {
|
|
6
|
+
token?: string;
|
|
7
|
+
boardId: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type AdditionalFilter<T> = Partial<T> | Partial<{ [K in keyof T]: T[K][] }>;
|
|
11
|
+
|
|
12
|
+
type FilteredResultsByType<
|
|
13
|
+
F extends string | string[] | undefined,
|
|
14
|
+
T
|
|
15
|
+
> = F extends string ? Extract<BoardObject, { type: F }>[] : T[]; // TODO: F extends string[]
|
|
16
|
+
|
|
17
|
+
export class MiroBoard {
|
|
18
|
+
private context = Promise.withResolvers<{ browser: Browser; page: Page }>();
|
|
19
|
+
|
|
20
|
+
constructor(options: InitialMiroBoardOptions) {
|
|
21
|
+
this.initialize(options);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private async initialize(options: InitialMiroBoardOptions) {
|
|
25
|
+
const browser = await puppeteer.launch({ headless: true });
|
|
26
|
+
const page = await browser.newPage();
|
|
27
|
+
|
|
28
|
+
if (options.token) {
|
|
29
|
+
await browser.browserContexts()[0].setCookie({
|
|
30
|
+
name: "token",
|
|
31
|
+
value: options.token,
|
|
32
|
+
domain: "miro.com",
|
|
33
|
+
path: "/",
|
|
34
|
+
expires: -1,
|
|
35
|
+
sameParty: false,
|
|
36
|
+
httpOnly: false,
|
|
37
|
+
secure: false
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await page.setViewport({ width: 1080, height: 1024 });
|
|
42
|
+
|
|
43
|
+
await page.goto(`https://miro.com/app/board/${options.boardId}/`, {
|
|
44
|
+
waitUntil: "domcontentloaded"
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await page.evaluate(
|
|
48
|
+
() =>
|
|
49
|
+
new Promise<void>((resolve) => {
|
|
50
|
+
if (window.miro) {
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let miroValue: (typeof window)["miro"];
|
|
55
|
+
Object.defineProperty(window, "miro", {
|
|
56
|
+
get() {
|
|
57
|
+
return miroValue;
|
|
58
|
+
},
|
|
59
|
+
set(value) {
|
|
60
|
+
miroValue = value;
|
|
61
|
+
resolve();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
this.context.resolve({ browser, page });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async dispose() {
|
|
71
|
+
await (await this.browser).close();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async [Symbol.asyncDispose]() {
|
|
75
|
+
await this.dispose();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private get browser() {
|
|
79
|
+
return this.context.promise.then(({ browser }) => browser);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private get page() {
|
|
83
|
+
return this.context.promise.then(({ page }) => page);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getBoardObjects<F extends GetBoardsFilter>(
|
|
87
|
+
filter: F,
|
|
88
|
+
additionalFilter?: AdditionalFilter<BoardObject>
|
|
89
|
+
): Promise<FilteredResultsByType<F["type"], BoardObject>> {
|
|
90
|
+
return (await this.page).evaluate(
|
|
91
|
+
async (filter, additionalFilter) => {
|
|
92
|
+
// @ts-expect-error - https://github.com/evanw/esbuild/issues/2605#issuecomment-2050808084
|
|
93
|
+
window.__name = (func: unknown) => func;
|
|
94
|
+
|
|
95
|
+
const objectFilterMatches = (
|
|
96
|
+
filter: AdditionalFilter<Record<string, unknown>>,
|
|
97
|
+
target: object
|
|
98
|
+
) => {
|
|
99
|
+
for (const key in filter) {
|
|
100
|
+
if (!(key in target)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const targetValue = target[key as keyof typeof target];
|
|
105
|
+
if (Array.isArray(filter[key])) {
|
|
106
|
+
if (!filter[key].includes(targetValue)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
} else if (targetValue !== filter[key]) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const objects = await window.miro.board.get(filter);
|
|
118
|
+
|
|
119
|
+
const filteredObjects = additionalFilter
|
|
120
|
+
? objects.filter((object) =>
|
|
121
|
+
objectFilterMatches(additionalFilter, object)
|
|
122
|
+
)
|
|
123
|
+
: objects;
|
|
124
|
+
|
|
125
|
+
return filteredObjects as FilteredResultsByType<F["type"], BoardObject>;
|
|
126
|
+
},
|
|
127
|
+
filter,
|
|
128
|
+
additionalFilter
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getSvg(objectsIds?: string[]) {
|
|
133
|
+
return (await this.page).evaluate(async (objectsIds) => {
|
|
134
|
+
await window.miro.board.deselect();
|
|
135
|
+
|
|
136
|
+
if (objectsIds) {
|
|
137
|
+
for (const id of objectsIds) {
|
|
138
|
+
await window.miro.board.select({ id });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return await window.cmd.board.api.export.makeVector();
|
|
143
|
+
}, objectsIds);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BoardObject, BoardObjectType } from "./miro-types";
|
|
2
|
+
|
|
3
|
+
interface GetBoardsFilter {
|
|
4
|
+
type?: BoardObjectType[] | BoardObjectType;
|
|
5
|
+
id?: string[] | string;
|
|
6
|
+
tags?: string[] | string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
miro: {
|
|
12
|
+
board: {
|
|
13
|
+
get(opts: GetBoardsFilter): Promise<BoardObject[]>;
|
|
14
|
+
select(opts: { id: string }): Promise<void>;
|
|
15
|
+
deselect(): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
cmd: {
|
|
19
|
+
board: {
|
|
20
|
+
api: {
|
|
21
|
+
export: {
|
|
22
|
+
makeVector: () => Promise<string>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|