heroshot 0.14.2 → 0.16.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/README.md +3 -1
- package/dist/cli/cli.js +195 -148
- package/dist/mcp/index.js +10960 -4
- package/dist/{snippet-B6Lg_Ant.js → snippet-Fc-PkcTD.js} +15 -5
- package/editor/dist/editor.js +2226 -1891
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -30,7 +30,9 @@ npx heroshot
|
|
|
30
30
|
|
|
31
31
|
First run opens a browser with a visual editor. Pick elements, adjust padding, style borders, edit text, and add annotations (arrows, rectangles, callouts). Screenshots land in `heroshots/`, config saves to `.heroshot/config.json`. Next run regenerates everything headlessly.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="https://github.com/omachala/heroshot/blob/main/assets/video/demo-v0.14.gif?raw=true" alt="Heroshot demo" width="720">
|
|
35
|
+
</p>
|
|
34
36
|
|
|
35
37
|
## Use in Your Docs
|
|
36
38
|
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as intro, D as setVerbose, E as outro, O as spinner, S as error, T as note, _ as loadConfig, a as launchBrowser, c as generateSessionKey, d as loadSession, f as saveLocalKey, g as getConfigPath, h as ensureHeroshotDirectory, k as verbose, l as getSessionPath, m as sessionExists, n as snippetAction, o as DEFAULT_VIEWPORT, p as saveSession, r as sync, s as EDITOR_DIR, u as loadLocalKey, v as saveConfig, w as log, x as generateUid, y as VIEWPORT_PRESETS } from "../snippet-
|
|
2
|
+
import { C as intro, D as setVerbose, E as outro, O as spinner, S as error, T as note, _ as loadConfig, a as launchBrowser, c as generateSessionKey, d as loadSession, f as saveLocalKey, g as getConfigPath, h as ensureHeroshotDirectory, k as verbose, l as getSessionPath, m as sessionExists, n as snippetAction, o as DEFAULT_VIEWPORT, p as saveSession, r as sync, s as EDITOR_DIR, u as loadLocalKey, v as saveConfig, w as log, x as generateUid, y as VIEWPORT_PRESETS } from "../snippet-Fc-PkcTD.js";
|
|
3
3
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -65,6 +65,151 @@ function dispatchHighlightJob(options) {
|
|
|
65
65
|
} }));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/browser/toConfigScreenshot.ts
|
|
70
|
+
/**
|
|
71
|
+
* Convert ScreenshotData to Screenshot for config.
|
|
72
|
+
* Filename is derived from name at sync time - not stored in config.
|
|
73
|
+
*/
|
|
74
|
+
function toConfigScreenshot(data) {
|
|
75
|
+
return {
|
|
76
|
+
id: data.id,
|
|
77
|
+
name: data.name,
|
|
78
|
+
url: data.url,
|
|
79
|
+
selector: data.selector,
|
|
80
|
+
...data.padding && { padding: {
|
|
81
|
+
top: Math.round(data.padding.top),
|
|
82
|
+
right: Math.round(data.padding.right),
|
|
83
|
+
bottom: Math.round(data.padding.bottom),
|
|
84
|
+
left: Math.round(data.padding.left)
|
|
85
|
+
} },
|
|
86
|
+
...data.scroll && { scroll: data.scroll },
|
|
87
|
+
...data.paddingFill && { paddingFill: data.paddingFill },
|
|
88
|
+
...data.paddingColor && { paddingColor: data.paddingColor },
|
|
89
|
+
...data.elementFill && { elementFill: data.elementFill },
|
|
90
|
+
...data.elementColor && { elementColor: data.elementColor },
|
|
91
|
+
...data.textOverrides && Object.keys(data.textOverrides).length > 0 && { textOverrides: data.textOverrides },
|
|
92
|
+
...data.annotations && data.annotations.length > 0 && { annotations: data.annotations },
|
|
93
|
+
...data.borderWidth && { borderWidth: data.borderWidth },
|
|
94
|
+
...data.borderColor && { borderColor: data.borderColor },
|
|
95
|
+
...data.borderRadius && { borderRadius: data.borderRadius }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/browser/saveCurrentConfig.ts
|
|
101
|
+
/**
|
|
102
|
+
* Save current screenshot and browser settings state to config file.
|
|
103
|
+
*/
|
|
104
|
+
function saveCurrentConfig(configPath, allScreenshots, browserSettings, hiddenElements) {
|
|
105
|
+
const config = loadConfig(configPath);
|
|
106
|
+
config.screenshots = allScreenshots.map(toConfigScreenshot);
|
|
107
|
+
if (browserSettings) {
|
|
108
|
+
config.browser = {
|
|
109
|
+
...config.browser,
|
|
110
|
+
viewport: browserSettings.viewport,
|
|
111
|
+
...browserSettings.colorScheme && { colorScheme: browserSettings.colorScheme },
|
|
112
|
+
...browserSettings.deviceScaleFactor && { deviceScaleFactor: browserSettings.deviceScaleFactor }
|
|
113
|
+
};
|
|
114
|
+
if (browserSettings.outputDirectory) config.outputDirectory = browserSettings.outputDirectory;
|
|
115
|
+
if (browserSettings.outputFormat) config.outputFormat = browserSettings.outputFormat;
|
|
116
|
+
else delete config.outputFormat;
|
|
117
|
+
if (browserSettings.jpegQuality && browserSettings.outputFormat === "jpeg") config.jpegQuality = browserSettings.jpegQuality;
|
|
118
|
+
if (browserSettings.workers) config.workers = browserSettings.workers;
|
|
119
|
+
else delete config.workers;
|
|
120
|
+
}
|
|
121
|
+
if (hiddenElements && Object.keys(hiddenElements).length > 0) config.hiddenElements = hiddenElements;
|
|
122
|
+
else delete config.hiddenElements;
|
|
123
|
+
saveConfig(configPath, config);
|
|
124
|
+
verbose("Config saved");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/browser/handleEvent.ts
|
|
129
|
+
function createEventHandler(state, browser, context) {
|
|
130
|
+
const save = () => saveCurrentConfig(state.configPath, state.allScreenshots, state.updatedBrowserSettings, state.hiddenElements);
|
|
131
|
+
return (event) => {
|
|
132
|
+
switch (event.type) {
|
|
133
|
+
case "screenshot-added":
|
|
134
|
+
state.allScreenshots.push(event.data);
|
|
135
|
+
verbose(`Added: ${event.data.name}`);
|
|
136
|
+
save();
|
|
137
|
+
break;
|
|
138
|
+
case "screenshot-updated": {
|
|
139
|
+
const index = state.allScreenshots.findIndex(({ id }) => id === event.data.id);
|
|
140
|
+
if (index !== -1) {
|
|
141
|
+
state.allScreenshots[index] = event.data;
|
|
142
|
+
verbose(`Updated: ${event.data.name}`);
|
|
143
|
+
save();
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "screenshot-removed": {
|
|
148
|
+
const index = state.allScreenshots.findIndex(({ id }) => id === event.id);
|
|
149
|
+
if (index !== -1) {
|
|
150
|
+
const [removed] = state.allScreenshots.splice(index, 1);
|
|
151
|
+
verbose(`Removed: ${removed?.name ?? event.id}`);
|
|
152
|
+
save();
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "screenshot-selected": {
|
|
157
|
+
const [currentPage] = context.pages();
|
|
158
|
+
if (!currentPage) break;
|
|
159
|
+
state.selectedId = event.id;
|
|
160
|
+
state.sidebarExpanded = true;
|
|
161
|
+
if (currentPage.url() === event.url) {
|
|
162
|
+
state.pendingJob = {
|
|
163
|
+
type: "highlight",
|
|
164
|
+
selector: event.selector,
|
|
165
|
+
screenshotId: event.id
|
|
166
|
+
};
|
|
167
|
+
currentPage.evaluate(dispatchHighlightJob, {
|
|
168
|
+
selector: event.selector,
|
|
169
|
+
screenshotId: event.id
|
|
170
|
+
}).catch(() => {});
|
|
171
|
+
} else {
|
|
172
|
+
state.pendingJob = {
|
|
173
|
+
type: "navigate-and-highlight",
|
|
174
|
+
url: event.url,
|
|
175
|
+
selector: event.selector,
|
|
176
|
+
screenshotId: event.id
|
|
177
|
+
};
|
|
178
|
+
currentPage.goto(event.url, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "settings-updated":
|
|
183
|
+
state.updatedBrowserSettings = event.data;
|
|
184
|
+
verbose(`Settings updated: ${JSON.stringify(event.data)}`);
|
|
185
|
+
save();
|
|
186
|
+
break;
|
|
187
|
+
case "hidden-elements-updated": {
|
|
188
|
+
const { domain, selectors } = event;
|
|
189
|
+
state.hiddenElements = selectors.length === 0 ? Object.fromEntries(Object.entries(state.hiddenElements).filter(([k]) => k !== domain)) : {
|
|
190
|
+
...state.hiddenElements,
|
|
191
|
+
[domain]: selectors
|
|
192
|
+
};
|
|
193
|
+
verbose(`Hidden elements updated for ${domain}: ${selectors.length} selectors`);
|
|
194
|
+
save();
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "job-complete":
|
|
198
|
+
state.pendingJob = null;
|
|
199
|
+
break;
|
|
200
|
+
case "done":
|
|
201
|
+
(async () => {
|
|
202
|
+
try {
|
|
203
|
+
saveSession(await context.storageState(), state.sessionKey);
|
|
204
|
+
verbose("Session saved");
|
|
205
|
+
} catch {}
|
|
206
|
+
await browser.close();
|
|
207
|
+
})();
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
68
213
|
//#endregion
|
|
69
214
|
//#region src/browser/schema.ts
|
|
70
215
|
/**
|
|
@@ -118,7 +263,11 @@ const browserSettingsSchema = z.object({
|
|
|
118
263
|
height: z.number()
|
|
119
264
|
}),
|
|
120
265
|
colorScheme: z.enum(["light", "dark"]).optional(),
|
|
121
|
-
deviceScaleFactor: z.number().optional()
|
|
266
|
+
deviceScaleFactor: z.number().optional(),
|
|
267
|
+
outputDirectory: z.string().optional(),
|
|
268
|
+
outputFormat: z.enum(["png", "jpeg"]).optional(),
|
|
269
|
+
jpegQuality: z.number().int().min(1).max(100).optional(),
|
|
270
|
+
workers: z.number().int().min(1).optional()
|
|
122
271
|
});
|
|
123
272
|
/** Schema for toolbar events (discriminated union) */
|
|
124
273
|
const toolbarEventSchema = z.discriminatedUnion("type", [
|
|
@@ -144,6 +293,11 @@ const toolbarEventSchema = z.discriminatedUnion("type", [
|
|
|
144
293
|
type: z.literal("settings-updated"),
|
|
145
294
|
data: browserSettingsSchema
|
|
146
295
|
}),
|
|
296
|
+
z.object({
|
|
297
|
+
type: z.literal("hidden-elements-updated"),
|
|
298
|
+
domain: z.string(),
|
|
299
|
+
selectors: z.array(z.string())
|
|
300
|
+
}),
|
|
147
301
|
z.object({ type: z.literal("job-complete") }),
|
|
148
302
|
z.object({ type: z.literal("done") })
|
|
149
303
|
]);
|
|
@@ -202,7 +356,7 @@ async function injectToolbar(page, options) {
|
|
|
202
356
|
}
|
|
203
357
|
}
|
|
204
358
|
async function doInjectToolbar(page, options) {
|
|
205
|
-
const { screenshots, pendingJob, selectedId, sidebarExpanded, onEvent } = options;
|
|
359
|
+
const { screenshots, settings, pendingJob, selectedId, sidebarExpanded, hiddenElements, onEvent } = options;
|
|
206
360
|
if (!exposedPages.has(page)) {
|
|
207
361
|
await page.exposeFunction("__heroshotEmit", (eventJson) => {
|
|
208
362
|
onEvent(toolbarEventSchema.parse(JSON.parse(eventJson)));
|
|
@@ -214,14 +368,16 @@ async function doInjectToolbar(page, options) {
|
|
|
214
368
|
return;
|
|
215
369
|
}
|
|
216
370
|
const scriptContent = readFileSync(`${EDITOR_DIR}/dist/editor.js`, "utf8");
|
|
217
|
-
await page.evaluate(`(function(screenshots, pendingJob, selectedId, sidebarExpanded, scriptContent) {
|
|
371
|
+
await page.evaluate(`(function(screenshots, settings, pendingJob, selectedId, sidebarExpanded, hiddenElements, scriptContent) {
|
|
218
372
|
// Initialize __heroshot global namespace
|
|
219
373
|
globalThis.__heroshot = {
|
|
220
374
|
initialized: false,
|
|
221
375
|
screenshots: screenshots,
|
|
376
|
+
settings: settings,
|
|
222
377
|
pendingJob: pendingJob,
|
|
223
378
|
selectedId: selectedId,
|
|
224
379
|
sidebarExpanded: sidebarExpanded,
|
|
380
|
+
hiddenElements: hiddenElements,
|
|
225
381
|
// This emit function is why we use string evaluation.
|
|
226
382
|
// As a nested function property, it would get __name() wrapped
|
|
227
383
|
// if we used a typed function approach.
|
|
@@ -235,56 +391,7 @@ async function doInjectToolbar(page, options) {
|
|
|
235
391
|
var script = document.createElement('script');
|
|
236
392
|
script.textContent = scriptContent;
|
|
237
393
|
document.body.appendChild(script);
|
|
238
|
-
})(${JSON.stringify(screenshots)}, ${JSON.stringify(pendingJob)}, ${JSON.stringify(selectedId)}, ${JSON.stringify(sidebarExpanded)}, ${JSON.stringify(scriptContent)})`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
//#endregion
|
|
242
|
-
//#region src/browser/toConfigScreenshot.ts
|
|
243
|
-
/**
|
|
244
|
-
* Convert ScreenshotData to Screenshot for config.
|
|
245
|
-
* Filename is derived from name at sync time - not stored in config.
|
|
246
|
-
*/
|
|
247
|
-
function toConfigScreenshot(data) {
|
|
248
|
-
return {
|
|
249
|
-
id: data.id,
|
|
250
|
-
name: data.name,
|
|
251
|
-
url: data.url,
|
|
252
|
-
selector: data.selector,
|
|
253
|
-
...data.padding && { padding: {
|
|
254
|
-
top: Math.round(data.padding.top),
|
|
255
|
-
right: Math.round(data.padding.right),
|
|
256
|
-
bottom: Math.round(data.padding.bottom),
|
|
257
|
-
left: Math.round(data.padding.left)
|
|
258
|
-
} },
|
|
259
|
-
...data.scroll && { scroll: data.scroll },
|
|
260
|
-
...data.paddingFill && { paddingFill: data.paddingFill },
|
|
261
|
-
...data.paddingColor && { paddingColor: data.paddingColor },
|
|
262
|
-
...data.elementFill && { elementFill: data.elementFill },
|
|
263
|
-
...data.elementColor && { elementColor: data.elementColor },
|
|
264
|
-
...data.textOverrides && Object.keys(data.textOverrides).length > 0 && { textOverrides: data.textOverrides },
|
|
265
|
-
...data.annotations && data.annotations.length > 0 && { annotations: data.annotations },
|
|
266
|
-
...data.borderWidth && { borderWidth: data.borderWidth },
|
|
267
|
-
...data.borderColor && { borderColor: data.borderColor },
|
|
268
|
-
...data.borderRadius && { borderRadius: data.borderRadius }
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/browser/saveCurrentConfig.ts
|
|
274
|
-
/**
|
|
275
|
-
* Save current screenshot and browser settings state to config file.
|
|
276
|
-
*/
|
|
277
|
-
function saveCurrentConfig(configPath, allScreenshots, browserSettings) {
|
|
278
|
-
const config = loadConfig(configPath);
|
|
279
|
-
config.screenshots = allScreenshots.map(toConfigScreenshot);
|
|
280
|
-
if (browserSettings) config.browser = {
|
|
281
|
-
...config.browser,
|
|
282
|
-
viewport: browserSettings.viewport,
|
|
283
|
-
...browserSettings.colorScheme && { colorScheme: browserSettings.colorScheme },
|
|
284
|
-
...browserSettings.deviceScaleFactor && { deviceScaleFactor: browserSettings.deviceScaleFactor }
|
|
285
|
-
};
|
|
286
|
-
saveConfig(configPath, config);
|
|
287
|
-
verbose("Config saved");
|
|
394
|
+
})(${JSON.stringify(screenshots)}, ${JSON.stringify(settings)}, ${JSON.stringify(pendingJob)}, ${JSON.stringify(selectedId)}, ${JSON.stringify(sidebarExpanded)}, ${JSON.stringify(hiddenElements)}, ${JSON.stringify(scriptContent)})`);
|
|
288
395
|
}
|
|
289
396
|
|
|
290
397
|
//#endregion
|
|
@@ -311,12 +418,24 @@ async function setup(options = {}) {
|
|
|
311
418
|
}
|
|
312
419
|
}
|
|
313
420
|
const allScreenshots = configToScreenshotData(config.screenshots);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
421
|
+
const initialSettings = {
|
|
422
|
+
viewport,
|
|
423
|
+
...options.colorScheme && { colorScheme: options.colorScheme },
|
|
424
|
+
...config.browser?.deviceScaleFactor && { deviceScaleFactor: config.browser.deviceScaleFactor },
|
|
425
|
+
outputDirectory: config.outputDirectory,
|
|
426
|
+
outputFormat: config.outputFormat,
|
|
427
|
+
jpegQuality: config.jpegQuality,
|
|
428
|
+
workers: config.workers
|
|
429
|
+
};
|
|
430
|
+
const browserState = {
|
|
431
|
+
allScreenshots,
|
|
432
|
+
pendingJob: null,
|
|
433
|
+
selectedId: null,
|
|
434
|
+
sidebarExpanded: false,
|
|
435
|
+
updatedBrowserSettings: null,
|
|
436
|
+
hiddenElements: config.hiddenElements ?? {},
|
|
437
|
+
configPath,
|
|
438
|
+
sessionKey
|
|
320
439
|
};
|
|
321
440
|
const { browser, context } = await launchBrowser({
|
|
322
441
|
headless: false,
|
|
@@ -325,88 +444,22 @@ async function setup(options = {}) {
|
|
|
325
444
|
colorScheme: options.colorScheme
|
|
326
445
|
});
|
|
327
446
|
setupSpinner.stop("Browser ready");
|
|
328
|
-
const handleEvent = (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
allScreenshots[index] = event.data;
|
|
339
|
-
verbose(`Updated: ${event.data.name}`);
|
|
340
|
-
save();
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
case "screenshot-removed": {
|
|
345
|
-
const index = allScreenshots.findIndex(({ id }) => id === event.id);
|
|
346
|
-
if (index !== -1) {
|
|
347
|
-
const [removed] = allScreenshots.splice(index, 1);
|
|
348
|
-
verbose(`Removed: ${removed?.name ?? event.id}`);
|
|
349
|
-
save();
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
case "screenshot-selected": {
|
|
354
|
-
const [currentPage] = context.pages();
|
|
355
|
-
if (!currentPage) break;
|
|
356
|
-
selectedId = event.id;
|
|
357
|
-
sidebarExpanded = true;
|
|
358
|
-
if (currentPage.url() === event.url) {
|
|
359
|
-
pendingJob = {
|
|
360
|
-
type: "highlight",
|
|
361
|
-
selector: event.selector,
|
|
362
|
-
screenshotId: event.id
|
|
363
|
-
};
|
|
364
|
-
currentPage.evaluate(dispatchHighlightJob, {
|
|
365
|
-
selector: event.selector,
|
|
366
|
-
screenshotId: event.id
|
|
367
|
-
}).catch(() => {});
|
|
368
|
-
} else {
|
|
369
|
-
pendingJob = {
|
|
370
|
-
type: "navigate-and-highlight",
|
|
371
|
-
url: event.url,
|
|
372
|
-
selector: event.selector,
|
|
373
|
-
screenshotId: event.id
|
|
374
|
-
};
|
|
375
|
-
currentPage.goto(event.url, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
376
|
-
}
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
case "settings-updated":
|
|
380
|
-
updatedBrowserSettings = event.data;
|
|
381
|
-
verbose(`Settings updated: ${JSON.stringify(event.data)}`);
|
|
382
|
-
save();
|
|
383
|
-
break;
|
|
384
|
-
case "job-complete":
|
|
385
|
-
pendingJob = null;
|
|
386
|
-
break;
|
|
387
|
-
case "done":
|
|
388
|
-
(async () => {
|
|
389
|
-
try {
|
|
390
|
-
saveSession(await context.storageState(), sessionKey);
|
|
391
|
-
verbose("Session saved");
|
|
392
|
-
} catch {}
|
|
393
|
-
await browser.close();
|
|
394
|
-
})();
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
};
|
|
447
|
+
const handleEvent = createEventHandler(browserState, browser, context);
|
|
448
|
+
const injectOptions = () => ({
|
|
449
|
+
screenshots: browserState.allScreenshots,
|
|
450
|
+
settings: initialSettings,
|
|
451
|
+
pendingJob: browserState.pendingJob,
|
|
452
|
+
selectedId: browserState.selectedId,
|
|
453
|
+
sidebarExpanded: browserState.sidebarExpanded,
|
|
454
|
+
hiddenElements: browserState.hiddenElements,
|
|
455
|
+
onEvent: handleEvent
|
|
456
|
+
});
|
|
398
457
|
const setupPage = (page) => {
|
|
399
458
|
page.on("domcontentloaded", async () => {
|
|
400
459
|
const url = page.url();
|
|
401
460
|
if (!url.startsWith("http")) return;
|
|
402
461
|
try {
|
|
403
|
-
await injectToolbar(page,
|
|
404
|
-
screenshots: allScreenshots,
|
|
405
|
-
pendingJob,
|
|
406
|
-
selectedId,
|
|
407
|
-
sidebarExpanded,
|
|
408
|
-
onEvent: handleEvent
|
|
409
|
-
});
|
|
462
|
+
await injectToolbar(page, injectOptions());
|
|
410
463
|
verbose(`Toolbar injected on ${url}`);
|
|
411
464
|
} catch (error) {
|
|
412
465
|
verbose(`Toolbar injection failed on ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -418,22 +471,16 @@ async function setup(options = {}) {
|
|
|
418
471
|
if (context.pages().length === 0) browser.close().catch(() => {});
|
|
419
472
|
});
|
|
420
473
|
};
|
|
421
|
-
|
|
474
|
+
const initPage = (page) => {
|
|
422
475
|
setupPage(page);
|
|
423
476
|
handlePageClose(page);
|
|
424
|
-
}
|
|
477
|
+
};
|
|
478
|
+
context.on("page", initPage);
|
|
425
479
|
const page = context.pages()[0] ?? await context.newPage();
|
|
426
|
-
|
|
427
|
-
handlePageClose(page);
|
|
480
|
+
initPage(page);
|
|
428
481
|
await page.goto("https://heroshot.sh/welcome", { waitUntil: "domcontentloaded" });
|
|
429
482
|
try {
|
|
430
|
-
await injectToolbar(page,
|
|
431
|
-
screenshots: allScreenshots,
|
|
432
|
-
pendingJob,
|
|
433
|
-
selectedId,
|
|
434
|
-
sidebarExpanded,
|
|
435
|
-
onEvent: handleEvent
|
|
436
|
-
});
|
|
483
|
+
await injectToolbar(page, injectOptions());
|
|
437
484
|
verbose("Toolbar injected on welcome page");
|
|
438
485
|
} catch (error) {
|
|
439
486
|
verbose(`Initial toolbar injection failed: ${error instanceof Error ? error.message : String(error)}`);
|