miro-export 1.0.0 → 1.1.0
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 +80 -7
- package/build/cli.d.ts +1 -0
- package/build/cli.js +121 -0
- package/build/index.d.ts +24 -0
- package/build/index.js +89 -70
- package/build/miro-types.d.ts +188 -0
- package/build/miro-types.js +1 -0
- package/eslint.config.mjs +35 -0
- package/package.json +30 -15
- 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 -124
|
@@ -2,7 +2,7 @@ name: Build
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
5
|
+
workflow_dispatch:
|
|
6
6
|
|
|
7
7
|
jobs:
|
|
8
8
|
build:
|
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
version: 8
|
|
15
15
|
- uses: actions/setup-node@v4
|
|
16
16
|
with:
|
|
17
|
-
node-version: "
|
|
17
|
+
node-version: "22.x"
|
|
18
18
|
- name: Install dependencies
|
|
19
19
|
run: pnpm i --frozen-lockfile
|
|
20
20
|
- name: Lint
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
name: Dependabot auto-merge
|
|
2
2
|
on: pull_request
|
|
3
3
|
|
|
4
|
-
permissions:
|
|
5
|
-
pull-requests: write
|
|
6
|
-
|
|
7
4
|
jobs:
|
|
8
5
|
dependabot:
|
|
9
6
|
runs-on: ubuntu-latest
|
|
7
|
+
permissions:
|
|
8
|
+
pull-requests: write
|
|
9
|
+
contents: write
|
|
10
10
|
if: github.actor == 'dependabot[bot]'
|
|
11
11
|
steps:
|
|
12
|
-
- name: Dependabot metadata
|
|
13
|
-
id: metadata
|
|
14
|
-
uses: dependabot/fetch-metadata@v1
|
|
15
|
-
with:
|
|
16
|
-
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
17
12
|
- name: Merge a PR
|
|
18
13
|
run: gh pr merge --auto --merge "$PR_URL"
|
|
19
14
|
env:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: pnpm/action-setup@v3
|
|
13
|
+
with:
|
|
14
|
+
version: 8
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: "22.x"
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: pnpm i --frozen-lockfile
|
|
20
|
+
- name: Test
|
|
21
|
+
run: pnpm run test
|
|
22
|
+
env:
|
|
23
|
+
TEST_BOARD_ID: ${{secrets.TEST_BOARD_ID}}
|
package/README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# Miro board exporter
|
|
2
2
|
|
|
3
|
-
Exports Miro frames as full-detail SVGs using a headless Puppeteer browser.
|
|
3
|
+
Exports Miro frames as full-detail SVGs or JSON using a headless Puppeteer browser.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- [Authentication](#authentication)
|
|
6
|
+
- [CLI](#cli)
|
|
7
|
+
- [Programmatic usage](#programmatic-usage)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Authentication
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
If accessing a private board, a personal token is required. To get a token, log in to Miro using a regular web browser, and then copy the value of the "token" cookie from developer tools. This is the token that should be used. If the board can be accessed without an account using a public link, the token is optional.
|
|
12
|
+
|
|
13
|
+
## CLI
|
|
14
|
+
|
|
15
|
+
### Usage
|
|
10
16
|
|
|
11
17
|
```
|
|
12
18
|
Options:
|
|
@@ -14,10 +20,11 @@ Options:
|
|
|
14
20
|
-b, --board-id <boardId> The board ID
|
|
15
21
|
-f, --frame-names <frameNames...> The frame name(s), leave empty to export entire board
|
|
16
22
|
-o, --output-file <filename> A file to output the SVG to (stdout if not supplied)
|
|
23
|
+
-e, --export-format <format> 'svg' or 'json' (default: 'svg')
|
|
17
24
|
-h, --help display help for command
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
### Examples
|
|
21
28
|
|
|
22
29
|
```sh
|
|
23
30
|
# export "Frame 2" to the file "My Frame 2.svg"
|
|
@@ -28,8 +35,74 @@ miro-export -t XYZ -b uMoVLkx8gIc=
|
|
|
28
35
|
|
|
29
36
|
# export "Frame 2" and "Frame 3" to "Frame 2.svg" and "Frame 3.svg" respectively
|
|
30
37
|
miro-export -t XYZ -b uMoVLkx8gIc= -f "Frame 2" "Frame 3" -o "{frameName}.svg"
|
|
38
|
+
|
|
39
|
+
# export JSON representation of "Frame 2"
|
|
40
|
+
miro-export -t XYZ -b uMoVLkx8gIc= -f "Frame 2" -e json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Capturing multiple frames at once
|
|
44
|
+
|
|
45
|
+
It is possible to give 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 (and thus avoiding capturing content in between), 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).
|
|
46
|
+
|
|
47
|
+
### JSON export
|
|
48
|
+
|
|
49
|
+
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. Some of the types have been documented as TypeScript interfaces at [miro-types.ts](src/miro-types.ts). For example, a `sticky_note` object could look like this:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"type": "sticky_note",
|
|
54
|
+
"shape": "square",
|
|
55
|
+
"content": "<p>Test content</p>",
|
|
56
|
+
"style": {
|
|
57
|
+
"fillColor": "cyan",
|
|
58
|
+
"textAlign": "center",
|
|
59
|
+
"textAlignVertical": "middle"
|
|
60
|
+
},
|
|
61
|
+
"tagIds": [],
|
|
62
|
+
"id": "3458764564249021457",
|
|
63
|
+
"parentId": "3458764564247784511",
|
|
64
|
+
"origin": "center",
|
|
65
|
+
"relativeTo": "parent_top_left",
|
|
66
|
+
"createdAt": "2023-09-11T12:45:00.041Z",
|
|
67
|
+
"createdBy": "3458764537906310005",
|
|
68
|
+
"modifiedAt": "2023-09-11T12:46:01.041Z",
|
|
69
|
+
"modifiedBy": "3458764537906310005",
|
|
70
|
+
"connectorIds": [],
|
|
71
|
+
"x": 129.29101113436059,
|
|
72
|
+
"y": 201.25587788616645,
|
|
73
|
+
"width": 101.46000000000001,
|
|
74
|
+
"height": 125.12
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Programmatic usage
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { MiroBoard } from "miro-export";
|
|
82
|
+
|
|
83
|
+
await using miroBoard = new MiroBoard({
|
|
84
|
+
boardId: "uMoVLkx8gIc=", // required
|
|
85
|
+
token: "..." // optional
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// get all board objects of type frame and with title "Frame 1"
|
|
89
|
+
const framesWithTitleFrame1 = miroBoard.getBoardObjects(
|
|
90
|
+
{ type: "frame" }, // required (but empty object is OK too), limited field support
|
|
91
|
+
{ title: "Frame 1" } // optional additional filters
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// get SVG of the first frame found above
|
|
95
|
+
const svgOfFrame1 = miroBoard.getSvg([framesWithTitleFrame1[0].id]);
|
|
96
|
+
|
|
97
|
+
// if you can't use "await using" for disposal, you can also dispose manually:
|
|
98
|
+
// await miroBoard.dispose()
|
|
99
|
+
// this can also be used to close the browser at the middle of the current scope
|
|
31
100
|
```
|
|
32
101
|
|
|
33
|
-
|
|
102
|
+
> [!WARNING]
|
|
103
|
+
> Remember to dispose the instance to make sure the browser is closed and the process
|
|
104
|
+
> can exit. `await using` (as shown above) does this automatically, but is not supported
|
|
105
|
+
> in all environments and may not be the optimal choise in every case. Alternatively,
|
|
106
|
+
> `miroBoard.dispose()` may be called at any time to dispose of the instance manually.
|
|
34
107
|
|
|
35
|
-
|
|
108
|
+
Types for many of the common board object types has been provided in [miro-types.ts](src/miro-types.ts).
|
package/build/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { writeFile } from "fs/promises";
|
|
54
|
+
import { program } from "@commander-js/extra-typings";
|
|
55
|
+
import { MiroBoard } from "./index.js";
|
|
56
|
+
const { token, boardId, frameNames, outputFile, exportFormat } = program
|
|
57
|
+
.option("-t, --token <token>", "Miro token")
|
|
58
|
+
.requiredOption("-b, --board-id <boardId>", "The board ID")
|
|
59
|
+
.option("-f, --frame-names <frameNames...>", "The frame name(s), leave empty to export entire board")
|
|
60
|
+
.option("-o, --output-file <filename>", "A file to output the SVG to (stdout if not supplied)")
|
|
61
|
+
.option("-e, --export-format <format>", "'svg' or 'json' (default: 'svg')")
|
|
62
|
+
.parse()
|
|
63
|
+
.opts();
|
|
64
|
+
(async () => {
|
|
65
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
66
|
+
try {
|
|
67
|
+
const miroBoard = __addDisposableResource(env_1, new MiroBoard({ token, boardId }), true);
|
|
68
|
+
async function getFrames(frameNames) {
|
|
69
|
+
const frames = await miroBoard.getBoardObjects({ type: "frame" }, { title: frameNames });
|
|
70
|
+
if (frames && frames.length !== frameNames.length) {
|
|
71
|
+
throw Error(`${frameNames.length - frames.length} frame(s) could not be found on the board.`);
|
|
72
|
+
}
|
|
73
|
+
return frames;
|
|
74
|
+
}
|
|
75
|
+
async function getSvg(frames) {
|
|
76
|
+
return await miroBoard.getSvg(frames?.map(({ id }) => id).filter((id) => !!id));
|
|
77
|
+
}
|
|
78
|
+
async function getJson(frames) {
|
|
79
|
+
if (frames) {
|
|
80
|
+
const frameChildren = await miroBoard.getBoardObjects({
|
|
81
|
+
id: frames.flatMap((frame) => frame.childrenIds)
|
|
82
|
+
});
|
|
83
|
+
const groupChildren = await miroBoard.getBoardObjects({
|
|
84
|
+
id: frameChildren
|
|
85
|
+
.filter((child) => child.type === "group")
|
|
86
|
+
.flatMap((child) => child.itemsIds)
|
|
87
|
+
});
|
|
88
|
+
return JSON.stringify([...frames, ...frameChildren, ...groupChildren]);
|
|
89
|
+
}
|
|
90
|
+
return JSON.stringify(await miroBoard.getBoardObjects({}));
|
|
91
|
+
}
|
|
92
|
+
const getFn = exportFormat === "json" ? getJson : getSvg;
|
|
93
|
+
if (outputFile?.includes("{frameName}")) {
|
|
94
|
+
if (!frameNames) {
|
|
95
|
+
throw Error("Expected frame names to be given when the output file name format expects a frame name.");
|
|
96
|
+
}
|
|
97
|
+
for (const frameName of frameNames) {
|
|
98
|
+
const output = await getFn(await getFrames([frameName]));
|
|
99
|
+
await writeFile(outputFile.replace("{frameName}", frameName), output);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const svg = await getFn(frameNames && (await getFrames(frameNames)));
|
|
104
|
+
if (outputFile) {
|
|
105
|
+
await writeFile(outputFile, svg);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
process.stdout.write(svg);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (e_1) {
|
|
113
|
+
env_1.error = e_1;
|
|
114
|
+
env_1.hasError = true;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
const result_1 = __disposeResources(env_1);
|
|
118
|
+
if (result_1)
|
|
119
|
+
await result_1;
|
|
120
|
+
}
|
|
121
|
+
})();
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BoardObject } from "./miro-types.ts";
|
|
2
|
+
import type { GetBoardsFilter } from "./miro-runtime.ts";
|
|
3
|
+
interface InitialMiroBoardOptions {
|
|
4
|
+
token?: string;
|
|
5
|
+
boardId: string;
|
|
6
|
+
}
|
|
7
|
+
type AdditionalFilter<T> = Partial<T> | Partial<{
|
|
8
|
+
[K in keyof T]: T[K][];
|
|
9
|
+
}>;
|
|
10
|
+
type FilteredResultsByType<F extends string | string[] | undefined, T> = F extends string ? Extract<BoardObject, {
|
|
11
|
+
type: F;
|
|
12
|
+
}>[] : T[];
|
|
13
|
+
export declare class MiroBoard {
|
|
14
|
+
private context;
|
|
15
|
+
constructor(options: InitialMiroBoardOptions);
|
|
16
|
+
private initialize;
|
|
17
|
+
dispose(): Promise<void>;
|
|
18
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
19
|
+
private get browser();
|
|
20
|
+
private get page();
|
|
21
|
+
getBoardObjects<F extends GetBoardsFilter>(filter: F, additionalFilter?: AdditionalFilter<BoardObject>): Promise<FilteredResultsByType<F["type"], BoardObject>>;
|
|
22
|
+
getSvg(objectsIds?: string[]): Promise<string>;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
package/build/index.js
CHANGED
|
@@ -1,76 +1,95 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
2
|
+
import puppeteer, { Browser, Page } from "puppeteer";
|
|
3
|
+
export class MiroBoard {
|
|
4
|
+
context = Promise.withResolvers();
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.initialize(options);
|
|
7
|
+
}
|
|
8
|
+
async initialize(options) {
|
|
9
|
+
const browser = await puppeteer.launch({ headless: true });
|
|
10
|
+
const page = await browser.newPage();
|
|
11
|
+
if (options.token) {
|
|
12
|
+
await browser.browserContexts()[0].setCookie({
|
|
13
|
+
name: "token",
|
|
14
|
+
value: options.token,
|
|
15
|
+
domain: "miro.com",
|
|
16
|
+
path: "/",
|
|
17
|
+
expires: -1,
|
|
18
|
+
sameParty: false,
|
|
19
|
+
httpOnly: false,
|
|
20
|
+
secure: false
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
await page.setViewport({ width: 1080, height: 1024 });
|
|
24
|
+
await page.goto(`https://miro.com/app/board/${options.boardId}/`, {
|
|
25
|
+
waitUntil: "domcontentloaded"
|
|
26
|
+
});
|
|
27
|
+
await page.evaluate(() => new Promise((resolve) => {
|
|
28
|
+
if (window.miro) {
|
|
29
|
+
resolve();
|
|
30
|
+
}
|
|
31
|
+
let miroValue;
|
|
32
|
+
Object.defineProperty(window, "miro", {
|
|
33
|
+
get() {
|
|
34
|
+
return miroValue;
|
|
35
|
+
},
|
|
36
|
+
set(value) {
|
|
37
|
+
miroValue = value;
|
|
33
38
|
resolve();
|
|
34
|
-
clearInterval(interval);
|
|
35
39
|
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
});
|
|
41
|
+
}));
|
|
42
|
+
this.context.resolve({ browser, page });
|
|
43
|
+
}
|
|
44
|
+
async dispose() {
|
|
45
|
+
await (await this.browser).close();
|
|
46
|
+
}
|
|
47
|
+
async [Symbol.asyncDispose]() {
|
|
48
|
+
await this.dispose();
|
|
49
|
+
}
|
|
50
|
+
get browser() {
|
|
51
|
+
return this.context.promise.then(({ browser }) => browser);
|
|
52
|
+
}
|
|
53
|
+
get page() {
|
|
54
|
+
return this.context.promise.then(({ page }) => page);
|
|
55
|
+
}
|
|
56
|
+
async getBoardObjects(filter, additionalFilter) {
|
|
57
|
+
return (await this.page).evaluate(async (filter, additionalFilter) => {
|
|
58
|
+
// @ts-expect-error - https://github.com/evanw/esbuild/issues/2605#issuecomment-2050808084
|
|
59
|
+
window.__name = (func) => func;
|
|
60
|
+
const objectFilterMatches = (filter, target) => {
|
|
61
|
+
for (const key in filter) {
|
|
62
|
+
if (!(key in target)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const targetValue = target[key];
|
|
66
|
+
if (Array.isArray(filter[key])) {
|
|
67
|
+
if (!filter[key].includes(targetValue)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (targetValue !== filter[key]) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
const objects = await window.miro.board.get(filter);
|
|
78
|
+
const filteredObjects = additionalFilter
|
|
79
|
+
? objects.filter((object) => objectFilterMatches(additionalFilter, object))
|
|
80
|
+
: objects;
|
|
81
|
+
return filteredObjects;
|
|
82
|
+
}, filter, additionalFilter);
|
|
83
|
+
}
|
|
84
|
+
async getSvg(objectsIds) {
|
|
85
|
+
return (await this.page).evaluate(async (objectsIds) => {
|
|
49
86
|
await window.miro.board.deselect();
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
if (objectsIds) {
|
|
88
|
+
for (const id of objectsIds) {
|
|
89
|
+
await window.miro.board.select({ id });
|
|
90
|
+
}
|
|
52
91
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}, frameNames);
|
|
56
|
-
if (outputFile?.includes("{frameName}")) {
|
|
57
|
-
if (!frameNames) {
|
|
58
|
-
throw Error("Expected frame names to be given when the output file name format expects a frame name.");
|
|
59
|
-
}
|
|
60
|
-
for (const frameName of frameNames) {
|
|
61
|
-
const svg = await getSvgForFrames([frameName]);
|
|
62
|
-
await (0, promises_1.writeFile)(outputFile.replace("{frameName}", frameName), svg);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
const svg = await getSvgForFrames(frameNames);
|
|
67
|
-
if (outputFile) {
|
|
68
|
-
await (0, promises_1.writeFile)(outputFile, svg);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
process.stdout.write(svg);
|
|
72
|
-
}
|
|
92
|
+
return await window.cmd.board.api.export.makeVector();
|
|
93
|
+
}, objectsIds);
|
|
73
94
|
}
|
|
74
|
-
|
|
75
|
-
await browser.close();
|
|
76
|
-
})();
|
|
95
|
+
}
|