@govtechsg/oobee 0.10.69 → 0.10.72
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/DETAILS.md +0 -1
- package/README.md +13 -1
- package/S3_UPLOAD_README.md +172 -0
- package/dev/runGenerateJustHtmlReport.ts +25 -0
- package/package.json +4 -2
- package/src/combine.ts +71 -14
- package/src/constants/common.ts +89 -91
- package/src/constants/constants.ts +535 -60
- package/src/crawlers/crawlDomain.ts +313 -305
- package/src/crawlers/crawlIntelligentSitemap.ts +24 -18
- package/src/crawlers/crawlLocalFile.ts +31 -25
- package/src/crawlers/crawlSitemap.ts +264 -253
- package/src/crawlers/custom/utils.ts +809 -119
- package/src/crawlers/guards/urlGuard.ts +77 -0
- package/src/crawlers/runCustom.ts +32 -4
- package/src/generateHtmlReport.ts +224 -0
- package/src/mergeAxeResults.ts +94 -44
- package/src/runGenerateJustHtmlReport.ts +20 -0
- package/src/services/s3Uploader.ts +184 -0
- package/src/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
- package/src/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
- package/src/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
- package/src/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
- package/src/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
- package/src/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
- package/src/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
- package/src/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
- package/src/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
- package/src/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
- package/src/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
- package/src/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
- package/src/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
- package/src/static/ejs/partials/components/{topFive.ejs → topTen.ejs} +2 -2
- package/src/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
- package/src/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
- package/src/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
- package/src/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
- package/src/static/ejs/partials/footer.ejs +1 -1
- package/src/static/ejs/partials/header.ejs +7 -223
- package/src/static/ejs/partials/main.ejs +12 -23
- package/src/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
- package/src/static/ejs/partials/scripts/categorySummary.ejs +1 -1
- package/src/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
- package/src/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
- package/src/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
- package/src/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
- package/src/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
- package/src/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
- package/src/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
- package/src/static/ejs/partials/scripts/ruleModal/constants.ejs +949 -0
- package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +352 -0
- package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
- package/src/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
- package/src/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
- package/src/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
- package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +61 -57
- package/src/static/ejs/partials/scripts/topTen.ejs +61 -0
- package/src/static/ejs/partials/scripts/utils.ejs +15 -0
- package/src/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
- package/src/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
- package/src/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
- package/src/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
- package/src/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
- package/src/static/ejs/partials/styles/bootstrap.ejs +17 -1
- package/src/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
- package/src/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
- package/src/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
- package/src/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
- package/src/static/ejs/partials/styles/header.ejs +7 -0
- package/src/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
- package/src/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
- package/src/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
- package/src/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
- package/src/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
- package/src/static/ejs/partials/styles/styles.ejs +198 -470
- package/src/static/ejs/partials/styles/topTenCard.ejs +44 -0
- package/src/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
- package/src/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
- package/src/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
- package/src/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
- package/src/static/ejs/report.ejs +42 -259
- package/src/static/ejs/summary.ejs +1 -1
- package/src/utils.ts +30 -0
- package/src/static/ejs/partials/components/categorySelector.ejs +0 -4
- package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +0 -57
- package/src/static/ejs/partials/components/pagesScannedModal.ejs +0 -70
- package/src/static/ejs/partials/components/reportSearch.ejs +0 -47
- package/src/static/ejs/partials/components/ruleOffcanvas.ejs +0 -105
- package/src/static/ejs/partials/components/scanAbout.ejs +0 -328
- package/src/static/ejs/partials/components/wcagCompliance.ejs +0 -52
- package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +0 -190
- package/src/static/ejs/partials/scripts/reportSearch.ejs +0 -287
- package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +0 -804
- package/src/static/ejs/partials/scripts/scanAboutScript.ejs +0 -38
|
@@ -6,7 +6,18 @@ import path from 'path';
|
|
|
6
6
|
import { runAxeScript } from '../commonCrawlerFunc.js';
|
|
7
7
|
import { consoleLogger, guiInfoLog, silentLogger } from '../../logs.js';
|
|
8
8
|
import { guiInfoStatusTypes } from '../../constants/constants.js';
|
|
9
|
-
import { isSkippedUrl } from '../../constants/common.js';
|
|
9
|
+
import { isSkippedUrl, validateCustomFlowLabel } from '../../constants/common.js';
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface Window {
|
|
13
|
+
handleOnScanClick?: () => Promise<void> | void;
|
|
14
|
+
handleOnStopClick?: () => Promise<void> | void;
|
|
15
|
+
oobeeSetCollapsed?: (val: boolean) => void;
|
|
16
|
+
oobeeShowStopModal?: () => Promise<{ confirmed: boolean; label: string }>;
|
|
17
|
+
oobeeHideStopModal?: () => void;
|
|
18
|
+
updateMenuPos?: (pos: 'LEFT' | 'RIGHT') => void;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
//! For Cypress Test
|
|
12
23
|
// env to check if Cypress test is running
|
|
@@ -77,7 +88,6 @@ export const screenshotFullPage = async (page, screenshotsDir: string, screensho
|
|
|
77
88
|
window.scrollTo(0, 0);
|
|
78
89
|
});
|
|
79
90
|
|
|
80
|
-
consoleLogger.info(`Screenshot page at: ${page.url()}`);
|
|
81
91
|
consoleLogger.info(`Screenshot page at: ${page.url()}`);
|
|
82
92
|
|
|
83
93
|
await page.screenshot({
|
|
@@ -115,9 +125,19 @@ export const runAxeScan = async (
|
|
|
115
125
|
|
|
116
126
|
await dataset.pushData(result);
|
|
117
127
|
|
|
128
|
+
const rawTitle = result.pageTitle ?? '';
|
|
129
|
+
let pageTitleTextOnly = rawTitle; // Note: The original pageTitle contains the index and is being used in top 10 issues
|
|
130
|
+
|
|
131
|
+
if (typeof result.pageIndex === 'number') {
|
|
132
|
+
const re = new RegExp(`^\\s*${result.pageIndex}\\s*:\\s*`);
|
|
133
|
+
pageTitleTextOnly = rawTitle.replace(re, '');
|
|
134
|
+
} else {
|
|
135
|
+
pageTitleTextOnly = rawTitle.replace(/^\s*\d+\s*:\s*/, '');
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
urlsCrawled.scanned.push({
|
|
119
139
|
url: page.url(),
|
|
120
|
-
pageTitle:
|
|
140
|
+
pageTitle: pageTitleTextOnly,
|
|
121
141
|
pageImagePath: customFlowDetails.pageImagePath,
|
|
122
142
|
});
|
|
123
143
|
};
|
|
@@ -196,6 +216,10 @@ export const processPage = async (page, processPageParams) => {
|
|
|
196
216
|
urlsCrawled,
|
|
197
217
|
);
|
|
198
218
|
|
|
219
|
+
if (includeScreenshots) {
|
|
220
|
+
consoleLogger.info(`Successfully screenshot page at: ${page.url()}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
199
223
|
guiInfoLog(guiInfoStatusTypes.SCANNED, {
|
|
200
224
|
numScanned: urlsCrawled.scanned.length,
|
|
201
225
|
urlScanned: pageUrl,
|
|
@@ -210,8 +234,14 @@ export const processPage = async (page, processPageParams) => {
|
|
|
210
234
|
};
|
|
211
235
|
|
|
212
236
|
export const MENU_POSITION = {
|
|
213
|
-
|
|
214
|
-
|
|
237
|
+
left: 'LEFT',
|
|
238
|
+
right: 'RIGHT',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
type OverlayOpts = {
|
|
242
|
+
inProgress?: boolean;
|
|
243
|
+
collapsed?: boolean;
|
|
244
|
+
hideStopInput?: boolean;
|
|
215
245
|
};
|
|
216
246
|
|
|
217
247
|
export const updateMenu = async (page, urlsCrawled) => {
|
|
@@ -232,132 +262,726 @@ export const updateMenu = async (page, urlsCrawled) => {
|
|
|
232
262
|
consoleLogger.info(`Overlay menu updated`);
|
|
233
263
|
};
|
|
234
264
|
|
|
235
|
-
|
|
265
|
+
|
|
266
|
+
export const addOverlayMenu = async (
|
|
267
|
+
page,
|
|
268
|
+
urlsCrawled,
|
|
269
|
+
menuPos,
|
|
270
|
+
opts: OverlayOpts = {
|
|
271
|
+
inProgress: false,
|
|
272
|
+
collapsed: false,
|
|
273
|
+
},
|
|
274
|
+
) => {
|
|
236
275
|
await page.waitForLoadState('domcontentloaded');
|
|
237
276
|
consoleLogger.info(`Overlay menu: adding to ${menuPos}...`);
|
|
238
|
-
interface CustomWindow extends Window {
|
|
239
|
-
updateMenuPos: (newPos: any) => void;
|
|
240
|
-
handleOnScanClick: () => void;
|
|
241
|
-
}
|
|
242
277
|
|
|
243
278
|
// Add the overlay menu with initial styling
|
|
244
279
|
return page
|
|
245
280
|
.evaluate(
|
|
246
281
|
async vars => {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
282
|
+
const customWindow: Window = window as unknown as Window;
|
|
283
|
+
const inProgress = !!(vars?.opts && vars.opts.inProgress);
|
|
284
|
+
const collapsedOption = !!(vars?.opts && vars.opts.collapsed);
|
|
285
|
+
|
|
286
|
+
const panel = document.createElement('aside');
|
|
287
|
+
panel.className = 'oobee-panel';
|
|
288
|
+
|
|
289
|
+
const minBtn = document.createElement('button');
|
|
290
|
+
minBtn.type = 'button';
|
|
291
|
+
minBtn.className = 'oobee-minbtn';
|
|
292
|
+
minBtn.setAttribute('aria-label', 'Minimize/expand panel');
|
|
293
|
+
|
|
294
|
+
const MINBTN_SVG = `
|
|
295
|
+
<svg class="oobee-minbtn__icon" xmlns="http://www.w3.org/2000/svg"
|
|
296
|
+
width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
|
|
297
|
+
<g clip-path="url(#clip0_59_3691)">
|
|
298
|
+
<path d="M6.41 6L5 7.41L9.58 12L5 16.59L6.41 18L12.41 12L6.41 6Z" fill="#9021A6"/>
|
|
299
|
+
<path d="M14.41 6L13 7.41L17.58 12L13 16.59L14.41 18L20.41 12L14.41 6Z" fill="#9021A6"/>
|
|
300
|
+
</g>
|
|
301
|
+
<defs>
|
|
302
|
+
<clipPath id="clip0_59_3691">
|
|
303
|
+
<rect width="24" height="24" fill="white"/>
|
|
304
|
+
</clipPath>
|
|
305
|
+
</defs>
|
|
306
|
+
</svg>
|
|
307
|
+
`;
|
|
308
|
+
minBtn.innerHTML = MINBTN_SVG;
|
|
309
|
+
|
|
310
|
+
let currentPos: 'LEFT' | 'RIGHT' = (vars.menuPos || 'RIGHT');
|
|
311
|
+
const isCollapsed = () => panel.classList.contains('collapsed');
|
|
312
|
+
|
|
313
|
+
const setPosClass = (pos: 'LEFT' | 'RIGHT') => {
|
|
314
|
+
panel.classList.remove('pos-left', 'pos-right');
|
|
315
|
+
minBtn.classList.remove('pos-left', 'pos-right');
|
|
316
|
+
if (pos === 'LEFT') {
|
|
317
|
+
panel.classList.add('pos-left');
|
|
318
|
+
minBtn.classList.add('pos-left');
|
|
319
|
+
} else {
|
|
320
|
+
panel.classList.add('pos-right');
|
|
321
|
+
minBtn.classList.add('pos-right');
|
|
266
322
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
323
|
+
positionMinimizeBtn();
|
|
324
|
+
setDraggableSidebarMenu();
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const toggleCollapsed = (force?: boolean) => {
|
|
328
|
+
const willCollapse = (typeof force === 'boolean') ? force : !isCollapsed();
|
|
329
|
+
if (willCollapse) {
|
|
330
|
+
panel.classList.add('collapsed');
|
|
331
|
+
localStorage.setItem('oobee:overlay-collapsed', '1');
|
|
332
|
+
customWindow.oobeeSetCollapsed?.(true);
|
|
333
|
+
} else {
|
|
334
|
+
panel.classList.remove('collapsed');
|
|
335
|
+
localStorage.setItem('oobee:overlay-collapsed', '0');
|
|
336
|
+
customWindow.oobeeSetCollapsed?.(false);
|
|
274
337
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
338
|
+
positionMinimizeBtn();
|
|
339
|
+
setDraggableSidebarMenu();
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
setPosClass(currentPos);
|
|
343
|
+
const persisted = localStorage.getItem('oobee:overlay-collapsed');
|
|
344
|
+
const startCollapsed = persisted != null ? persisted === '1' : collapsedOption;
|
|
345
|
+
if (startCollapsed) panel.classList.add('collapsed');
|
|
346
|
+
|
|
347
|
+
const header = document.createElement('div');
|
|
348
|
+
header.className = 'oobee-header';
|
|
349
|
+
|
|
350
|
+
const grip = document.createElement('button');
|
|
351
|
+
grip.type = 'button';
|
|
352
|
+
grip.className = 'oobee-grip';
|
|
353
|
+
grip.setAttribute('aria-label', 'Drag to move panel left or right');
|
|
354
|
+
|
|
355
|
+
const GRIP_SVG = `
|
|
356
|
+
<svg class="oobee-grip__icon" xmlns="http://www.w3.org/2000/svg"
|
|
357
|
+
width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
|
|
358
|
+
<path d="M6 11C4.9 11 4 10.1 4 9C4 7.9 4.9 7 6 7C7.1 7 8 7.9 8 9C8 10.1 7.1 11 6 11ZM14 9C14 7.9 13.1 7 12 7C10.9 7 10 7.9 10 9C10 10.1 10.9 11 12 11C13.1 11 14 10.1 14 9ZM20 9C20 7.9 19.1 7 18 7C16.9 7 16 7.9 16 9C16 10.1 16.9 11 18 11C19.1 11 20 10.1 20 9ZM16 15C16 16.1 16.9 17 18 17C19.1 17 20 16.1 20 15C20 13.9 19.1 13 18 13C16.9 13 16 13.9 16 15ZM14 15C14 13.9 13.1 13 12 13C10.9 13 10 13.9 10 15C10 16.1 10.9 17 12 17C13.1 17 14 16.1 14 15ZM8 15C8 13.9 7.1 13 6 13C4.9 13 4 13.9 4 15C4 16.1 4.9 17 6 17C7.1 17 8 16.1 8 15Z" fill="#AFAFB0"/>
|
|
359
|
+
</svg>
|
|
360
|
+
`;
|
|
361
|
+
grip.innerHTML = GRIP_SVG;
|
|
362
|
+
|
|
363
|
+
const leftSpacer = document.createElement('div');
|
|
364
|
+
leftSpacer.className = 'oobee-spacer';
|
|
365
|
+
const rightSpacer = document.createElement('div');
|
|
366
|
+
rightSpacer.className = 'oobee-spacer';
|
|
367
|
+
|
|
368
|
+
header.appendChild(leftSpacer);
|
|
369
|
+
header.appendChild(grip);
|
|
370
|
+
header.appendChild(rightSpacer);
|
|
371
|
+
|
|
372
|
+
const body = document.createElement('div');
|
|
373
|
+
body.className = 'oobee-body';
|
|
374
|
+
|
|
375
|
+
const h2 = document.createElement('h2');
|
|
376
|
+
h2.id = 'oobeeHPagesScanned';
|
|
377
|
+
h2.className = 'oobee-section-title';
|
|
378
|
+
h2.textContent = 'Pages Scanned';
|
|
379
|
+
|
|
380
|
+
const scanBtn = document.createElement('button');
|
|
381
|
+
scanBtn.id = 'oobeeBtnScan';
|
|
382
|
+
scanBtn.className = 'oobee-btn oobee-btn-primary';
|
|
383
|
+
scanBtn.innerText = 'Scan this page';
|
|
384
|
+
scanBtn.disabled = inProgress;
|
|
385
|
+
scanBtn.addEventListener('click', async () => customWindow.handleOnScanClick?.());
|
|
386
|
+
|
|
387
|
+
const stopBtn = document.createElement('button');
|
|
388
|
+
stopBtn.id = 'oobeeBtnStop';
|
|
389
|
+
stopBtn.className = 'oobee-btn oobee-btn-secondary';
|
|
390
|
+
stopBtn.innerText = 'Stop scan';
|
|
391
|
+
stopBtn.addEventListener('click', async () => customWindow.handleOnStopClick?.());
|
|
392
|
+
|
|
393
|
+
const btnGroup = document.createElement('div');
|
|
394
|
+
btnGroup.className = 'oobee-actions';
|
|
395
|
+
btnGroup.appendChild(scanBtn);
|
|
396
|
+
btnGroup.appendChild(stopBtn);
|
|
397
|
+
|
|
398
|
+
const listWrap = document.createElement('div');
|
|
399
|
+
listWrap.id = 'oobeeList';
|
|
400
|
+
listWrap.className = 'oobee-list';
|
|
401
|
+
|
|
402
|
+
const renderList = () => {
|
|
403
|
+
const scanned = vars.urlsCrawled.scanned || [];
|
|
404
|
+
listWrap.innerHTML = '';
|
|
405
|
+
|
|
406
|
+
if (scanned.length === 0) {
|
|
407
|
+
const empty = document.createElement('div');
|
|
408
|
+
empty.className = 'oobee-empty';
|
|
409
|
+
empty.textContent = 'Scan a page to start';
|
|
410
|
+
listWrap.appendChild(empty);
|
|
411
|
+
return;
|
|
295
412
|
}
|
|
296
|
-
});
|
|
297
413
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
p.innerText = `Pages Scanned: ${vars.urlsCrawled.scanned.length || 0}`;
|
|
414
|
+
const ol = document.createElement('ol');
|
|
415
|
+
ol.className = 'oobee-ol';
|
|
301
416
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
417
|
+
scanned.forEach((item) => {
|
|
418
|
+
const li = document.createElement('li');
|
|
419
|
+
li.className = 'oobee-li';
|
|
420
|
+
|
|
421
|
+
const title = document.createElement('div');
|
|
422
|
+
title.className = 'oobee-item-title';
|
|
423
|
+
title.textContent = (item.pageTitle && item.pageTitle.trim()) ? item.pageTitle : item.url;
|
|
424
|
+
|
|
425
|
+
const url = document.createElement('div');
|
|
426
|
+
url.className = 'oobee-item-url';
|
|
427
|
+
url.textContent = item.url;
|
|
428
|
+
|
|
429
|
+
li.appendChild(title);
|
|
430
|
+
li.appendChild(url);
|
|
431
|
+
ol.appendChild(li);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
listWrap.appendChild(ol);
|
|
435
|
+
};
|
|
436
|
+
renderList();
|
|
307
437
|
|
|
308
|
-
|
|
309
|
-
|
|
438
|
+
body.appendChild(btnGroup);
|
|
439
|
+
body.appendChild(h2);
|
|
440
|
+
body.appendChild(listWrap);
|
|
441
|
+
|
|
442
|
+
panel.appendChild(header);
|
|
443
|
+
panel.appendChild(body);
|
|
310
444
|
|
|
311
445
|
const sheet = new CSSStyleSheet();
|
|
312
446
|
// TODO: separate out into css file if this gets too big
|
|
313
447
|
sheet.replaceSync(`
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
448
|
+
.oobee-panel{
|
|
449
|
+
position: fixed;
|
|
450
|
+
top: 0;
|
|
451
|
+
height: 100vh;
|
|
452
|
+
width: 320px;
|
|
453
|
+
box-sizing: border-box;
|
|
454
|
+
background: #fff;
|
|
455
|
+
color: #111;
|
|
456
|
+
z-index: 2147483647;
|
|
457
|
+
display: flex;
|
|
458
|
+
flex-direction: column;
|
|
459
|
+
border: 1px solid rgba(0,0,0,.08);border-left: none;border-right: none;
|
|
460
|
+
box-shadow: 0 6px 24px rgba(0,0,0,.08);
|
|
461
|
+
transition: width .16s ease,left .16s ease,right .16s ease
|
|
462
|
+
}
|
|
463
|
+
.oobee-panel.pos-right {
|
|
464
|
+
right: 0;
|
|
465
|
+
border-left: 1px solid rgba(0,0,0,.08)
|
|
466
|
+
}
|
|
467
|
+
.oobee-panel.pos-left {
|
|
468
|
+
left: 0;
|
|
469
|
+
border-right: 1px solid rgba(0,0,0,.08)
|
|
470
|
+
}
|
|
471
|
+
.oobee-panel.collapsed {
|
|
472
|
+
width: 56px;
|
|
473
|
+
overflow: hidden
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
:host {
|
|
477
|
+
--oobee-gap: 8px; /* distance from panel edge */
|
|
478
|
+
--oobee-panel-offset: 320px; /* overwritten by JS to actual width */
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/* external minimize button (always OUTSIDE the panel) */
|
|
482
|
+
.oobee-minbtn {
|
|
483
|
+
position: fixed;
|
|
484
|
+
top: 0;
|
|
485
|
+
z-index: 2147483647;
|
|
486
|
+
width: 32px;
|
|
487
|
+
height: 32px;
|
|
488
|
+
border: none;
|
|
489
|
+
background: #fff;
|
|
490
|
+
cursor: pointer;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* right-docked: button sits to the LEFT of the panel */
|
|
494
|
+
.oobee-minbtn.pos-right{
|
|
495
|
+
right: calc(var(--oobee-panel-offset) + var(--oobee-gap));
|
|
496
|
+
}
|
|
497
|
+
/* left-docked: button sits to the RIGHT of the panel */
|
|
498
|
+
.oobee-minbtn.pos-left{
|
|
499
|
+
left: calc(var(--oobee-panel-offset) + var(--oobee-gap));
|
|
500
|
+
}
|
|
501
|
+
.oobee-minbtn:hover {
|
|
502
|
+
box-shadow:0 4px 12px rgba(0,0,0,.12);
|
|
503
|
+
}
|
|
504
|
+
.oobee-minbtn:active {
|
|
505
|
+
transform:translateY(1px);
|
|
506
|
+
}
|
|
507
|
+
.oobee-minbtn:focus-visible {
|
|
508
|
+
outline: 2px solid #7b4dff;
|
|
509
|
+
outline-offset: 2px;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.oobee-header {
|
|
513
|
+
position: relative;
|
|
514
|
+
display: flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
justify-content: space-between;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.oobee-spacer {
|
|
520
|
+
width:28px;
|
|
521
|
+
height:28px;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.oobee-grip{
|
|
525
|
+
border: 0;
|
|
526
|
+
background: #FFFFFF;
|
|
527
|
+
cursor: grab;
|
|
528
|
+
margin-top: 0.4rem;
|
|
529
|
+
}
|
|
530
|
+
.oobee-grip:active {
|
|
531
|
+
cursor:grabbing;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.oobee-body {
|
|
535
|
+
display: flex;
|
|
536
|
+
flex-direction: column;
|
|
537
|
+
flex: 1;
|
|
538
|
+
min-height: 0;
|
|
539
|
+
overflow: hidden;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.oobee-actions {
|
|
543
|
+
display: flex;
|
|
544
|
+
flex-direction: column;
|
|
545
|
+
gap: 12px;
|
|
546
|
+
padding: 1rem;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* Base button */
|
|
550
|
+
.oobee-btn {
|
|
551
|
+
width: 100%;
|
|
552
|
+
min-height: 44px;
|
|
553
|
+
border-radius: 999px;
|
|
554
|
+
padding: 12px 16px;
|
|
555
|
+
font-size: 16px;
|
|
556
|
+
line-height: 1.2;
|
|
557
|
+
font-weight: 400;
|
|
558
|
+
cursor: pointer;
|
|
559
|
+
transition: {
|
|
560
|
+
box-shadow .12s ease,
|
|
561
|
+
transform .02s ease,
|
|
562
|
+
background-color .12s ease,
|
|
563
|
+
color .12s ease,
|
|
564
|
+
border-color .12s ease;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
.oobee-btn:disabled {
|
|
568
|
+
opacity:.6;
|
|
569
|
+
cursor:not-allowed
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/* Primary (filled) */
|
|
573
|
+
.oobee-btn-primary {
|
|
574
|
+
background: #9021a6;
|
|
575
|
+
color: #fff;
|
|
576
|
+
border: 1px solid transparent;
|
|
577
|
+
}
|
|
578
|
+
.oobee-btn-primary:hover:not(:disabled) {
|
|
579
|
+
box-shadow:0 2px 10px rgba(0,0,0,.12);
|
|
580
|
+
}
|
|
581
|
+
.oobee-btn-primary:active:not(:disabled) {
|
|
582
|
+
transform:translateY(1px);
|
|
583
|
+
}
|
|
584
|
+
.oobee-btn-primary:focus-visible {
|
|
585
|
+
outline:2px solid #7b4dff;
|
|
586
|
+
outline-offset:2px;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/* Stop button */
|
|
590
|
+
.oobee-btn-secondary{
|
|
591
|
+
background: #fff;
|
|
592
|
+
color: #9021A6;
|
|
593
|
+
border: 1px solid #9021A6;
|
|
594
|
+
}
|
|
595
|
+
.oobee-btn-secondary:active:not(:disabled) {
|
|
596
|
+
transform:translateY(1px);
|
|
597
|
+
}
|
|
598
|
+
.oobee-btn-secondary:focus-visible{
|
|
599
|
+
outline: 2px solid #7b4dff;
|
|
600
|
+
outline-offset:2px;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* Text for empty scans */
|
|
604
|
+
.oobee-empty{
|
|
605
|
+
display: flex;
|
|
606
|
+
justify-content: center;
|
|
607
|
+
align-items: center;
|
|
608
|
+
height: 100%;
|
|
609
|
+
font-size: 14px;
|
|
610
|
+
color: #555555;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.oobee-list {
|
|
614
|
+
flex: 1;
|
|
615
|
+
min-height: 0;
|
|
616
|
+
overflow-y: auto;
|
|
617
|
+
padding-left: 1rem;
|
|
618
|
+
padding-right: 1rem;
|
|
619
|
+
padding-bottom: 1rem;
|
|
620
|
+
padding-top: 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.oobee-panel.collapsed .oobee-list {
|
|
624
|
+
display: none;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
#oobeeStopOverlay[hidden] {
|
|
628
|
+
display:none !important;
|
|
629
|
+
}
|
|
630
|
+
#oobeeStopOverlay {
|
|
631
|
+
display:grid;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.oobee-section-title {
|
|
635
|
+
font-size: 16px;
|
|
636
|
+
font-weight: 700;
|
|
637
|
+
color: #161616;
|
|
638
|
+
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
|
639
|
+
padding: 1rem;
|
|
640
|
+
margin: 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.oobee-panel.collapsed .oobee-section-title {
|
|
644
|
+
display: none;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.oobee-ol {
|
|
648
|
+
margin: 0;
|
|
649
|
+
padding-left: 1.25rem;
|
|
650
|
+
display: flex;
|
|
651
|
+
flex-direction: column;
|
|
652
|
+
gap: 10px;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.oobee-li {
|
|
656
|
+
list-style: decimal;
|
|
657
|
+
font-size: 14px;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.oobee-item-title {
|
|
661
|
+
font-size: 14px;
|
|
662
|
+
color: #161616;
|
|
663
|
+
white-space: nowrap;
|
|
664
|
+
overflow: hidden;
|
|
665
|
+
text-overflow: ellipsis;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.oobee-item-url {
|
|
669
|
+
font-size: 12px;
|
|
670
|
+
color: #6b7280;
|
|
671
|
+
white-space: nowrap;
|
|
672
|
+
overflow: hidden;
|
|
673
|
+
text-overflow: ellipsis;
|
|
674
|
+
direction: rtl;
|
|
675
|
+
text-align: left;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.oobee-minbtn__icon {
|
|
679
|
+
transition: transform .18s ease;
|
|
680
|
+
transform: rotate(0deg);
|
|
681
|
+
}
|
|
682
|
+
.oobee-minbtn__icon.is-left {
|
|
683
|
+
transform: rotate(180deg);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
:host-context(.oobee-snap) .oobee-panel,
|
|
687
|
+
:host-context(.oobee-snap) .oobee-minbtn { display:none !important; }
|
|
688
|
+
|
|
689
|
+
@media (max-width:1024px) {
|
|
690
|
+
.oobee-panel {
|
|
691
|
+
width:280px
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
@media (max-width:768px) {
|
|
695
|
+
.oobee-panel {
|
|
696
|
+
width: 92vw;
|
|
697
|
+
height: 100vh;
|
|
698
|
+
top: 0;
|
|
699
|
+
bottom: 0;
|
|
700
|
+
border-radius: 0;
|
|
701
|
+
}
|
|
702
|
+
.oobee-panel.collapsed {
|
|
703
|
+
width: auto;
|
|
704
|
+
height: auto;
|
|
705
|
+
padding: 0;
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
justify-content: center;
|
|
709
|
+
border-radius: 999px;
|
|
710
|
+
box-shadow: 0 6px 24px rgba(0,0,0,.4);
|
|
711
|
+
top: auto;
|
|
712
|
+
bottom: max(16px, env(safe-area-inset-bottom,0px))
|
|
713
|
+
}
|
|
714
|
+
}
|
|
336
715
|
`);
|
|
337
716
|
|
|
338
|
-
|
|
717
|
+
document.documentElement.classList.remove('oobee-snap');
|
|
339
718
|
const shadowHost = document.createElement('div');
|
|
340
|
-
shadowHost.id = '
|
|
719
|
+
shadowHost.id = 'oobeeShadowHost';
|
|
341
720
|
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
|
|
342
721
|
|
|
343
722
|
shadowRoot.adoptedStyleSheets = [sheet];
|
|
344
723
|
|
|
345
|
-
shadowRoot.appendChild(
|
|
724
|
+
shadowRoot.appendChild(panel);
|
|
725
|
+
shadowRoot.appendChild(minBtn);
|
|
346
726
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
727
|
+
function setDraggableSidebarMenu() {
|
|
728
|
+
const icon = minBtn.querySelector<SVGElement>('.oobee-minbtn__icon');
|
|
729
|
+
if (!icon) return;
|
|
730
|
+
|
|
731
|
+
const closed = isCollapsed();
|
|
732
|
+
const arrowPointsRight =
|
|
733
|
+
(currentPos === 'RIGHT' && !closed) ||
|
|
734
|
+
(currentPos === 'LEFT' && closed);
|
|
735
|
+
|
|
736
|
+
icon.classList.toggle('is-left', !arrowPointsRight);
|
|
737
|
+
minBtn.setAttribute('aria-label', closed ? 'Expand panel' : 'Collapse panel');
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function positionMinimizeBtn() {
|
|
741
|
+
const OPEN_OFFSET = 318;
|
|
742
|
+
const COLLAPSED_OFFSET = 55;
|
|
743
|
+
const offset = isCollapsed() ? COLLAPSED_OFFSET : OPEN_OFFSET;
|
|
744
|
+
|
|
745
|
+
minBtn.style.left = '';
|
|
746
|
+
minBtn.style.right = '';
|
|
747
|
+
|
|
748
|
+
if (currentPos === 'RIGHT') {
|
|
749
|
+
minBtn.style.right = `${offset}px`;
|
|
357
750
|
} else {
|
|
358
|
-
|
|
359
|
-
|
|
751
|
+
minBtn.style.left = `${offset}px`;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
positionMinimizeBtn();
|
|
755
|
+
setDraggableSidebarMenu();
|
|
756
|
+
|
|
757
|
+
minBtn.addEventListener('click', () => toggleCollapsed());
|
|
758
|
+
|
|
759
|
+
let startX = 0;
|
|
760
|
+
const THRESH = 40;
|
|
761
|
+
|
|
762
|
+
grip.addEventListener('pointerdown', (e: PointerEvent) => {
|
|
763
|
+
startX = e.clientX;
|
|
764
|
+
grip.setPointerCapture(e.pointerId); // <-- use the button
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
grip.addEventListener('pointermove', (e: PointerEvent) => {
|
|
768
|
+
if (!grip.hasPointerCapture?.(e.pointerId)) return; // <-- check the button
|
|
769
|
+
const dx = e.clientX - startX;
|
|
770
|
+
if (Math.abs(dx) >= THRESH) {
|
|
771
|
+
const nextPos: 'LEFT' | 'RIGHT' = dx < 0 ? 'LEFT' : 'RIGHT';
|
|
772
|
+
if (nextPos !== currentPos) {
|
|
773
|
+
currentPos = nextPos;
|
|
774
|
+
setPosClass(currentPos);
|
|
775
|
+
window.updateMenuPos?.(currentPos);
|
|
776
|
+
}
|
|
777
|
+
startX = e.clientX;
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
grip.addEventListener('pointerup', (e: PointerEvent) => {
|
|
782
|
+
try { grip.releasePointerCapture(e.pointerId); } catch {}
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
const stopDialog = document.createElement('dialog');
|
|
786
|
+
stopDialog.id = 'oobeeStopDialog';
|
|
787
|
+
Object.assign(stopDialog.style, {
|
|
788
|
+
width: 'min(560px, calc(100vw - 32px))',
|
|
789
|
+
border: 'none',
|
|
790
|
+
padding: '0',
|
|
791
|
+
borderRadius: '16px',
|
|
792
|
+
overflow: 'hidden',
|
|
793
|
+
boxShadow: '0 10px 40px rgba(0,0,0,.35)',
|
|
794
|
+
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif'
|
|
795
|
+
});
|
|
796
|
+
const dialogSheet = new CSSStyleSheet();
|
|
797
|
+
dialogSheet.replaceSync(`
|
|
798
|
+
#oobeeStopDialog::backdrop {
|
|
799
|
+
background: rgba(0,0,0,.55);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/* primary button hover/focus */
|
|
803
|
+
.oobee-stop-primary:hover {
|
|
804
|
+
filter: brightness(0.95);
|
|
805
|
+
}
|
|
806
|
+
.oobee-stop-primary:focus-visible {
|
|
807
|
+
outline: 2px solid #7b4dff; outline-offset: 2px;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/* cancel link hover */
|
|
811
|
+
.oobee-stop-cancel {
|
|
812
|
+
color: #9021A6;
|
|
813
|
+
text-decoration: underline;
|
|
814
|
+
}
|
|
815
|
+
.oobee-stop-cancel:hover {
|
|
816
|
+
filter: brightness(0.95);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/* close “X” hover ring */
|
|
820
|
+
.oobee-stop-close:hover {
|
|
821
|
+
background: #f3f4f6;
|
|
822
|
+
}
|
|
823
|
+
`);
|
|
824
|
+
shadowRoot.adoptedStyleSheets = [sheet, dialogSheet];
|
|
825
|
+
|
|
826
|
+
const head = document.createElement('div');
|
|
827
|
+
Object.assign(head.style, {
|
|
828
|
+
padding: '20px 20px 8px 20px',
|
|
829
|
+
display: 'flex',
|
|
830
|
+
alignItems: 'center',
|
|
831
|
+
justifyContent: 'space-between',
|
|
832
|
+
gap: '8px'
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
const title = document.createElement('h2');
|
|
836
|
+
title.id = 'oobee-stop-title';
|
|
837
|
+
title.textContent = 'Are you sure you want to stop this scan?';
|
|
838
|
+
Object.assign(title.style, { margin: '0', fontSize: '22px', fontWeight: '700', lineHeight: '1.25' });
|
|
839
|
+
|
|
840
|
+
const closeX = document.createElement('button');
|
|
841
|
+
closeX.type = 'button';
|
|
842
|
+
closeX.setAttribute('aria-label', 'Close');
|
|
843
|
+
closeX.textContent = '×';
|
|
844
|
+
closeX.className = 'oobee-stop-close';
|
|
845
|
+
Object.assign(closeX.style, {
|
|
846
|
+
border: 'none',
|
|
847
|
+
background: 'transparent',
|
|
848
|
+
fontSize: '28px',
|
|
849
|
+
lineHeight: '1',
|
|
850
|
+
cursor: 'pointer',
|
|
851
|
+
color: '#4b5563',
|
|
852
|
+
width: '36px',
|
|
853
|
+
height: '36px',
|
|
854
|
+
borderRadius: '12px',
|
|
855
|
+
display: 'grid',
|
|
856
|
+
placeItems: 'center'
|
|
857
|
+
});
|
|
858
|
+
head.appendChild(title);
|
|
859
|
+
head.appendChild(closeX);
|
|
860
|
+
|
|
861
|
+
const bodyWrap = document.createElement('div');
|
|
862
|
+
Object.assign(bodyWrap.style, {
|
|
863
|
+
padding: '12px 20px 20px 20px'
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
const form = document.createElement('form');
|
|
867
|
+
form.noValidate = true;
|
|
868
|
+
form.autocomplete = 'off';
|
|
869
|
+
Object.assign(form.style, {
|
|
870
|
+
display: 'grid',
|
|
871
|
+
gridTemplateColumns: '1fr',
|
|
872
|
+
rowGap: '12px'
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
const label = document.createElement('label');
|
|
876
|
+
label.setAttribute('for', 'oobee-stop-input');
|
|
877
|
+
label.textContent = 'Enter a name for this scan';
|
|
878
|
+
Object.assign(label.style, { fontSize: '15px', fontWeight: '600' });
|
|
879
|
+
|
|
880
|
+
const input = document.createElement('input');
|
|
881
|
+
input.id = 'oobeeStopInput';
|
|
882
|
+
input.type = 'text';
|
|
883
|
+
Object.assign(input.style, {
|
|
884
|
+
width: '100%',
|
|
885
|
+
borderRadius: '5px',
|
|
886
|
+
border: '1px solid #e5e7eb',
|
|
887
|
+
padding: '12px 14px',
|
|
888
|
+
fontSize: '14px',
|
|
889
|
+
outline: 'none',
|
|
890
|
+
boxSizing: 'border-box'
|
|
891
|
+
});
|
|
892
|
+
input.addEventListener('focus', () => {
|
|
893
|
+
input.style.borderColor = '#7b4dff';
|
|
894
|
+
input.style.boxShadow = '0 0 0 3px rgba(123,77,255,.25)';
|
|
895
|
+
});
|
|
896
|
+
input.addEventListener('blur', () => {
|
|
897
|
+
input.style.borderColor = '#e5e7eb';
|
|
898
|
+
input.style.boxShadow = 'none';
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
const actions = document.createElement('div');
|
|
902
|
+
Object.assign(actions.style, { display: 'grid', gap: '12px', marginTop: '4px' });
|
|
903
|
+
|
|
904
|
+
const primary = document.createElement('button');
|
|
905
|
+
primary.type = 'submit';
|
|
906
|
+
primary.textContent = 'Stop scan';
|
|
907
|
+
primary.className = 'oobee-stop-primary';
|
|
908
|
+
Object.assign(primary.style, {
|
|
909
|
+
border: 'none',
|
|
910
|
+
borderRadius: '999px',
|
|
911
|
+
padding: '12px 16px',
|
|
912
|
+
fontSize: '15px',
|
|
913
|
+
fontWeight: '600',
|
|
914
|
+
color: '#fff',
|
|
915
|
+
background: '#9021A6',
|
|
916
|
+
cursor: 'pointer'
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
const cancel = document.createElement('button');
|
|
920
|
+
cancel.type = 'button';
|
|
921
|
+
cancel.textContent = 'No, continue scan';
|
|
922
|
+
cancel.className = 'oobee-stop-cancel';
|
|
923
|
+
Object.assign(cancel.style, {
|
|
924
|
+
border: 'none',
|
|
925
|
+
background: 'transparent',
|
|
926
|
+
fontSize: '14px',
|
|
927
|
+
justifySelf: 'center',
|
|
928
|
+
cursor: 'pointer',
|
|
929
|
+
padding: '6px'
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
actions.appendChild(primary);
|
|
933
|
+
actions.appendChild(cancel);
|
|
934
|
+
const shouldHideInput = !!(vars?.opts && vars.opts.hideStopInput);
|
|
935
|
+
if (!shouldHideInput) {
|
|
936
|
+
form.appendChild(label);
|
|
937
|
+
form.appendChild(input);
|
|
938
|
+
}
|
|
939
|
+
form.appendChild(actions);
|
|
940
|
+
bodyWrap.appendChild(form);
|
|
941
|
+
|
|
942
|
+
stopDialog.appendChild(head);
|
|
943
|
+
stopDialog.appendChild(bodyWrap);
|
|
944
|
+
shadowRoot.appendChild(stopDialog);
|
|
945
|
+
|
|
946
|
+
let stopResolver: null | ((v: { confirmed: boolean; label: string }) => void) = null;
|
|
947
|
+
const hideStop = () => { try { stopDialog.close(); } catch {} stopResolver = null; };
|
|
948
|
+
const showStop = () => {
|
|
949
|
+
if (!shouldHideInput) input.value = '';
|
|
950
|
+
try { stopDialog.showModal(); } catch {}
|
|
951
|
+
if (!shouldHideInput) {
|
|
952
|
+
requestAnimationFrame(() => {
|
|
953
|
+
try { input.focus({ preventScroll: true }); input.select(); } catch {}
|
|
954
|
+
});
|
|
360
955
|
}
|
|
956
|
+
};
|
|
957
|
+
form.addEventListener('submit', (e) => {
|
|
958
|
+
e.preventDefault();
|
|
959
|
+
const v = (input.value || '').trim();
|
|
960
|
+
if (stopResolver) stopResolver({ confirmed: true, label: v });
|
|
961
|
+
hideStop();
|
|
962
|
+
});
|
|
963
|
+
closeX.addEventListener('click', () => {
|
|
964
|
+
if (stopResolver) stopResolver({ confirmed: false, label: '' });
|
|
965
|
+
hideStop();
|
|
966
|
+
});
|
|
967
|
+
cancel.addEventListener('click', () => {
|
|
968
|
+
if (stopResolver) stopResolver({ confirmed: false, label: '' });
|
|
969
|
+
hideStop();
|
|
970
|
+
});
|
|
971
|
+
stopDialog.addEventListener('cancel', (e) => {
|
|
972
|
+
e.preventDefault();
|
|
973
|
+
if (stopResolver) stopResolver({ confirmed: false, label: '' });
|
|
974
|
+
hideStop();
|
|
975
|
+
});
|
|
976
|
+
(customWindow as Window).oobeeShowStopModal = () =>
|
|
977
|
+
new Promise<{ confirmed: boolean; label: string }>((resolve) => {
|
|
978
|
+
stopResolver = resolve;
|
|
979
|
+
showStop();
|
|
980
|
+
});
|
|
981
|
+
(customWindow as Window).oobeeHideStopModal = hideStop;
|
|
982
|
+
|
|
983
|
+
if (document.body) {
|
|
984
|
+
document.body.appendChild(shadowHost);
|
|
361
985
|
} else if (document.head) {
|
|
362
986
|
// The <head> element exists
|
|
363
987
|
// Append the variable below the head
|
|
@@ -367,8 +991,10 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos) => {
|
|
|
367
991
|
// Append the variable to the document
|
|
368
992
|
document.documentElement.appendChild(shadowHost);
|
|
369
993
|
}
|
|
994
|
+
positionMinimizeBtn();
|
|
995
|
+
setDraggableSidebarMenu();
|
|
370
996
|
},
|
|
371
|
-
{ menuPos, MENU_POSITION, urlsCrawled },
|
|
997
|
+
{ menuPos, MENU_POSITION, urlsCrawled, opts },
|
|
372
998
|
)
|
|
373
999
|
.then(() => {
|
|
374
1000
|
log('Overlay menu: successfully added');
|
|
@@ -381,7 +1007,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos) => {
|
|
|
381
1007
|
export const removeOverlayMenu = async page => {
|
|
382
1008
|
await page
|
|
383
1009
|
.evaluate(() => {
|
|
384
|
-
const existingOverlay = document.querySelector('#
|
|
1010
|
+
const existingOverlay = document.querySelector('#oobeeShadowHost');
|
|
385
1011
|
if (existingOverlay) {
|
|
386
1012
|
existingOverlay.remove();
|
|
387
1013
|
return true;
|
|
@@ -396,7 +1022,7 @@ export const removeOverlayMenu = async page => {
|
|
|
396
1022
|
};
|
|
397
1023
|
|
|
398
1024
|
export const initNewPage = async (page, pageClosePromises, processPageParams, pagesDict) => {
|
|
399
|
-
let menuPos = MENU_POSITION.
|
|
1025
|
+
let menuPos = MENU_POSITION.right;
|
|
400
1026
|
|
|
401
1027
|
// eslint-disable-next-line no-underscore-dangle
|
|
402
1028
|
const pageId = page._guid;
|
|
@@ -413,42 +1039,106 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
413
1039
|
pageClosePromises.push(pageClosePromise);
|
|
414
1040
|
|
|
415
1041
|
if (!pagesDict[pageId]) {
|
|
416
|
-
pagesDict[pageId] = {
|
|
1042
|
+
pagesDict[pageId] = {
|
|
1043
|
+
page,
|
|
1044
|
+
isScanning: false,
|
|
1045
|
+
collapsed: false,
|
|
1046
|
+
};
|
|
417
1047
|
}
|
|
418
1048
|
|
|
419
1049
|
type handleOnScanClickFunction = () => void;
|
|
420
1050
|
|
|
421
1051
|
// Window functions exposed in browser
|
|
422
1052
|
const handleOnScanClick: handleOnScanClickFunction = async () => {
|
|
1053
|
+
consoleLogger.info('Scan: click detected');
|
|
423
1054
|
log('Scan: click detected');
|
|
424
1055
|
try {
|
|
1056
|
+
pagesDict[pageId].isScanning = true;
|
|
425
1057
|
await removeOverlayMenu(page);
|
|
426
1058
|
await processPage(page, processPageParams);
|
|
427
1059
|
log('Scan: success');
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
1060
|
+
pagesDict[pageId].isScanning = false;
|
|
1061
|
+
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1062
|
+
inProgress: false,
|
|
1063
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1064
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1065
|
+
});
|
|
435
1066
|
} catch (error) {
|
|
436
1067
|
log(`Scan failed ${error}`);
|
|
437
1068
|
}
|
|
438
1069
|
};
|
|
439
1070
|
|
|
440
|
-
|
|
1071
|
+
const handleOnStopClick = async () => {
|
|
1072
|
+
const scannedCount = processPageParams?.urlsCrawled?.scanned?.length ?? 0;
|
|
1073
|
+
if (scannedCount === 0) {
|
|
1074
|
+
if (typeof processPageParams.stopAll === 'function') {
|
|
1075
|
+
try {
|
|
1076
|
+
await processPageParams.stopAll();
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
// ignore invalid; continue without label
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
try {
|
|
1085
|
+
const inputValue = await page.evaluate(async () => {
|
|
1086
|
+
const win = window as Window;
|
|
1087
|
+
if (typeof win.oobeeShowStopModal === 'function') {
|
|
1088
|
+
return await win.oobeeShowStopModal();
|
|
1089
|
+
}
|
|
1090
|
+
const ok = window.confirm('Are you sure you want to stop this scan?');
|
|
1091
|
+
return { confirmed: ok, label: '' };
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
if (!inputValue?.confirmed) {
|
|
1095
|
+
await page.evaluate(() => {
|
|
1096
|
+
const stopBtn = document.getElementById('oobeeBtnStop') as HTMLButtonElement | null;
|
|
1097
|
+
if (stopBtn) {
|
|
1098
|
+
stopBtn.disabled = false;
|
|
1099
|
+
stopBtn.textContent = 'Stop';
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const label = (inputValue.label || '').trim();
|
|
1106
|
+
try {
|
|
1107
|
+
const { isValid } = validateCustomFlowLabel(label);
|
|
1108
|
+
if (isValid && label) {
|
|
1109
|
+
processPageParams.customFlowLabel = label;
|
|
1110
|
+
}
|
|
1111
|
+
} catch {
|
|
1112
|
+
// ignore invalid; continue without label
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (typeof processPageParams.stopAll === 'function') {
|
|
1116
|
+
try {
|
|
1117
|
+
await processPageParams.stopAll();
|
|
1118
|
+
} catch (e) {
|
|
1119
|
+
// any console log will be on user browser, do not need to log
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
} catch (e) {
|
|
1123
|
+
// any console log will be on user browser, do not need to log
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
|
|
441
1127
|
page.on('domcontentloaded', async () => {
|
|
442
1128
|
try {
|
|
443
1129
|
const existingOverlay = await page.evaluate(() => {
|
|
444
|
-
return document.querySelector('#
|
|
1130
|
+
return document.querySelector('#oobeeShadowHost');
|
|
445
1131
|
});
|
|
446
1132
|
|
|
447
1133
|
consoleLogger.info(`Overlay state: ${existingOverlay}`);
|
|
448
1134
|
|
|
449
1135
|
if (!existingOverlay) {
|
|
450
1136
|
consoleLogger.info(`Adding overlay menu to page: ${page.url()}`);
|
|
451
|
-
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos
|
|
1137
|
+
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1138
|
+
inProgress: !!pagesDict[pageId]?.isScanning,
|
|
1139
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1140
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1141
|
+
});
|
|
452
1142
|
}
|
|
453
1143
|
|
|
454
1144
|
setTimeout(() => {
|
|
@@ -474,6 +1164,7 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
474
1164
|
});
|
|
475
1165
|
|
|
476
1166
|
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
1167
|
+
await page.exposeFunction('handleOnStopClick', handleOnStopClick);
|
|
477
1168
|
|
|
478
1169
|
type UpdateMenuPosFunction = (newPos: any) => void;
|
|
479
1170
|
|
|
@@ -481,7 +1172,6 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
481
1172
|
const updateMenuPos: UpdateMenuPosFunction = newPos => {
|
|
482
1173
|
const prevPos = menuPos;
|
|
483
1174
|
if (prevPos !== newPos) {
|
|
484
|
-
console.log(`Overlay menu: position updated from ${prevPos} to ${newPos}`);
|
|
485
1175
|
menuPos = newPos;
|
|
486
1176
|
}
|
|
487
1177
|
};
|