@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,483 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
function generateWcagConformanceLinks(conformanceList) {
|
|
3
|
+
const wcagConformanceUrls = {
|
|
4
|
+
wcag111: 'https://www.w3.org/TR/WCAG22/#non-text-content',
|
|
5
|
+
wcag122: 'https://www.w3.org/TR/WCAG22/#captions-prerecorded',
|
|
6
|
+
wcag131: 'https://www.w3.org/TR/WCAG22/#info-and-relationships',
|
|
7
|
+
wcag135: 'https://www.w3.org/TR/WCAG22/#identify-input-purpose',
|
|
8
|
+
wcag141: 'https://www.w3.org/TR/WCAG22/#use-of-color',
|
|
9
|
+
wcag142: 'https://www.w3.org/TR/WCAG22/#audio-control',
|
|
10
|
+
wcag143: 'https://www.w3.org/TR/WCAG22/#contrast-minimum',
|
|
11
|
+
wcag144: 'https://www.w3.org/TR/WCAG22/#resize-text',
|
|
12
|
+
wcag146: 'https://www.w3.org/TR/WCAG22/#contrast-enhanced',
|
|
13
|
+
wcag1412: 'https://www.w3.org/TR/WCAG22/#text-spacing',
|
|
14
|
+
wcag211: 'https://www.w3.org/TR/WCAG22/#keyboard',
|
|
15
|
+
wcag213: 'https://www.w3.org/WAI/WCAG22/Understanding/keyboard-no-exception.html',
|
|
16
|
+
wcag221: 'https://www.w3.org/TR/WCAG22/#timing-adjustable',
|
|
17
|
+
wcag222: 'https://www.w3.org/TR/WCAG22/#pause-stop-hide',
|
|
18
|
+
wcag224: 'https://www.w3.org/TR/WCAG22/#interruptions',
|
|
19
|
+
wcag241: 'https://www.w3.org/TR/WCAG22/#bypass-blocks',
|
|
20
|
+
wcag242: 'https://www.w3.org/TR/WCAG22/#page-titled',
|
|
21
|
+
wcag244: 'https://www.w3.org/TR/WCAG22/#link-purpose-in-context',
|
|
22
|
+
wcag249: 'https://www.w3.org/TR/WCAG22/#link-purpose-link-only',
|
|
23
|
+
wcag258: 'https://www.w3.org/TR/WCAG22/#target-size-minimum',
|
|
24
|
+
wcag311: 'https://www.w3.org/TR/WCAG22/#language-of-page',
|
|
25
|
+
wcag312: 'https://www.w3.org/TR/WCAG22/#language-of-parts',
|
|
26
|
+
wcag315: 'https://www.w3.org/TR/WCAG22/#reading-level',
|
|
27
|
+
wcag325: 'https://www.w3.org/TR/WCAG22/#change-on-request',
|
|
28
|
+
wcag332: 'https://www.w3.org/TR/WCAG22/#labels-or-instructions',
|
|
29
|
+
wcag412: 'https://www.w3.org/TR/WCAG22/#name-role-value',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const links = [];
|
|
33
|
+
for (let i = 1; i < conformanceList.length; i++) {
|
|
34
|
+
const [wcagSection, subSection, ...sectionItem] = conformanceList[i].slice(4);
|
|
35
|
+
const formattedConformanceNumber = `${wcagSection}.${subSection}.${sectionItem.join('')}`;
|
|
36
|
+
links.push(
|
|
37
|
+
`<a href="${wcagConformanceUrls[conformanceList[i]]}" target="_blank">WCAG ${formattedConformanceNumber}</a>`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return links.join('  ,   ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function generateItemMessageElement(displayNeedsReview, rawMessage) {
|
|
44
|
+
if (rawMessage.includes('\n\nFix')) {
|
|
45
|
+
rawMessage = rawMessage.replace('\n\nFix', '\n Fix');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const htmlEscapedMessageArray = rawMessage.split('\n ').map(m => htmlEscapeString(m));
|
|
49
|
+
|
|
50
|
+
if (displayNeedsReview) {
|
|
51
|
+
if (htmlEscapedMessageArray.length === 1) {
|
|
52
|
+
return `<p class="mb-0">${htmlEscapedMessageArray[0]}</p>`;
|
|
53
|
+
} else {
|
|
54
|
+
return `<ul>${htmlEscapedMessageArray.map(m => `<li>${m}</li>`).join('')}</ul>`;
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
let i = 0;
|
|
58
|
+
const elements = [];
|
|
59
|
+
while (i < htmlEscapedMessageArray.length) {
|
|
60
|
+
if (htmlEscapedMessageArray[i].startsWith('Fix ')) {
|
|
61
|
+
elements.push(`<p class="mb-0">${htmlEscapedMessageArray[i]}</p>`);
|
|
62
|
+
i++;
|
|
63
|
+
} else {
|
|
64
|
+
const fixesList = [];
|
|
65
|
+
while (
|
|
66
|
+
i < htmlEscapedMessageArray.length &&
|
|
67
|
+
!htmlEscapedMessageArray[i].startsWith('Fix a')
|
|
68
|
+
) {
|
|
69
|
+
fixesList.push(`<li>${htmlEscapedMessageArray[i]}</li>`);
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
72
|
+
elements.push(`<ul>${fixesList.join('')}</ul>`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return elements.join('');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generate AI-powered fix suggestions using wcag-eval API
|
|
81
|
+
const generateGenAiSuggestFix = async (ruleId, accordionDiv, html, buttonDiv, errorDiv) => {
|
|
82
|
+
console.log('Gen AI Suggest Fix called:', { ruleId, accordionDiv, html, buttonDiv, errorDiv });
|
|
83
|
+
|
|
84
|
+
const SUPPORTED_RULES = ['color-contrast', 'oobee-accessible-label', 'image-alt', 'listitem', 'link-in-text-block', 'target-size'];
|
|
85
|
+
if (!SUPPORTED_RULES.includes(ruleId)) {
|
|
86
|
+
console.warn(`Gen AI Suggest Fix not yet available for ${ruleId}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Update button state
|
|
91
|
+
const button = document.getElementById(buttonDiv);
|
|
92
|
+
if (!button) {
|
|
93
|
+
console.error('Gen AI button not found:', buttonDiv);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
button.disabled = true;
|
|
98
|
+
button.textContent = 'Generating Fix...';
|
|
99
|
+
|
|
100
|
+
// Clear previous errors
|
|
101
|
+
const errorContainer = document.getElementById(errorDiv);
|
|
102
|
+
if (errorContainer) {
|
|
103
|
+
errorContainer.innerHTML = '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
let violationContext = null;
|
|
108
|
+
let elementContext = null;
|
|
109
|
+
|
|
110
|
+
// Attempt to get violation context from embedded JSON data
|
|
111
|
+
if (window.oobeeJsonContextParser && window.oobeeJsonContextParser.jsonData) {
|
|
112
|
+
violationContext = window.oobeeJsonContextParser.getViolationContextByHtml(html, ruleId);
|
|
113
|
+
|
|
114
|
+
if (violationContext) {
|
|
115
|
+
console.log('Found violation context from embedded JSON:', violationContext);
|
|
116
|
+
} else {
|
|
117
|
+
console.warn('No matching violation found in embedded JSON data, will extract context from DOM');
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.warn('Embedded JSON context parser not available, will extract context from DOM');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Extract element context from DOM if embedded JSON data is not available
|
|
124
|
+
if (!violationContext || !violationContext.colorData) {
|
|
125
|
+
console.log('Extracting element context from DOM as fallback');
|
|
126
|
+
elementContext = extractElementContext(html);
|
|
127
|
+
} else {
|
|
128
|
+
console.log('Using embedded JSON violation context, skipping DOM extraction');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Build scanner findings from embedded JSON data or fallback to extracted context
|
|
132
|
+
let scannerFindings;
|
|
133
|
+
if (violationContext && violationContext.colorData) {
|
|
134
|
+
const colorData = violationContext.colorData;
|
|
135
|
+
console.log('Using color data from embedded JSON:', {
|
|
136
|
+
foreground: colorData.foreground,
|
|
137
|
+
background: colorData.background,
|
|
138
|
+
currentRatio: colorData.currentRatio,
|
|
139
|
+
requiredRatio: colorData.requiredRatio,
|
|
140
|
+
fontSize: colorData.fontSize,
|
|
141
|
+
fontWeight: colorData.fontWeight
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
scannerFindings = {
|
|
145
|
+
foreground: colorData.foreground || '#000000',
|
|
146
|
+
background: colorData.background || '#ffffff',
|
|
147
|
+
font_size_px: colorData.fontSize || 16,
|
|
148
|
+
font_weight: colorData.fontWeight || '400',
|
|
149
|
+
contrast_ratio: colorData.currentRatio || 1.0,
|
|
150
|
+
required_ratio: colorData.requiredRatio || 4.5,
|
|
151
|
+
actual_html: violationContext.html,
|
|
152
|
+
xpath_selector: violationContext.xpath
|
|
153
|
+
};
|
|
154
|
+
console.log('Using embedded JSON-based scanner findings with contrast ratio:', scannerFindings.contrast_ratio);
|
|
155
|
+
} else {
|
|
156
|
+
scannerFindings = {
|
|
157
|
+
foreground: elementContext?.foreground || '#000000',
|
|
158
|
+
background: elementContext?.background || '#ffffff',
|
|
159
|
+
font_size_px: elementContext?.fontSize || 16,
|
|
160
|
+
font_weight: elementContext?.fontWeight || '400',
|
|
161
|
+
contrast_ratio: elementContext?.contrastRatio || 1.0,
|
|
162
|
+
required_ratio: 4.5
|
|
163
|
+
};
|
|
164
|
+
console.log('Using fallback scanner findings:', scannerFindings);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const violationTypeMap = {
|
|
168
|
+
'color-contrast': 'color-contrast',
|
|
169
|
+
'oobee-accessible-label': 'accessible-label',
|
|
170
|
+
'image-alt': 'image-alt',
|
|
171
|
+
'listitem': 'listitem',
|
|
172
|
+
'target-size': 'target-size'
|
|
173
|
+
};
|
|
174
|
+
let violationType = violationTypeMap[ruleId] || ruleId;
|
|
175
|
+
|
|
176
|
+
// Prepare payload for wcag-eval API
|
|
177
|
+
const apiPayload = {
|
|
178
|
+
violationType: violationType,
|
|
179
|
+
html: html,
|
|
180
|
+
css: elementContext?.css || '',
|
|
181
|
+
violationContext: violationContext ? {
|
|
182
|
+
severity: violationContext.severity,
|
|
183
|
+
issue_description: violationContext.issueDescription,
|
|
184
|
+
wcag_conformance: violationContext.wcagConformance,
|
|
185
|
+
page_title: violationContext.pageTitle,
|
|
186
|
+
page_url: violationContext.url,
|
|
187
|
+
how_to_fix: violationContext.howToFix,
|
|
188
|
+
axe_impact: violationContext.axeImpact,
|
|
189
|
+
learn_more: violationContext.learnMore
|
|
190
|
+
} : {},
|
|
191
|
+
context: {
|
|
192
|
+
rule_id: ruleId,
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
data_source: violationContext ? 'json_scan_results' : 'dom_extraction'
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Add scannerFindings for color-contrast violations
|
|
199
|
+
if (violationType === 'color-contrast') {
|
|
200
|
+
apiPayload.scannerFindings = scannerFindings;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log('Sending request to wcag-eval API:', apiPayload);
|
|
204
|
+
|
|
205
|
+
// Call wcag-eval API (now in oobee-web-proxy)
|
|
206
|
+
const wcagEvalApiUrl = `${proxyUrl}/api/ai/generate-fix` || 'http://localhost:3002/api/ai/generate-fix';
|
|
207
|
+
const response = await fetch(wcagEvalApiUrl, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: {
|
|
210
|
+
'Content-Type': 'application/json',
|
|
211
|
+
'Accept': 'application/json'
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify(apiPayload)
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const apiResponse = await response.json();
|
|
221
|
+
console.log('Received response from wcag-eval API:', apiResponse);
|
|
222
|
+
|
|
223
|
+
if (!apiResponse.success) {
|
|
224
|
+
throw new Error(apiResponse.error || 'API returned unsuccessful response');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const fixSuggestion = apiResponse.fixSuggestion;
|
|
228
|
+
console.log('Fix suggestion data:', fixSuggestion);
|
|
229
|
+
console.log('Code fixes array:', fixSuggestion?.codeFixes);
|
|
230
|
+
|
|
231
|
+
// Format the AI response for display
|
|
232
|
+
const formattedResponse = formatFixSuggestionResponse(fixSuggestion);
|
|
233
|
+
|
|
234
|
+
// Display the generated fix
|
|
235
|
+
const responseContainer = document.getElementById(accordionDiv);
|
|
236
|
+
if (responseContainer) {
|
|
237
|
+
responseContainer.innerHTML = formattedResponse;
|
|
238
|
+
|
|
239
|
+
// Highlight code blocks
|
|
240
|
+
responseContainer.querySelectorAll('.codeForAiResponse').forEach(el => {
|
|
241
|
+
if (typeof hljs !== 'undefined') {
|
|
242
|
+
hljs.highlightElement(el);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Reset button to regenerate state
|
|
248
|
+
button.disabled = false;
|
|
249
|
+
button.style = 'border: 1px solid var(--oobee-blue-100); color: var(--oobee-blue-100);';
|
|
250
|
+
button.textContent = 'Regenerate Fix';
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error('Error in Gen AI fix generation:', error);
|
|
254
|
+
|
|
255
|
+
// Reset button to initial state
|
|
256
|
+
button.disabled = false;
|
|
257
|
+
button.style = 'border: 1px solid var(--oobee-blue-100); color: var(--oobee-blue-100);';
|
|
258
|
+
button.textContent = 'Gen AI Suggest Fix';
|
|
259
|
+
|
|
260
|
+
if (errorContainer) {
|
|
261
|
+
const isConnectionError = error.message.includes('fetch') || error.message.includes('Failed to fetch');
|
|
262
|
+
const errorMessage = isConnectionError
|
|
263
|
+
? 'Unable to connect to AI service. Please ensure the wcag-eval API is running on port 5000.'
|
|
264
|
+
: `Failed to generate fix suggestion: ${error.message}`;
|
|
265
|
+
|
|
266
|
+
errorContainer.innerHTML = `<div class="generateAiError">${errorMessage}</div>`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Helper function to extract element context from HTML/CSS
|
|
272
|
+
function extractElementContext(html) {
|
|
273
|
+
const context = {
|
|
274
|
+
css: '',
|
|
275
|
+
foreground: '#000000',
|
|
276
|
+
background: '#ffffff',
|
|
277
|
+
fontSize: 16,
|
|
278
|
+
fontWeight: '400',
|
|
279
|
+
contrastRatio: 1.0
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const tempDiv = document.createElement('div');
|
|
284
|
+
tempDiv.innerHTML = html;
|
|
285
|
+
const element = tempDiv.querySelector('*') || tempDiv;
|
|
286
|
+
|
|
287
|
+
if (element && element.parentNode) {
|
|
288
|
+
document.body.appendChild(tempDiv);
|
|
289
|
+
const computedStyle = window.getComputedStyle(element);
|
|
290
|
+
|
|
291
|
+
context.foreground = rgbToHex(computedStyle.color) || context.foreground;
|
|
292
|
+
context.background = rgbToHex(computedStyle.backgroundColor) || context.background;
|
|
293
|
+
context.fontSize = parseFloat(computedStyle.fontSize) || context.fontSize;
|
|
294
|
+
context.fontWeight = computedStyle.fontWeight || context.fontWeight;
|
|
295
|
+
|
|
296
|
+
if (element.style && element.style.cssText) {
|
|
297
|
+
context.css = element.style.cssText;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
document.body.removeChild(tempDiv);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (context.foreground && context.background) {
|
|
304
|
+
context.contrastRatio = calculateContrastRatio(context.foreground, context.background);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.warn('Could not extract element context:', error);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return context;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Format fix suggestion response for display
|
|
315
|
+
function formatFixSuggestionResponse(fixSuggestion) {
|
|
316
|
+
// Support both snake_case (Python) and camelCase (TypeScript) responses
|
|
317
|
+
const contrastAnalysis = fixSuggestion.contrast_analysis || fixSuggestion.contrastAnalysis || {};
|
|
318
|
+
const targetSizeAnalysis = fixSuggestion.target_size_analysis || fixSuggestion.targetSizeAnalysis || {};
|
|
319
|
+
const plainExplanation = fixSuggestion.why_it_matters || fixSuggestion.explanation || 'This fix improves accessibility for users with visual impairments.';
|
|
320
|
+
const diagnosis = fixSuggestion.diagnosis || fixSuggestion.explanation || plainExplanation;
|
|
321
|
+
|
|
322
|
+
// Escape HTML for safe rendering
|
|
323
|
+
const escapeHtml = (text) => String(text)
|
|
324
|
+
.replace(/&/g, '&')
|
|
325
|
+
.replace(/</g, '<')
|
|
326
|
+
.replace(/>/g, '>')
|
|
327
|
+
.replace(/"/g, '"')
|
|
328
|
+
.replace(/'/g, ''');
|
|
329
|
+
|
|
330
|
+
// Build code fixes section
|
|
331
|
+
let codeFixesHtml = '';
|
|
332
|
+
|
|
333
|
+
// Handle TypeScript format (css/html directly in response)
|
|
334
|
+
if (fixSuggestion.css || fixSuggestion.html) {
|
|
335
|
+
const mainFix = [];
|
|
336
|
+
if (fixSuggestion.css) {
|
|
337
|
+
mainFix.push(`
|
|
338
|
+
<div style="margin-bottom: 16px;">
|
|
339
|
+
<p style="font-size: 13px; margin-bottom: 8px; color: #666; font-weight: 500;">Recommended CSS Fix:</p>
|
|
340
|
+
<code class="codeForAiResponse language-css hljs" style="display: block; padding: 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; border: 1px solid #e0e0e0; white-space: pre-wrap; word-break: break-all;">${escapeHtml(fixSuggestion.css)}</code>
|
|
341
|
+
</div>
|
|
342
|
+
`);
|
|
343
|
+
}
|
|
344
|
+
if (fixSuggestion.html) {
|
|
345
|
+
mainFix.push(`
|
|
346
|
+
<div style="margin-bottom: 16px;">
|
|
347
|
+
<p style="font-size: 13px; margin-bottom: 8px; color: #666; font-weight: 500;">Recommended HTML Fix:</p>
|
|
348
|
+
<code class="codeForAiResponse language-html hljs" style="display: block; padding: 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; border: 1px solid #e0e0e0; white-space: pre-wrap; word-break: break-all;">${escapeHtml(fixSuggestion.html)}</code>
|
|
349
|
+
</div>
|
|
350
|
+
`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Handle alternatives
|
|
354
|
+
if (fixSuggestion.alternatives && Array.isArray(fixSuggestion.alternatives) && fixSuggestion.alternatives.length > 0) {
|
|
355
|
+
const alternatives = fixSuggestion.alternatives.map((alt, index) => {
|
|
356
|
+
const altCode = alt.css || alt.html || '';
|
|
357
|
+
return `
|
|
358
|
+
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #e0e0e0;">
|
|
359
|
+
<p style="font-size: 13px; margin-bottom: 8px; color: #666; font-weight: 500;">Alternative ${index + 1}: ${escapeHtml(alt.explanation || '')}</p>
|
|
360
|
+
<code class="codeForAiResponse language-${alt.css ? 'css' : 'html'} hljs" style="display: block; padding: 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; border: 1px solid #e0e0e0; white-space: pre-wrap; word-break: break-all;">${escapeHtml(altCode)}</code>
|
|
361
|
+
</div>
|
|
362
|
+
`;
|
|
363
|
+
}).join('');
|
|
364
|
+
mainFix.push(alternatives);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
codeFixesHtml = `
|
|
368
|
+
<div style="margin-bottom: 16px;">
|
|
369
|
+
<h4 style="font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #1a1a1a;">Code Fix:</h4>
|
|
370
|
+
${mainFix.join('')}
|
|
371
|
+
</div>
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
// Handle Python format (code_fixes array)
|
|
375
|
+
else if (fixSuggestion.code_fixes && Array.isArray(fixSuggestion.code_fixes) && fixSuggestion.code_fixes.length > 0) {
|
|
376
|
+
const codeFixes = fixSuggestion.code_fixes.map((fix, index) => {
|
|
377
|
+
const language = fix.language || 'html';
|
|
378
|
+
const description = fix.description || `Option ${index + 1}`;
|
|
379
|
+
const code = fix.after || fix.before || '';
|
|
380
|
+
const marginBottom = index === fixSuggestion.code_fixes.length - 1 ? '0' : '20px';
|
|
381
|
+
|
|
382
|
+
return `
|
|
383
|
+
<div style="margin-bottom: ${marginBottom};">
|
|
384
|
+
<p style="font-size: 13px; margin-bottom: 8px; color: #666; font-weight: 500;">${escapeHtml(description)}</p>
|
|
385
|
+
<code class="codeForAiResponse language-${escapeHtml(language)} hljs" style="display: block; padding: 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; border: 1px solid #e0e0e0; white-space: pre-wrap; word-break: break-all;">${escapeHtml(code)}</code>
|
|
386
|
+
</div>`;
|
|
387
|
+
}).join('');
|
|
388
|
+
|
|
389
|
+
codeFixesHtml = `
|
|
390
|
+
<div style="margin-bottom: 16px;">
|
|
391
|
+
<h4 style="font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #1a1a1a;">Code Fix${fixSuggestion.code_fixes.length > 1 ? 'es' : ''}:</h4>
|
|
392
|
+
${codeFixes}
|
|
393
|
+
</div>
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return `
|
|
398
|
+
<div class="genai-response-card">
|
|
399
|
+
<div style="margin-bottom: 24px;">
|
|
400
|
+
<h4 style="font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #1a1a1a;">What's the problem?</h4>
|
|
401
|
+
<p style="font-size: 14px; line-height: 1.6; margin-bottom: 16px; color: #333;">
|
|
402
|
+
${escapeHtml(diagnosis)}
|
|
403
|
+
</p>
|
|
404
|
+
|
|
405
|
+
${contrastAnalysis.current_ratio ? `
|
|
406
|
+
<div style="background: #FAF8FD; padding: 12px; border-radius: 6px; margin-bottom: 16px;">
|
|
407
|
+
<p style="margin: 0; font-size: 14px; line-height: 1.6; color: #333;">
|
|
408
|
+
The current text has a contrast ratio of <strong>${contrastAnalysis.current_ratio}:1</strong>,
|
|
409
|
+
but it needs at least <strong>${contrastAnalysis.required_ratio}:1</strong> to meet accessibility standards.
|
|
410
|
+
${contrastAnalysis.passes_aa
|
|
411
|
+
? `The suggested fix achieves <strong>${contrastAnalysis.proposed_ratio}:1</strong>, which passes WCAG AA requirements.`
|
|
412
|
+
: `The suggested fix improves it to <strong>${contrastAnalysis.proposed_ratio}:1</strong>.`}
|
|
413
|
+
</p>
|
|
414
|
+
</div>
|
|
415
|
+
` : ''}
|
|
416
|
+
|
|
417
|
+
${targetSizeAnalysis && (targetSizeAnalysis.estimated_width !== undefined || targetSizeAnalysis.estimated_height !== undefined || targetSizeAnalysis.notes) ? `
|
|
418
|
+
<div style="background: #F5FAFF; padding: 12px; border-radius: 6px; margin-bottom: 16px; border: 1px solid #e0efff;">
|
|
419
|
+
<p style="margin: 0; font-size: 14px; line-height: 1.6; color: #333;">
|
|
420
|
+
${targetSizeAnalysis.meets_minimum === false
|
|
421
|
+
? 'Estimated hit area is below the WCAG 2.5.8 minimum of 24x24 CSS pixels.'
|
|
422
|
+
: 'Target size analysis for this control:'}
|
|
423
|
+
${targetSizeAnalysis.estimated_width !== undefined && targetSizeAnalysis.estimated_width !== null
|
|
424
|
+
? ` Estimated width: <strong>${targetSizeAnalysis.estimated_width}${typeof targetSizeAnalysis.estimated_width === 'number' ? 'px' : ''}</strong>.`
|
|
425
|
+
: ''}
|
|
426
|
+
${targetSizeAnalysis.estimated_height !== undefined && targetSizeAnalysis.estimated_height !== null
|
|
427
|
+
? ` Estimated height: <strong>${targetSizeAnalysis.estimated_height}${typeof targetSizeAnalysis.estimated_height === 'number' ? 'px' : ''}</strong>.`
|
|
428
|
+
: ''}
|
|
429
|
+
${targetSizeAnalysis.notes ? ` ${escapeHtml(targetSizeAnalysis.notes)}` : ''}
|
|
430
|
+
</p>
|
|
431
|
+
</div>
|
|
432
|
+
` : ''}
|
|
433
|
+
|
|
434
|
+
${fixSuggestion.fix_steps && fixSuggestion.fix_steps.length > 0 ? `
|
|
435
|
+
<div style="margin-top: 16px;">
|
|
436
|
+
<h5 style="font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #1a1a1a;">How to fix it:</h5>
|
|
437
|
+
<ol style="margin: 0; padding-left: 20px; font-size: 14px; line-height: 1.8; color: #333;">
|
|
438
|
+
${fixSuggestion.fix_steps.map(step => `<li style="word-wrap: break-word; overflow-wrap: break-word;">${escapeHtml(step)}</li>`).join('')}
|
|
439
|
+
</ol>
|
|
440
|
+
</div>
|
|
441
|
+
` : ''}
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
${codeFixesHtml}
|
|
445
|
+
</div>
|
|
446
|
+
`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Convert RGB color to hex format
|
|
450
|
+
function rgbToHex(rgb) {
|
|
451
|
+
if (!rgb || rgb === 'transparent' || rgb === 'inherit') return null;
|
|
452
|
+
|
|
453
|
+
const result = rgb.match(/\d+/g);
|
|
454
|
+
if (!result || result.length < 3) return null;
|
|
455
|
+
|
|
456
|
+
const r = parseInt(result[0]);
|
|
457
|
+
const g = parseInt(result[1]);
|
|
458
|
+
const b = parseInt(result[2]);
|
|
459
|
+
|
|
460
|
+
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Calculate WCAG contrast ratio between two colors
|
|
464
|
+
function calculateContrastRatio(color1, color2) {
|
|
465
|
+
try {
|
|
466
|
+
const getLuminance = (hex) => {
|
|
467
|
+
const rgb = hex.match(/\w\w/g).map(h => parseInt(h, 16) / 255);
|
|
468
|
+
const [r, g, b] = rgb.map(c => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
469
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const l1 = getLuminance(color1.replace('#', ''));
|
|
473
|
+
const l2 = getLuminance(color2.replace('#', ''));
|
|
474
|
+
const lightest = Math.max(l1, l2);
|
|
475
|
+
const darkest = Math.min(l1, l2);
|
|
476
|
+
|
|
477
|
+
return Math.round(((lightest + 0.05) / (darkest + 0.05)) * 100) / 100;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.warn('Could not calculate contrast ratio:', error);
|
|
480
|
+
return 1.0;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
document.addEventListener('click', function (e) {
|
|
3
|
+
const btn = e.target.closest('.seg-pill');
|
|
4
|
+
if (!btn) return;
|
|
5
|
+
|
|
6
|
+
const container = btn.closest('.segmented-tabs');
|
|
7
|
+
const targetSel = btn.getAttribute('data-tab-target');
|
|
8
|
+
const panel = document.querySelector(targetSel);
|
|
9
|
+
if (!panel) return;
|
|
10
|
+
|
|
11
|
+
container.querySelectorAll('.seg-pill').forEach((p) =>
|
|
12
|
+
p.setAttribute('aria-selected', 'false')
|
|
13
|
+
);
|
|
14
|
+
btn.setAttribute('aria-selected', 'true');
|
|
15
|
+
|
|
16
|
+
document
|
|
17
|
+
.querySelectorAll('.seg-panels > [role="tabpanel"]')
|
|
18
|
+
.forEach((p) => (p.hidden = true));
|
|
19
|
+
panel.hidden = false;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
23
|
+
const pairs = [
|
|
24
|
+
['#seg-not-scanned', '#totalPagesNotScannedLabel'],
|
|
25
|
+
['#seg-unsupported', '#totalUnsupportedDocsLabel']
|
|
26
|
+
];
|
|
27
|
+
pairs.forEach(([btnSel, countSel]) => {
|
|
28
|
+
const btn = document.querySelector(btnSel);
|
|
29
|
+
const countEl = document.querySelector(countSel);
|
|
30
|
+
if (btn && countEl && countEl.textContent.trim() === '0') {
|
|
31
|
+
btn.style.display = 'none';
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<%# functions used to show lightbox of screenshot when thumbnail is clicked on %>
|
|
2
|
+
<script>
|
|
3
|
+
const lightbox = document.getElementsByClassName('screenshot-lightbox')[0];
|
|
4
|
+
const lightboxHeader = document.getElementsByClassName('lightbox-header')[0];
|
|
5
|
+
const lightboxTitle = document.querySelector('.lightbox-header h5');
|
|
6
|
+
const lightboxContent = document.getElementsByClassName('lightbox-content')[0];
|
|
7
|
+
const lightboxImg = document.getElementById('lightbox-image');
|
|
8
|
+
|
|
9
|
+
var customFlowScreenshots = document.getElementsByClassName('custom-flow-screenshot');
|
|
10
|
+
Array.from(customFlowScreenshots).forEach(screenshot => {
|
|
11
|
+
screenshot.onerror = function (event) {
|
|
12
|
+
screenshot.onerror = null;
|
|
13
|
+
screenshot.remove();
|
|
14
|
+
};
|
|
15
|
+
screenshot.onclick = event => {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
const pageTitle = screenshot.parentNode.getElementsByTagName('a')[0].textContent;
|
|
18
|
+
const pageUrl = screenshot.parentNode.getElementsByTagName('a')[0].href;
|
|
19
|
+
openLightbox(screenshot.src, pageTitle, pageUrl);
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
lightbox.addEventListener('click', event => {
|
|
24
|
+
if (
|
|
25
|
+
event.target === lightbox ||
|
|
26
|
+
event.target === lightboxHeader ||
|
|
27
|
+
event.target === lightboxTitle
|
|
28
|
+
) {
|
|
29
|
+
closeLightbox();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const offcanvasElem = document.getElementsByClassName('offcanvas')[0];
|
|
34
|
+
const offcanvasItem = new bootstrap.Offcanvas(offcanvasElem);
|
|
35
|
+
offcanvasItem._config.keyboard = false; // Disable default keyboard handling
|
|
36
|
+
|
|
37
|
+
const pagesScannedModalElem = document.getElementById('pagesScannedModal');
|
|
38
|
+
const pagesScannedModalItem = new bootstrap.Modal(pagesScannedModalElem);
|
|
39
|
+
pagesScannedModalItem._config.keyboard = false;
|
|
40
|
+
|
|
41
|
+
document.addEventListener('keydown', event => {
|
|
42
|
+
if (event.key === 'Escape') {
|
|
43
|
+
if (offcanvasItem._isShown) {
|
|
44
|
+
if (lightbox.style.display === 'block') {
|
|
45
|
+
event.preventDefault(); // Prevent default bootstrap behaviour
|
|
46
|
+
closeLightbox();
|
|
47
|
+
} else {
|
|
48
|
+
offcanvasItem.hide();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (pagesScannedModalItem._isShown) {
|
|
53
|
+
if (lightbox.style.display === 'block') {
|
|
54
|
+
event.preventDefault(); // Prevent default bootstrap behaviour
|
|
55
|
+
closeLightbox();
|
|
56
|
+
} else {
|
|
57
|
+
pagesScannedModalItem.hide();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function openLightbox(imgSrc, pageTitle, pageUrl) {
|
|
64
|
+
lightbox.style.display = 'block';
|
|
65
|
+
|
|
66
|
+
lightboxImg.src = imgSrc;
|
|
67
|
+
lightboxImg.alt = `Screenshot of ${pageUrl}`;
|
|
68
|
+
|
|
69
|
+
lightboxTitle.textContent = pageTitle;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function closeLightbox() {
|
|
73
|
+
lightbox.style.display = 'none';
|
|
74
|
+
};
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
document.getElementById('summarymustFixIcon').src =
|
|
3
|
+
"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.1429 0H2.85714C1.27919 0 0 1.27919 0 2.85714V17.1429C0 18.7208 1.27919 20 2.85714 20H17.1429C18.7208 20 20 18.7208 20 17.1429V2.85714C20 1.27919 18.7208 0 17.1429 0Z' fill='%23f26949'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.4292 9.99822V5.71251C11.4292 4.92353 10.7896 4.28394 10.0006 4.28394C9.21161 4.28394 8.57202 4.92353 8.57202 5.71251V9.99822C8.57202 10.7872 9.21161 11.4268 10.0006 11.4268C10.7896 11.4268 11.4292 10.7872 11.4292 9.99822ZM10.0006 12.8554C9.21202 12.8554 8.57202 13.4954 8.57202 14.2839C8.57202 15.0725 9.21202 15.7125 10.0006 15.7125C10.7892 15.7125 11.4292 15.0725 11.4292 14.2839C11.4292 13.4954 10.7892 12.8554 10.0006 12.8554Z' fill='white'/%3E%3C/svg%3E%0A";
|
|
4
|
+
document.getElementById('summarymustFixIcon').alt = 'must fix icon';
|
|
5
|
+
document.getElementById('summarygoodToFixIcon').src =
|
|
6
|
+
"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.1429 0H2.85714C1.27919 0 0 1.27919 0 2.85714V17.1429C0 18.7208 1.27919 20 2.85714 20H17.1429C18.7208 20 20 18.7208 20 17.1429V2.85714C20 1.27919 18.7208 0 17.1429 0Z' fill='%23ffb200'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10 6.5625C8.10131 6.5625 6.5625 8.10131 6.5625 10C6.5625 11.8987 8.10131 13.4375 10 13.4375C11.8987 13.4375 13.4375 11.8987 13.4375 10C13.4375 8.10131 11.8987 6.5625 10 6.5625ZM4.84375 10C4.84375 7.15207 7.15207 4.84375 10 4.84375C12.8479 4.84375 15.1562 7.15207 15.1562 10C15.1562 12.8479 12.8479 15.1562 10 15.1562C7.15207 15.1562 4.84375 12.8479 4.84375 10Z' fill='white'/%3E%3C/svg%3E%0A";
|
|
7
|
+
document.getElementById('summarygoodToFixIcon').alt = 'good to fix icon';
|
|
8
|
+
document.getElementById('summaryneedsReviewIcon').src =
|
|
9
|
+
"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.1429 0H2.85714C1.27919 0 0 1.27919 0 2.85714V17.1429C0 18.7208 1.27919 20 2.85714 20H17.1429C18.7208 20 20 18.7208 20 17.1429V2.85714C20 1.27919 18.7208 0 17.1429 0Z' fill='%23c9c8c6'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.09759 7.26594C8.23445 6.67496 8.46462 6.23328 8.7881 5.9409C9.14891 5.65474 9.57815 5.51166 10.0758 5.51166C10.5984 5.51166 11.0152 5.64852 11.3262 5.92224C11.6372 6.19596 11.7928 6.53188 11.7928 6.93002C11.7928 7.22862 11.7057 7.48367 11.5315 7.69518C11.4195 7.84448 11.0712 8.15552 10.4864 8.6283C9.90785 9.11353 9.51594 9.54277 9.31065 9.91602C9.12403 10.3079 9.03072 10.7745 9.03072 11.3157V11.7449L9.04938 11.8569C9.04938 11.9316 9.06026 12 9.08204 12.0622C9.10381 12.1244 9.12558 12.1742 9.14736 12.2115C9.16913 12.2488 9.20645 12.2815 9.25933 12.3095C9.31221 12.3375 9.35264 12.3593 9.38064 12.3748C9.40863 12.3904 9.45995 12.4012 9.5346 12.4075C9.60925 12.4137 9.66058 12.4168 9.68857 12.4168H9.85187H10.0012C10.2065 12.4168 10.3589 12.409 10.4584 12.3935C10.5579 12.3779 10.6466 12.3313 10.7243 12.2535C10.8021 12.1757 10.841 12.056 10.841 11.8942C10.841 11.6827 10.8534 11.465 10.8783 11.2411C10.897 11.0109 10.9343 10.8305 10.9903 10.6998C11.0712 10.5008 11.3324 10.2208 11.7741 9.86003C12.6077 9.15707 13.1427 8.61587 13.3791 8.23639C13.6279 7.83204 13.7523 7.41524 13.7523 6.986C13.7523 6.17729 13.4102 5.48678 12.7259 4.91446C12.0603 4.30482 11.1458 4 9.9825 4C8.88763 4 8.00428 4.2986 7.33243 4.8958C6.71656 5.41835 6.36198 6.07154 6.26866 6.85537C6.25622 6.89269 6.25 6.9549 6.25 7.04199C6.25 7.29705 6.3402 7.51633 6.52061 7.69984C6.70101 7.88336 6.91563 7.97512 7.16446 7.97512C7.37597 7.97512 7.56571 7.90824 7.73367 7.77449C7.90163 7.64075 8.0105 7.47123 8.06026 7.26594H8.09759ZM10.0012 13.2939C9.62791 13.2939 9.30754 13.4246 9.04005 13.6858C8.77255 13.9471 8.6388 14.2644 8.6388 14.6376C8.6388 15.0047 8.77255 15.3235 9.04005 15.5941C9.30754 15.8647 9.62791 16 10.0012 16C10.3744 16 10.6948 15.8647 10.9623 15.5941C11.2298 15.3235 11.3635 15.0047 11.3635 14.6376C11.3635 14.2644 11.2298 13.9471 10.9623 13.6858C10.6948 13.4246 10.3744 13.2939 10.0012 13.2939Z' fill='white'/%3E%3C/svg%3E%0A";
|
|
10
|
+
document.getElementById('summaryneedsReviewIcon').alt = 'needs review icon';
|
|
11
|
+
document.getElementById('summarypassedIcon').src =
|
|
12
|
+
"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.1429 0H2.85714C1.27919 0 0 1.27919 0 2.85714V17.1429C0 18.7208 1.27919 20 2.85714 20H17.1429C18.7208 20 20 18.7208 20 17.1429V2.85714C20 1.27919 18.7208 0 17.1429 0Z' fill='%233aa566'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15.1781 5.3131C15.7942 5.80597 15.8941 6.70496 15.4012 7.32105L9.68694 14.4639C9.19407 15.08 8.29508 15.1799 7.67899 14.687C7.0629 14.1941 6.96301 13.2951 7.45589 12.6791L13.1702 5.5362C13.663 4.92011 14.562 4.82023 15.1781 5.3131Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4.70415 9.70455C5.26204 9.14666 6.16656 9.14666 6.72446 9.70455L9.5816 12.5617C10.1395 13.1196 10.1395 14.0241 9.5816 14.582C9.02371 15.1399 8.11919 15.1399 7.56129 14.582L4.70415 11.7249C4.14626 11.167 4.14626 10.2624 4.70415 9.70455Z' fill='white'/%3E%3C/svg%3E%0A";
|
|
13
|
+
document.getElementById('summarypassedIcon').alt = 'passed icon';
|
|
14
|
+
</script>
|