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 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 getSvgForFrames([frameName]);
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 getSvgForFrames(frameNames);
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: "frame"[];
11
- }): Promise<{ title: string; id: string }[]>;
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 getSvgForFrames([frameName]);
178
+ const svg = await getFn([frameName]);
111
179
  await writeFile(outputFile.replace("{frameName}", frameName), svg);
112
180
  }
113
181
  } else {
114
- const svg = await getSvgForFrames(frameNames);
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.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 SVGs",
6
+ "description": "Export Miro boards and/or frames as SVG or JSON",
7
7
  "keywords": [
8
8
  "miro",
9
9
  "svg",