@govtechsg/oobee 0.10.76 → 0.10.78-alpha1
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/publish.yml +8 -1
- package/INTEGRATION.md +50 -3
- package/dist/cli.js +252 -0
- package/dist/combine.js +221 -0
- package/dist/constants/cliFunctions.js +306 -0
- package/dist/constants/common.js +1669 -0
- package/dist/constants/constants.js +913 -0
- package/dist/constants/errorMeta.json +319 -0
- package/dist/constants/itemTypeDescription.js +7 -0
- package/dist/constants/oobeeAi.js +121 -0
- package/dist/constants/questions.js +151 -0
- package/dist/constants/sampleData.js +176 -0
- package/dist/crawlers/commonCrawlerFunc.js +428 -0
- package/dist/crawlers/crawlDomain.js +613 -0
- package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
- package/dist/crawlers/crawlLocalFile.js +151 -0
- package/dist/crawlers/crawlSitemap.js +303 -0
- package/dist/crawlers/custom/escapeCssSelector.js +10 -0
- package/dist/crawlers/custom/evaluateAltText.js +11 -0
- package/dist/crawlers/custom/extractAndGradeText.js +44 -0
- package/dist/crawlers/custom/extractText.js +27 -0
- package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
- package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
- package/dist/crawlers/custom/framesCheck.js +37 -0
- package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
- package/dist/crawlers/custom/gradeReadability.js +23 -0
- package/dist/crawlers/custom/utils.js +1024 -0
- package/dist/crawlers/custom/xPathToCss.js +147 -0
- package/dist/crawlers/guards/urlGuard.js +71 -0
- package/dist/crawlers/pdfScanFunc.js +276 -0
- package/dist/crawlers/runCustom.js +89 -0
- package/dist/exclusions.txt +7 -0
- package/dist/generateHtmlReport.js +144 -0
- package/dist/index.js +62 -0
- package/dist/logs.js +84 -0
- package/dist/mergeAxeResults.js +1588 -0
- package/dist/npmIndex.js +640 -0
- package/dist/proxyService.js +360 -0
- package/dist/runGenerateJustHtmlReport.js +16 -0
- package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
- package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
- package/dist/services/s3Uploader.js +127 -0
- package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
- package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
- package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
- package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
- package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
- package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
- package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
- package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
- package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/dist/static/ejs/partials/components/topTen.ejs +6 -0
- package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
- package/dist/static/ejs/partials/footer.ejs +24 -0
- package/dist/static/ejs/partials/header.ejs +14 -0
- package/dist/static/ejs/partials/main.ejs +29 -0
- package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
- package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
- package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
- package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
- package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
- package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
- package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
- package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
- package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
- package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
- package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
- package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
- package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
- package/dist/static/ejs/partials/styles/header.ejs +7 -0
- package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
- package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
- package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
- package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
- package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
- package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
- package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
- package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
- package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
- package/dist/static/ejs/partials/summaryMain.ejs +49 -0
- package/dist/static/ejs/report.ejs +226 -0
- package/dist/static/ejs/summary.ejs +47 -0
- package/dist/types/types.js +1 -0
- package/dist/utils.js +1070 -0
- package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
- package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
- package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
- package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
- package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
- package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
- package/examples/oobee-scan-html-demo.js +51 -0
- package/examples/oobee-scan-page-demo.js +40 -0
- package/package.json +9 -3
- package/src/constants/common.ts +2 -2
- package/src/constants/constants.ts +3 -1
- package/src/crawlers/crawlDomain.ts +1 -0
- package/src/crawlers/runCustom.ts +0 -1
- package/src/mergeAxeResults.ts +43 -22
- package/src/npmIndex.ts +500 -131
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
/* eslint-disable no-shadow */
|
|
2
|
+
/* eslint-disable no-alert */
|
|
3
|
+
/* eslint-disable no-param-reassign */
|
|
4
|
+
/* eslint-env browser */
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { runAxeScript } from '../commonCrawlerFunc.js';
|
|
7
|
+
import { consoleLogger, guiInfoLog } from '../../logs.js';
|
|
8
|
+
import { guiInfoStatusTypes } from '../../constants/constants.js';
|
|
9
|
+
import { isSkippedUrl, validateCustomFlowLabel } from '../../constants/common.js';
|
|
10
|
+
//! For Cypress Test
|
|
11
|
+
// env to check if Cypress test is running
|
|
12
|
+
const isCypressTest = process.env.IS_CYPRESS_TEST === 'true';
|
|
13
|
+
export const DEBUG = false;
|
|
14
|
+
export const log = str => {
|
|
15
|
+
if (DEBUG) {
|
|
16
|
+
console.log(str);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const screenshotFullPage = async (page, screenshotsDir, screenshotIdx) => {
|
|
20
|
+
const imgName = `PHScan-screenshot${screenshotIdx}.png`;
|
|
21
|
+
const imgPath = path.join(screenshotsDir, imgName);
|
|
22
|
+
const originalSize = page.viewportSize();
|
|
23
|
+
try {
|
|
24
|
+
const fullPageSize = await page.evaluate(() => ({
|
|
25
|
+
width: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.body.offsetWidth, document.documentElement.offsetWidth, document.body.clientWidth, document.documentElement.clientWidth),
|
|
26
|
+
height: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight),
|
|
27
|
+
}));
|
|
28
|
+
const usesInfiniteScroll = async () => {
|
|
29
|
+
const prevHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
30
|
+
await page.evaluate(() => {
|
|
31
|
+
window.scrollTo(0, document.body.scrollHeight);
|
|
32
|
+
});
|
|
33
|
+
const isLoadMoreContent = async () => new Promise(resolve => {
|
|
34
|
+
setTimeout(async () => {
|
|
35
|
+
await page.waitForLoadState('domcontentloaded');
|
|
36
|
+
const newHeight = await page.evaluate(
|
|
37
|
+
// eslint-disable-next-line no-shadow
|
|
38
|
+
() => document.body.scrollHeight);
|
|
39
|
+
const result = newHeight > prevHeight;
|
|
40
|
+
resolve(result);
|
|
41
|
+
}, 2500);
|
|
42
|
+
});
|
|
43
|
+
const result = await isLoadMoreContent();
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
await usesInfiniteScroll();
|
|
47
|
+
// scroll back to top of page for screenshot
|
|
48
|
+
await page.evaluate(() => {
|
|
49
|
+
window.scrollTo(0, 0);
|
|
50
|
+
});
|
|
51
|
+
consoleLogger.info(`Screenshot page at: ${page.url()}`);
|
|
52
|
+
await page.screenshot({
|
|
53
|
+
timeout: 5000,
|
|
54
|
+
path: imgPath,
|
|
55
|
+
clip: {
|
|
56
|
+
x: 0,
|
|
57
|
+
y: 0,
|
|
58
|
+
width: fullPageSize.width,
|
|
59
|
+
height: 5400,
|
|
60
|
+
},
|
|
61
|
+
fullPage: true,
|
|
62
|
+
scale: 'css',
|
|
63
|
+
});
|
|
64
|
+
if (originalSize)
|
|
65
|
+
await page.setViewportSize(originalSize);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
consoleLogger.error('Unable to take screenshot');
|
|
69
|
+
// Do not return screenshot path if screenshot fails
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
return `screenshots/${imgName}`; // relative path from reports folder
|
|
73
|
+
};
|
|
74
|
+
export const runAxeScan = async (page, includeScreenshots, randomToken, customFlowDetails, dataset, urlsCrawled) => {
|
|
75
|
+
const result = await runAxeScript({ includeScreenshots, page, randomToken, customFlowDetails });
|
|
76
|
+
await dataset.pushData(result);
|
|
77
|
+
const rawTitle = result.pageTitle ?? '';
|
|
78
|
+
let pageTitleTextOnly = rawTitle; // Note: The original pageTitle contains the index and is being used in top 10 issues
|
|
79
|
+
if (typeof result.pageIndex === 'number') {
|
|
80
|
+
const re = new RegExp(`^\\s*${result.pageIndex}\\s*:\\s*`);
|
|
81
|
+
pageTitleTextOnly = rawTitle.replace(re, '');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
pageTitleTextOnly = rawTitle.replace(/^\s*\d+\s*:\s*/, '');
|
|
85
|
+
}
|
|
86
|
+
urlsCrawled.scanned.push({
|
|
87
|
+
url: page.url(),
|
|
88
|
+
pageTitle: pageTitleTextOnly,
|
|
89
|
+
pageImagePath: customFlowDetails.pageImagePath,
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
export const processPage = async (page, processPageParams) => {
|
|
93
|
+
// make sure to update processPageParams' scannedIdx
|
|
94
|
+
processPageParams.scannedIdx += 1;
|
|
95
|
+
let { includeScreenshots } = processPageParams;
|
|
96
|
+
const { scannedIdx, blacklistedPatterns, dataset, intermediateScreenshotsPath, urlsCrawled, randomToken, } = processPageParams;
|
|
97
|
+
try {
|
|
98
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
consoleLogger.info('Unable to detect page load state');
|
|
102
|
+
}
|
|
103
|
+
consoleLogger.info(`Attempting to scan: ${page.url()}`);
|
|
104
|
+
const pageUrl = page.url();
|
|
105
|
+
if (blacklistedPatterns && isSkippedUrl(pageUrl, blacklistedPatterns)) {
|
|
106
|
+
const continueScan = await page.evaluate(() => window.confirm('Page has been excluded, would you still like to proceed with the scan?'));
|
|
107
|
+
if (!continueScan) {
|
|
108
|
+
urlsCrawled.userExcluded.push({
|
|
109
|
+
url: pageUrl,
|
|
110
|
+
pageTitle: pageUrl,
|
|
111
|
+
actualUrl: pageUrl,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// TODO: Check if necessary
|
|
117
|
+
// To skip already scanned pages
|
|
118
|
+
// if (urlsCrawled.scanned.some(scan => scan.url === pageUrl)) {
|
|
119
|
+
// page.evaluate(() => {
|
|
120
|
+
// window.alert('Page has already been scanned, skipping scan.');
|
|
121
|
+
// });
|
|
122
|
+
// return;
|
|
123
|
+
// }
|
|
124
|
+
try {
|
|
125
|
+
const initialScrollPos = await page.evaluate(() => ({
|
|
126
|
+
x: window.scrollX,
|
|
127
|
+
y: window.scrollY,
|
|
128
|
+
}));
|
|
129
|
+
const pageImagePath = await screenshotFullPage(page, intermediateScreenshotsPath, scannedIdx);
|
|
130
|
+
// TODO: This is a temporary fix to not take element screenshots on pages when errors out at full page screenshot
|
|
131
|
+
if (pageImagePath === '') {
|
|
132
|
+
includeScreenshots = false;
|
|
133
|
+
}
|
|
134
|
+
await runAxeScan(page, includeScreenshots, randomToken, {
|
|
135
|
+
pageIndex: scannedIdx,
|
|
136
|
+
pageImagePath,
|
|
137
|
+
}, dataset, urlsCrawled);
|
|
138
|
+
if (includeScreenshots) {
|
|
139
|
+
consoleLogger.info(`Successfully screenshot page at: ${page.url()}`);
|
|
140
|
+
}
|
|
141
|
+
guiInfoLog(guiInfoStatusTypes.SCANNED, {
|
|
142
|
+
numScanned: urlsCrawled.scanned.length,
|
|
143
|
+
urlScanned: pageUrl,
|
|
144
|
+
});
|
|
145
|
+
await page.evaluate(pos => {
|
|
146
|
+
window.scrollTo(pos.x, pos.y);
|
|
147
|
+
}, initialScrollPos);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
consoleLogger.error(`Error in scanning page: ${pageUrl}`);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
export const MENU_POSITION = {
|
|
154
|
+
left: 'LEFT',
|
|
155
|
+
right: 'RIGHT',
|
|
156
|
+
};
|
|
157
|
+
export const updateMenu = async (page, urlsCrawled) => {
|
|
158
|
+
log(`Overlay menu: updating: ${page.url()}`);
|
|
159
|
+
await page.evaluate(vars => {
|
|
160
|
+
const shadowHost = document.querySelector('#oobee-shadow-host');
|
|
161
|
+
if (shadowHost) {
|
|
162
|
+
const p = shadowHost.shadowRoot.querySelector('#oobee-p-pages-scanned');
|
|
163
|
+
if (p) {
|
|
164
|
+
p.textContent = `Pages Scanned: ${vars.urlsCrawled.scanned.length || 0}`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}, { urlsCrawled });
|
|
168
|
+
consoleLogger.info(`Overlay menu updated`);
|
|
169
|
+
};
|
|
170
|
+
export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
171
|
+
inProgress: false,
|
|
172
|
+
collapsed: false,
|
|
173
|
+
}) => {
|
|
174
|
+
await page.waitForLoadState('domcontentloaded');
|
|
175
|
+
consoleLogger.info(`Overlay menu: adding to ${menuPos}...`);
|
|
176
|
+
// Add the overlay menu with initial styling
|
|
177
|
+
return page
|
|
178
|
+
.evaluate(async (vars) => {
|
|
179
|
+
const customWindow = window;
|
|
180
|
+
const inProgress = !!(vars?.opts && vars.opts.inProgress);
|
|
181
|
+
const collapsedOption = !!(vars?.opts && vars.opts.collapsed);
|
|
182
|
+
const panel = document.createElement('aside');
|
|
183
|
+
panel.className = 'oobee-panel';
|
|
184
|
+
const minBtn = document.createElement('button');
|
|
185
|
+
minBtn.type = 'button';
|
|
186
|
+
minBtn.className = 'oobee-minbtn';
|
|
187
|
+
minBtn.setAttribute('aria-label', 'Minimize/expand panel');
|
|
188
|
+
const MINBTN_SVG = `
|
|
189
|
+
<svg class="oobee-minbtn__icon" xmlns="http://www.w3.org/2000/svg"
|
|
190
|
+
width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
|
|
191
|
+
<g clip-path="url(#clip0_59_3691)">
|
|
192
|
+
<path d="M6.41 6L5 7.41L9.58 12L5 16.59L6.41 18L12.41 12L6.41 6Z" fill="#9021A6"/>
|
|
193
|
+
<path d="M14.41 6L13 7.41L17.58 12L13 16.59L14.41 18L20.41 12L14.41 6Z" fill="#9021A6"/>
|
|
194
|
+
</g>
|
|
195
|
+
<defs>
|
|
196
|
+
<clipPath id="clip0_59_3691">
|
|
197
|
+
<rect width="24" height="24" fill="white"/>
|
|
198
|
+
</clipPath>
|
|
199
|
+
</defs>
|
|
200
|
+
</svg>
|
|
201
|
+
`;
|
|
202
|
+
minBtn.innerHTML = MINBTN_SVG;
|
|
203
|
+
let currentPos = (vars.menuPos || 'RIGHT');
|
|
204
|
+
const isCollapsed = () => panel.classList.contains('collapsed');
|
|
205
|
+
const setPosClass = (pos) => {
|
|
206
|
+
panel.classList.remove('pos-left', 'pos-right');
|
|
207
|
+
minBtn.classList.remove('pos-left', 'pos-right');
|
|
208
|
+
if (pos === 'LEFT') {
|
|
209
|
+
panel.classList.add('pos-left');
|
|
210
|
+
minBtn.classList.add('pos-left');
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
panel.classList.add('pos-right');
|
|
214
|
+
minBtn.classList.add('pos-right');
|
|
215
|
+
}
|
|
216
|
+
positionMinimizeBtn();
|
|
217
|
+
setDraggableSidebarMenu();
|
|
218
|
+
};
|
|
219
|
+
const toggleCollapsed = (force) => {
|
|
220
|
+
const willCollapse = (typeof force === 'boolean') ? force : !isCollapsed();
|
|
221
|
+
if (willCollapse) {
|
|
222
|
+
panel.classList.add('collapsed');
|
|
223
|
+
localStorage.setItem('oobee:overlay-collapsed', '1');
|
|
224
|
+
customWindow.oobeeSetCollapsed?.(true);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
panel.classList.remove('collapsed');
|
|
228
|
+
localStorage.setItem('oobee:overlay-collapsed', '0');
|
|
229
|
+
customWindow.oobeeSetCollapsed?.(false);
|
|
230
|
+
}
|
|
231
|
+
positionMinimizeBtn();
|
|
232
|
+
setDraggableSidebarMenu();
|
|
233
|
+
};
|
|
234
|
+
setPosClass(currentPos);
|
|
235
|
+
const persisted = localStorage.getItem('oobee:overlay-collapsed');
|
|
236
|
+
const startCollapsed = persisted != null ? persisted === '1' : collapsedOption;
|
|
237
|
+
if (startCollapsed)
|
|
238
|
+
panel.classList.add('collapsed');
|
|
239
|
+
const header = document.createElement('div');
|
|
240
|
+
header.className = 'oobee-header';
|
|
241
|
+
const grip = document.createElement('button');
|
|
242
|
+
grip.type = 'button';
|
|
243
|
+
grip.className = 'oobee-grip';
|
|
244
|
+
grip.setAttribute('aria-label', 'Drag to move panel left or right');
|
|
245
|
+
const GRIP_SVG = `
|
|
246
|
+
<svg class="oobee-grip__icon" xmlns="http://www.w3.org/2000/svg"
|
|
247
|
+
width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
|
|
248
|
+
<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"/>
|
|
249
|
+
</svg>
|
|
250
|
+
`;
|
|
251
|
+
grip.innerHTML = GRIP_SVG;
|
|
252
|
+
const leftSpacer = document.createElement('div');
|
|
253
|
+
leftSpacer.className = 'oobee-spacer';
|
|
254
|
+
const rightSpacer = document.createElement('div');
|
|
255
|
+
rightSpacer.className = 'oobee-spacer';
|
|
256
|
+
header.appendChild(leftSpacer);
|
|
257
|
+
header.appendChild(grip);
|
|
258
|
+
header.appendChild(rightSpacer);
|
|
259
|
+
const body = document.createElement('div');
|
|
260
|
+
body.className = 'oobee-body';
|
|
261
|
+
const h2 = document.createElement('h2');
|
|
262
|
+
h2.id = 'oobeeHPagesScanned';
|
|
263
|
+
h2.className = 'oobee-section-title';
|
|
264
|
+
h2.textContent = 'Pages Scanned';
|
|
265
|
+
const scanBtn = document.createElement('button');
|
|
266
|
+
scanBtn.id = 'oobeeBtnScan';
|
|
267
|
+
scanBtn.className = 'oobee-btn oobee-btn-primary';
|
|
268
|
+
scanBtn.innerText = 'Scan this page';
|
|
269
|
+
scanBtn.disabled = inProgress;
|
|
270
|
+
scanBtn.addEventListener('click', async () => customWindow.handleOnScanClick?.());
|
|
271
|
+
const stopBtn = document.createElement('button');
|
|
272
|
+
stopBtn.id = 'oobeeBtnStop';
|
|
273
|
+
stopBtn.className = 'oobee-btn oobee-btn-secondary';
|
|
274
|
+
stopBtn.innerText = 'Stop scan';
|
|
275
|
+
stopBtn.addEventListener('click', async () => customWindow.handleOnStopClick?.());
|
|
276
|
+
const btnGroup = document.createElement('div');
|
|
277
|
+
btnGroup.className = 'oobee-actions';
|
|
278
|
+
btnGroup.appendChild(scanBtn);
|
|
279
|
+
btnGroup.appendChild(stopBtn);
|
|
280
|
+
const listWrap = document.createElement('div');
|
|
281
|
+
listWrap.id = 'oobeeList';
|
|
282
|
+
listWrap.className = 'oobee-list';
|
|
283
|
+
const renderList = () => {
|
|
284
|
+
const scanned = vars.urlsCrawled.scanned || [];
|
|
285
|
+
listWrap.innerHTML = '';
|
|
286
|
+
if (scanned.length === 0) {
|
|
287
|
+
const empty = document.createElement('div');
|
|
288
|
+
empty.className = 'oobee-empty';
|
|
289
|
+
empty.textContent = 'Scan a page to start';
|
|
290
|
+
listWrap.appendChild(empty);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const ol = document.createElement('ol');
|
|
294
|
+
ol.className = 'oobee-ol';
|
|
295
|
+
scanned.forEach((item) => {
|
|
296
|
+
const li = document.createElement('li');
|
|
297
|
+
li.className = 'oobee-li';
|
|
298
|
+
const title = document.createElement('div');
|
|
299
|
+
title.className = 'oobee-item-title';
|
|
300
|
+
title.textContent = (item.pageTitle && item.pageTitle.trim()) ? item.pageTitle : item.url;
|
|
301
|
+
const url = document.createElement('div');
|
|
302
|
+
url.className = 'oobee-item-url';
|
|
303
|
+
url.textContent = item.url;
|
|
304
|
+
li.appendChild(title);
|
|
305
|
+
li.appendChild(url);
|
|
306
|
+
ol.appendChild(li);
|
|
307
|
+
});
|
|
308
|
+
listWrap.appendChild(ol);
|
|
309
|
+
};
|
|
310
|
+
renderList();
|
|
311
|
+
body.appendChild(btnGroup);
|
|
312
|
+
body.appendChild(h2);
|
|
313
|
+
body.appendChild(listWrap);
|
|
314
|
+
panel.appendChild(header);
|
|
315
|
+
panel.appendChild(body);
|
|
316
|
+
const sheet = new CSSStyleSheet();
|
|
317
|
+
// TODO: separate out into css file if this gets too big
|
|
318
|
+
sheet.replaceSync(`
|
|
319
|
+
.oobee-panel{
|
|
320
|
+
position: fixed;
|
|
321
|
+
top: 0;
|
|
322
|
+
height: 100vh;
|
|
323
|
+
width: 320px;
|
|
324
|
+
box-sizing: border-box;
|
|
325
|
+
background: #fff;
|
|
326
|
+
color: #111;
|
|
327
|
+
z-index: 2147483647;
|
|
328
|
+
display: flex;
|
|
329
|
+
flex-direction: column;
|
|
330
|
+
border: 1px solid rgba(0,0,0,.08);border-left: none;border-right: none;
|
|
331
|
+
box-shadow: 0 6px 24px rgba(0,0,0,.08);
|
|
332
|
+
transition: width .16s ease,left .16s ease,right .16s ease
|
|
333
|
+
}
|
|
334
|
+
.oobee-panel.pos-right {
|
|
335
|
+
right: 0;
|
|
336
|
+
border-left: 1px solid rgba(0,0,0,.08)
|
|
337
|
+
}
|
|
338
|
+
.oobee-panel.pos-left {
|
|
339
|
+
left: 0;
|
|
340
|
+
border-right: 1px solid rgba(0,0,0,.08)
|
|
341
|
+
}
|
|
342
|
+
.oobee-panel.collapsed {
|
|
343
|
+
width: 56px;
|
|
344
|
+
overflow: hidden
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
:host {
|
|
348
|
+
--oobee-gap: 8px; /* distance from panel edge */
|
|
349
|
+
--oobee-panel-offset: 320px; /* overwritten by JS to actual width */
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* external minimize button (always OUTSIDE the panel) */
|
|
353
|
+
.oobee-minbtn {
|
|
354
|
+
position: fixed;
|
|
355
|
+
top: 0;
|
|
356
|
+
z-index: 2147483647;
|
|
357
|
+
width: 32px;
|
|
358
|
+
height: 32px;
|
|
359
|
+
border: none;
|
|
360
|
+
background: #fff;
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* right-docked: button sits to the LEFT of the panel */
|
|
365
|
+
.oobee-minbtn.pos-right{
|
|
366
|
+
right: calc(var(--oobee-panel-offset) + var(--oobee-gap));
|
|
367
|
+
}
|
|
368
|
+
/* left-docked: button sits to the RIGHT of the panel */
|
|
369
|
+
.oobee-minbtn.pos-left{
|
|
370
|
+
left: calc(var(--oobee-panel-offset) + var(--oobee-gap));
|
|
371
|
+
}
|
|
372
|
+
.oobee-minbtn:hover {
|
|
373
|
+
box-shadow:0 4px 12px rgba(0,0,0,.12);
|
|
374
|
+
}
|
|
375
|
+
.oobee-minbtn:active {
|
|
376
|
+
transform:translateY(1px);
|
|
377
|
+
}
|
|
378
|
+
.oobee-minbtn:focus-visible {
|
|
379
|
+
outline: 2px solid #7b4dff;
|
|
380
|
+
outline-offset: 2px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.oobee-header {
|
|
384
|
+
position: relative;
|
|
385
|
+
display: flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
justify-content: space-between;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.oobee-spacer {
|
|
391
|
+
width:28px;
|
|
392
|
+
height:28px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.oobee-grip{
|
|
396
|
+
border: 0;
|
|
397
|
+
background: #FFFFFF;
|
|
398
|
+
cursor: grab;
|
|
399
|
+
margin-top: 0.4rem;
|
|
400
|
+
}
|
|
401
|
+
.oobee-grip:active {
|
|
402
|
+
cursor:grabbing;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.oobee-body {
|
|
406
|
+
display: flex;
|
|
407
|
+
flex-direction: column;
|
|
408
|
+
flex: 1;
|
|
409
|
+
min-height: 0;
|
|
410
|
+
overflow: hidden;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.oobee-actions {
|
|
414
|
+
display: flex;
|
|
415
|
+
flex-direction: column;
|
|
416
|
+
gap: 12px;
|
|
417
|
+
padding: 1rem;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* Base button */
|
|
421
|
+
.oobee-btn {
|
|
422
|
+
width: 100%;
|
|
423
|
+
min-height: 44px;
|
|
424
|
+
border-radius: 999px;
|
|
425
|
+
padding: 12px 16px;
|
|
426
|
+
font-size: 16px;
|
|
427
|
+
line-height: 1.2;
|
|
428
|
+
font-weight: 400;
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
transition: {
|
|
431
|
+
box-shadow .12s ease,
|
|
432
|
+
transform .02s ease,
|
|
433
|
+
background-color .12s ease,
|
|
434
|
+
color .12s ease,
|
|
435
|
+
border-color .12s ease;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
.oobee-btn:disabled {
|
|
439
|
+
opacity:.6;
|
|
440
|
+
cursor:not-allowed
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* Primary (filled) */
|
|
444
|
+
.oobee-btn-primary {
|
|
445
|
+
background: #9021a6;
|
|
446
|
+
color: #fff;
|
|
447
|
+
border: 1px solid transparent;
|
|
448
|
+
}
|
|
449
|
+
.oobee-btn-primary:hover:not(:disabled) {
|
|
450
|
+
box-shadow:0 2px 10px rgba(0,0,0,.12);
|
|
451
|
+
}
|
|
452
|
+
.oobee-btn-primary:active:not(:disabled) {
|
|
453
|
+
transform:translateY(1px);
|
|
454
|
+
}
|
|
455
|
+
.oobee-btn-primary:focus-visible {
|
|
456
|
+
outline:2px solid #7b4dff;
|
|
457
|
+
outline-offset:2px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Stop button */
|
|
461
|
+
.oobee-btn-secondary{
|
|
462
|
+
background: #fff;
|
|
463
|
+
color: #9021A6;
|
|
464
|
+
border: 1px solid #9021A6;
|
|
465
|
+
}
|
|
466
|
+
.oobee-btn-secondary:active:not(:disabled) {
|
|
467
|
+
transform:translateY(1px);
|
|
468
|
+
}
|
|
469
|
+
.oobee-btn-secondary:focus-visible{
|
|
470
|
+
outline: 2px solid #7b4dff;
|
|
471
|
+
outline-offset:2px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* Text for empty scans */
|
|
475
|
+
.oobee-empty{
|
|
476
|
+
display: flex;
|
|
477
|
+
justify-content: center;
|
|
478
|
+
align-items: center;
|
|
479
|
+
height: 100%;
|
|
480
|
+
font-size: 14px;
|
|
481
|
+
color: #555555;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.oobee-list {
|
|
485
|
+
flex: 1;
|
|
486
|
+
min-height: 0;
|
|
487
|
+
overflow-y: auto;
|
|
488
|
+
padding-left: 1rem;
|
|
489
|
+
padding-right: 1rem;
|
|
490
|
+
padding-bottom: 1rem;
|
|
491
|
+
padding-top: 0;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.oobee-panel.collapsed .oobee-list {
|
|
495
|
+
display: none;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
#oobeeStopOverlay[hidden] {
|
|
499
|
+
display:none !important;
|
|
500
|
+
}
|
|
501
|
+
#oobeeStopOverlay {
|
|
502
|
+
display:grid;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.oobee-section-title {
|
|
506
|
+
font-size: 16px;
|
|
507
|
+
font-weight: 700;
|
|
508
|
+
color: #161616;
|
|
509
|
+
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
|
510
|
+
padding: 1rem;
|
|
511
|
+
margin: 0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.oobee-panel.collapsed .oobee-section-title {
|
|
515
|
+
display: none;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.oobee-ol {
|
|
519
|
+
margin: 0;
|
|
520
|
+
padding-left: 1.25rem;
|
|
521
|
+
display: flex;
|
|
522
|
+
flex-direction: column;
|
|
523
|
+
gap: 10px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.oobee-li {
|
|
527
|
+
list-style: decimal;
|
|
528
|
+
font-size: 14px;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.oobee-item-title {
|
|
532
|
+
font-size: 14px;
|
|
533
|
+
color: #161616;
|
|
534
|
+
white-space: nowrap;
|
|
535
|
+
overflow: hidden;
|
|
536
|
+
text-overflow: ellipsis;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.oobee-item-url {
|
|
540
|
+
font-size: 12px;
|
|
541
|
+
color: #6b7280;
|
|
542
|
+
white-space: nowrap;
|
|
543
|
+
overflow: hidden;
|
|
544
|
+
text-overflow: ellipsis;
|
|
545
|
+
direction: rtl;
|
|
546
|
+
text-align: left;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.oobee-minbtn__icon {
|
|
550
|
+
transition: transform .18s ease;
|
|
551
|
+
transform: rotate(0deg);
|
|
552
|
+
}
|
|
553
|
+
.oobee-minbtn__icon.is-left {
|
|
554
|
+
transform: rotate(180deg);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
:host-context(.oobee-snap) .oobee-panel,
|
|
558
|
+
:host-context(.oobee-snap) .oobee-minbtn { display:none !important; }
|
|
559
|
+
|
|
560
|
+
@media (max-width:1024px) {
|
|
561
|
+
.oobee-panel {
|
|
562
|
+
width:280px
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
@media (max-width:768px) {
|
|
566
|
+
.oobee-panel {
|
|
567
|
+
width: 92vw;
|
|
568
|
+
height: 100vh;
|
|
569
|
+
top: 0;
|
|
570
|
+
bottom: 0;
|
|
571
|
+
border-radius: 0;
|
|
572
|
+
}
|
|
573
|
+
.oobee-panel.collapsed {
|
|
574
|
+
width: auto;
|
|
575
|
+
height: auto;
|
|
576
|
+
padding: 0;
|
|
577
|
+
display: flex;
|
|
578
|
+
align-items: center;
|
|
579
|
+
justify-content: center;
|
|
580
|
+
border-radius: 999px;
|
|
581
|
+
box-shadow: 0 6px 24px rgba(0,0,0,.4);
|
|
582
|
+
top: auto;
|
|
583
|
+
bottom: max(16px, env(safe-area-inset-bottom,0px))
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
`);
|
|
587
|
+
document.documentElement.classList.remove('oobee-snap');
|
|
588
|
+
const shadowHost = document.createElement('div');
|
|
589
|
+
shadowHost.id = 'oobeeShadowHost';
|
|
590
|
+
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
|
|
591
|
+
shadowRoot.adoptedStyleSheets = [sheet];
|
|
592
|
+
shadowRoot.appendChild(panel);
|
|
593
|
+
shadowRoot.appendChild(minBtn);
|
|
594
|
+
function setDraggableSidebarMenu() {
|
|
595
|
+
const icon = minBtn.querySelector('.oobee-minbtn__icon');
|
|
596
|
+
if (!icon)
|
|
597
|
+
return;
|
|
598
|
+
const closed = isCollapsed();
|
|
599
|
+
const arrowPointsRight = (currentPos === 'RIGHT' && !closed) ||
|
|
600
|
+
(currentPos === 'LEFT' && closed);
|
|
601
|
+
icon.classList.toggle('is-left', !arrowPointsRight);
|
|
602
|
+
minBtn.setAttribute('aria-label', closed ? 'Expand panel' : 'Collapse panel');
|
|
603
|
+
}
|
|
604
|
+
function positionMinimizeBtn() {
|
|
605
|
+
const OPEN_OFFSET = 318;
|
|
606
|
+
const COLLAPSED_OFFSET = 55;
|
|
607
|
+
const offset = isCollapsed() ? COLLAPSED_OFFSET : OPEN_OFFSET;
|
|
608
|
+
minBtn.style.left = '';
|
|
609
|
+
minBtn.style.right = '';
|
|
610
|
+
if (currentPos === 'RIGHT') {
|
|
611
|
+
minBtn.style.right = `${offset}px`;
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
minBtn.style.left = `${offset}px`;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
positionMinimizeBtn();
|
|
618
|
+
setDraggableSidebarMenu();
|
|
619
|
+
minBtn.addEventListener('click', () => toggleCollapsed());
|
|
620
|
+
let startX = 0;
|
|
621
|
+
const THRESH = 40;
|
|
622
|
+
grip.addEventListener('pointerdown', (e) => {
|
|
623
|
+
startX = e.clientX;
|
|
624
|
+
grip.setPointerCapture(e.pointerId); // <-- use the button
|
|
625
|
+
});
|
|
626
|
+
grip.addEventListener('pointermove', (e) => {
|
|
627
|
+
if (!grip.hasPointerCapture?.(e.pointerId))
|
|
628
|
+
return; // <-- check the button
|
|
629
|
+
const dx = e.clientX - startX;
|
|
630
|
+
if (Math.abs(dx) >= THRESH) {
|
|
631
|
+
const nextPos = dx < 0 ? 'LEFT' : 'RIGHT';
|
|
632
|
+
if (nextPos !== currentPos) {
|
|
633
|
+
currentPos = nextPos;
|
|
634
|
+
setPosClass(currentPos);
|
|
635
|
+
window.updateMenuPos?.(currentPos);
|
|
636
|
+
}
|
|
637
|
+
startX = e.clientX;
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
grip.addEventListener('pointerup', (e) => {
|
|
641
|
+
try {
|
|
642
|
+
grip.releasePointerCapture(e.pointerId);
|
|
643
|
+
}
|
|
644
|
+
catch { }
|
|
645
|
+
});
|
|
646
|
+
const stopDialog = document.createElement('dialog');
|
|
647
|
+
stopDialog.id = 'oobeeStopDialog';
|
|
648
|
+
Object.assign(stopDialog.style, {
|
|
649
|
+
width: 'min(560px, calc(100vw - 32px))',
|
|
650
|
+
border: 'none',
|
|
651
|
+
padding: '0',
|
|
652
|
+
borderRadius: '16px',
|
|
653
|
+
overflow: 'hidden',
|
|
654
|
+
boxShadow: '0 10px 40px rgba(0,0,0,.35)',
|
|
655
|
+
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif'
|
|
656
|
+
});
|
|
657
|
+
const dialogSheet = new CSSStyleSheet();
|
|
658
|
+
dialogSheet.replaceSync(`
|
|
659
|
+
#oobeeStopDialog::backdrop {
|
|
660
|
+
background: rgba(0,0,0,.55);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* primary button hover/focus */
|
|
664
|
+
.oobee-stop-primary:hover {
|
|
665
|
+
filter: brightness(0.95);
|
|
666
|
+
}
|
|
667
|
+
.oobee-stop-primary:focus-visible {
|
|
668
|
+
outline: 2px solid #7b4dff; outline-offset: 2px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* cancel link hover */
|
|
672
|
+
.oobee-stop-cancel {
|
|
673
|
+
color: #9021A6;
|
|
674
|
+
text-decoration: underline;
|
|
675
|
+
}
|
|
676
|
+
.oobee-stop-cancel:hover {
|
|
677
|
+
filter: brightness(0.95);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* close “X” hover ring */
|
|
681
|
+
.oobee-stop-close:hover {
|
|
682
|
+
background: #f3f4f6;
|
|
683
|
+
}
|
|
684
|
+
`);
|
|
685
|
+
shadowRoot.adoptedStyleSheets = [sheet, dialogSheet];
|
|
686
|
+
const head = document.createElement('div');
|
|
687
|
+
Object.assign(head.style, {
|
|
688
|
+
padding: '20px 20px 8px 20px',
|
|
689
|
+
display: 'flex',
|
|
690
|
+
alignItems: 'center',
|
|
691
|
+
justifyContent: 'space-between',
|
|
692
|
+
gap: '8px'
|
|
693
|
+
});
|
|
694
|
+
const title = document.createElement('h2');
|
|
695
|
+
title.id = 'oobee-stop-title';
|
|
696
|
+
title.textContent = 'Are you sure you want to stop this scan?';
|
|
697
|
+
Object.assign(title.style, { margin: '0', fontSize: '22px', fontWeight: '700', lineHeight: '1.25' });
|
|
698
|
+
const closeX = document.createElement('button');
|
|
699
|
+
closeX.type = 'button';
|
|
700
|
+
closeX.setAttribute('aria-label', 'Close');
|
|
701
|
+
closeX.textContent = '×';
|
|
702
|
+
closeX.className = 'oobee-stop-close';
|
|
703
|
+
Object.assign(closeX.style, {
|
|
704
|
+
border: 'none',
|
|
705
|
+
background: 'transparent',
|
|
706
|
+
fontSize: '28px',
|
|
707
|
+
lineHeight: '1',
|
|
708
|
+
cursor: 'pointer',
|
|
709
|
+
color: '#4b5563',
|
|
710
|
+
width: '36px',
|
|
711
|
+
height: '36px',
|
|
712
|
+
borderRadius: '12px',
|
|
713
|
+
display: 'grid',
|
|
714
|
+
placeItems: 'center'
|
|
715
|
+
});
|
|
716
|
+
head.appendChild(title);
|
|
717
|
+
head.appendChild(closeX);
|
|
718
|
+
const bodyWrap = document.createElement('div');
|
|
719
|
+
Object.assign(bodyWrap.style, {
|
|
720
|
+
padding: '12px 20px 20px 20px'
|
|
721
|
+
});
|
|
722
|
+
const form = document.createElement('form');
|
|
723
|
+
form.noValidate = true;
|
|
724
|
+
form.autocomplete = 'off';
|
|
725
|
+
Object.assign(form.style, {
|
|
726
|
+
display: 'grid',
|
|
727
|
+
gridTemplateColumns: '1fr',
|
|
728
|
+
rowGap: '12px'
|
|
729
|
+
});
|
|
730
|
+
const label = document.createElement('label');
|
|
731
|
+
label.setAttribute('for', 'oobee-stop-input');
|
|
732
|
+
label.textContent = 'Enter a name for this scan';
|
|
733
|
+
Object.assign(label.style, { fontSize: '15px', fontWeight: '600' });
|
|
734
|
+
const input = document.createElement('input');
|
|
735
|
+
input.id = 'oobeeStopInput';
|
|
736
|
+
input.type = 'text';
|
|
737
|
+
Object.assign(input.style, {
|
|
738
|
+
width: '100%',
|
|
739
|
+
borderRadius: '5px',
|
|
740
|
+
border: '1px solid #e5e7eb',
|
|
741
|
+
padding: '12px 14px',
|
|
742
|
+
fontSize: '14px',
|
|
743
|
+
outline: 'none',
|
|
744
|
+
boxSizing: 'border-box'
|
|
745
|
+
});
|
|
746
|
+
input.addEventListener('focus', () => {
|
|
747
|
+
input.style.borderColor = '#7b4dff';
|
|
748
|
+
input.style.boxShadow = '0 0 0 3px rgba(123,77,255,.25)';
|
|
749
|
+
});
|
|
750
|
+
input.addEventListener('blur', () => {
|
|
751
|
+
input.style.borderColor = '#e5e7eb';
|
|
752
|
+
input.style.boxShadow = 'none';
|
|
753
|
+
});
|
|
754
|
+
const actions = document.createElement('div');
|
|
755
|
+
Object.assign(actions.style, { display: 'grid', gap: '12px', marginTop: '4px' });
|
|
756
|
+
const primary = document.createElement('button');
|
|
757
|
+
primary.type = 'submit';
|
|
758
|
+
primary.textContent = 'Stop scan';
|
|
759
|
+
primary.className = 'oobee-stop-primary';
|
|
760
|
+
Object.assign(primary.style, {
|
|
761
|
+
border: 'none',
|
|
762
|
+
borderRadius: '999px',
|
|
763
|
+
padding: '12px 16px',
|
|
764
|
+
fontSize: '15px',
|
|
765
|
+
fontWeight: '600',
|
|
766
|
+
color: '#fff',
|
|
767
|
+
background: '#9021A6',
|
|
768
|
+
cursor: 'pointer'
|
|
769
|
+
});
|
|
770
|
+
const cancel = document.createElement('button');
|
|
771
|
+
cancel.type = 'button';
|
|
772
|
+
cancel.textContent = 'No, continue scan';
|
|
773
|
+
cancel.className = 'oobee-stop-cancel';
|
|
774
|
+
Object.assign(cancel.style, {
|
|
775
|
+
border: 'none',
|
|
776
|
+
background: 'transparent',
|
|
777
|
+
fontSize: '14px',
|
|
778
|
+
justifySelf: 'center',
|
|
779
|
+
cursor: 'pointer',
|
|
780
|
+
padding: '6px'
|
|
781
|
+
});
|
|
782
|
+
actions.appendChild(primary);
|
|
783
|
+
actions.appendChild(cancel);
|
|
784
|
+
const shouldHideInput = !!(vars?.opts && vars.opts.hideStopInput);
|
|
785
|
+
if (!shouldHideInput) {
|
|
786
|
+
form.appendChild(label);
|
|
787
|
+
form.appendChild(input);
|
|
788
|
+
}
|
|
789
|
+
form.appendChild(actions);
|
|
790
|
+
bodyWrap.appendChild(form);
|
|
791
|
+
stopDialog.appendChild(head);
|
|
792
|
+
stopDialog.appendChild(bodyWrap);
|
|
793
|
+
shadowRoot.appendChild(stopDialog);
|
|
794
|
+
let stopResolver = null;
|
|
795
|
+
const hideStop = () => { try {
|
|
796
|
+
stopDialog.close();
|
|
797
|
+
}
|
|
798
|
+
catch { } stopResolver = null; };
|
|
799
|
+
const showStop = () => {
|
|
800
|
+
if (!shouldHideInput)
|
|
801
|
+
input.value = '';
|
|
802
|
+
try {
|
|
803
|
+
stopDialog.showModal();
|
|
804
|
+
}
|
|
805
|
+
catch { }
|
|
806
|
+
if (!shouldHideInput) {
|
|
807
|
+
requestAnimationFrame(() => {
|
|
808
|
+
try {
|
|
809
|
+
input.focus({ preventScroll: true });
|
|
810
|
+
input.select();
|
|
811
|
+
}
|
|
812
|
+
catch { }
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
form.addEventListener('submit', (e) => {
|
|
817
|
+
e.preventDefault();
|
|
818
|
+
const v = (input.value || '').trim();
|
|
819
|
+
if (stopResolver)
|
|
820
|
+
stopResolver({ confirmed: true, label: v });
|
|
821
|
+
hideStop();
|
|
822
|
+
});
|
|
823
|
+
closeX.addEventListener('click', () => {
|
|
824
|
+
if (stopResolver)
|
|
825
|
+
stopResolver({ confirmed: false, label: '' });
|
|
826
|
+
hideStop();
|
|
827
|
+
});
|
|
828
|
+
cancel.addEventListener('click', () => {
|
|
829
|
+
if (stopResolver)
|
|
830
|
+
stopResolver({ confirmed: false, label: '' });
|
|
831
|
+
hideStop();
|
|
832
|
+
});
|
|
833
|
+
stopDialog.addEventListener('cancel', (e) => {
|
|
834
|
+
e.preventDefault();
|
|
835
|
+
if (stopResolver)
|
|
836
|
+
stopResolver({ confirmed: false, label: '' });
|
|
837
|
+
hideStop();
|
|
838
|
+
});
|
|
839
|
+
customWindow.oobeeShowStopModal = () => new Promise((resolve) => {
|
|
840
|
+
stopResolver = resolve;
|
|
841
|
+
showStop();
|
|
842
|
+
});
|
|
843
|
+
customWindow.oobeeHideStopModal = hideStop;
|
|
844
|
+
if (document.body) {
|
|
845
|
+
document.body.appendChild(shadowHost);
|
|
846
|
+
}
|
|
847
|
+
else if (document.head) {
|
|
848
|
+
// The <head> element exists
|
|
849
|
+
// Append the variable below the head
|
|
850
|
+
document.head.insertAdjacentElement('afterend', shadowHost);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
// Neither <body> nor <head> nor <html> exists
|
|
854
|
+
// Append the variable to the document
|
|
855
|
+
document.documentElement.appendChild(shadowHost);
|
|
856
|
+
}
|
|
857
|
+
positionMinimizeBtn();
|
|
858
|
+
setDraggableSidebarMenu();
|
|
859
|
+
}, { menuPos, MENU_POSITION, urlsCrawled, opts })
|
|
860
|
+
.then(() => {
|
|
861
|
+
log('Overlay menu: successfully added');
|
|
862
|
+
})
|
|
863
|
+
.catch(error => {
|
|
864
|
+
error('Overlay menu: failed to add', error);
|
|
865
|
+
});
|
|
866
|
+
};
|
|
867
|
+
export const removeOverlayMenu = async (page) => {
|
|
868
|
+
await page
|
|
869
|
+
.evaluate(() => {
|
|
870
|
+
const existingOverlay = document.querySelector('#oobeeShadowHost');
|
|
871
|
+
if (existingOverlay) {
|
|
872
|
+
existingOverlay.remove();
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
return false;
|
|
876
|
+
})
|
|
877
|
+
.then(removed => {
|
|
878
|
+
if (removed) {
|
|
879
|
+
consoleLogger.info('Overlay Menu: successfully removed');
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
};
|
|
883
|
+
export const initNewPage = async (page, pageClosePromises, processPageParams, pagesDict) => {
|
|
884
|
+
let menuPos = MENU_POSITION.right;
|
|
885
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
886
|
+
const pageId = page._guid;
|
|
887
|
+
page.on('dialog', () => { });
|
|
888
|
+
const pageClosePromise = new Promise(resolve => {
|
|
889
|
+
page.on('close', () => {
|
|
890
|
+
log(`Page: close detected: ${page.url()}`);
|
|
891
|
+
delete pagesDict[pageId];
|
|
892
|
+
resolve(true);
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
pageClosePromises.push(pageClosePromise);
|
|
896
|
+
if (!pagesDict[pageId]) {
|
|
897
|
+
pagesDict[pageId] = {
|
|
898
|
+
page,
|
|
899
|
+
isScanning: false,
|
|
900
|
+
collapsed: false,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
// Window functions exposed in browser
|
|
904
|
+
const handleOnScanClick = async () => {
|
|
905
|
+
consoleLogger.info('Scan: click detected');
|
|
906
|
+
log('Scan: click detected');
|
|
907
|
+
try {
|
|
908
|
+
pagesDict[pageId].isScanning = true;
|
|
909
|
+
await removeOverlayMenu(page);
|
|
910
|
+
await processPage(page, processPageParams);
|
|
911
|
+
log('Scan: success');
|
|
912
|
+
pagesDict[pageId].isScanning = false;
|
|
913
|
+
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
914
|
+
inProgress: false,
|
|
915
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
916
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
log(`Scan failed ${error}`);
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
const handleOnStopClick = async () => {
|
|
924
|
+
const scannedCount = processPageParams?.urlsCrawled?.scanned?.length ?? 0;
|
|
925
|
+
if (scannedCount === 0) {
|
|
926
|
+
if (typeof processPageParams.stopAll === 'function') {
|
|
927
|
+
try {
|
|
928
|
+
await processPageParams.stopAll();
|
|
929
|
+
}
|
|
930
|
+
catch (e) {
|
|
931
|
+
// ignore invalid; continue without label
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
const inputValue = await page.evaluate(async () => {
|
|
938
|
+
const win = window;
|
|
939
|
+
if (typeof win.oobeeShowStopModal === 'function') {
|
|
940
|
+
return await win.oobeeShowStopModal();
|
|
941
|
+
}
|
|
942
|
+
const ok = window.confirm('Are you sure you want to stop this scan?');
|
|
943
|
+
return { confirmed: ok, label: '' };
|
|
944
|
+
});
|
|
945
|
+
if (!inputValue?.confirmed) {
|
|
946
|
+
await page.evaluate(() => {
|
|
947
|
+
const stopBtn = document.getElementById('oobeeBtnStop');
|
|
948
|
+
if (stopBtn) {
|
|
949
|
+
stopBtn.disabled = false;
|
|
950
|
+
stopBtn.textContent = 'Stop';
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const label = (inputValue.label || '').trim();
|
|
956
|
+
try {
|
|
957
|
+
const { isValid } = validateCustomFlowLabel(label);
|
|
958
|
+
if (isValid && label) {
|
|
959
|
+
processPageParams.customFlowLabel = label;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
// ignore invalid; continue without label
|
|
964
|
+
}
|
|
965
|
+
if (typeof processPageParams.stopAll === 'function') {
|
|
966
|
+
try {
|
|
967
|
+
await processPageParams.stopAll();
|
|
968
|
+
}
|
|
969
|
+
catch (e) {
|
|
970
|
+
// any console log will be on user browser, do not need to log
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch (e) {
|
|
975
|
+
// any console log will be on user browser, do not need to log
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
page.on('domcontentloaded', async () => {
|
|
979
|
+
try {
|
|
980
|
+
const existingOverlay = await page.evaluate(() => {
|
|
981
|
+
return document.querySelector('#oobeeShadowHost');
|
|
982
|
+
});
|
|
983
|
+
consoleLogger.info(`Overlay state: ${existingOverlay}`);
|
|
984
|
+
if (!existingOverlay) {
|
|
985
|
+
consoleLogger.info(`Adding overlay menu to page: ${page.url()}`);
|
|
986
|
+
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
987
|
+
inProgress: !!pagesDict[pageId]?.isScanning,
|
|
988
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
989
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
setTimeout(() => {
|
|
993
|
+
// Timeout here to slow things down a little
|
|
994
|
+
}, 1000);
|
|
995
|
+
//! For Cypress Test
|
|
996
|
+
// Auto-clicks 'Scan this page' button only once
|
|
997
|
+
if (isCypressTest) {
|
|
998
|
+
try {
|
|
999
|
+
await handleOnScanClick();
|
|
1000
|
+
page.close();
|
|
1001
|
+
}
|
|
1002
|
+
catch {
|
|
1003
|
+
consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
consoleLogger.info(`Overlay state: ${existingOverlay}`);
|
|
1007
|
+
}
|
|
1008
|
+
catch {
|
|
1009
|
+
consoleLogger.info('Error in adding overlay menu to page');
|
|
1010
|
+
consoleLogger.info('Error in adding overlay menu to page');
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
1014
|
+
await page.exposeFunction('handleOnStopClick', handleOnStopClick);
|
|
1015
|
+
// Define the updateMenuPos function
|
|
1016
|
+
const updateMenuPos = newPos => {
|
|
1017
|
+
const prevPos = menuPos;
|
|
1018
|
+
if (prevPos !== newPos) {
|
|
1019
|
+
menuPos = newPos;
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
await page.exposeFunction('updateMenuPos', updateMenuPos);
|
|
1023
|
+
return page;
|
|
1024
|
+
};
|