Scans KEX SOP pages for post-publication issues (broken links, missing images, leftover Watson refs, encoding problems)
// ==UserScript==
// @name KEX Page Checker
// @namespace https://knowledge.sps.amazon.dev
// @version 1.0.0
// @description Scans KEX SOP pages for post-publication issues (broken links, missing images, leftover Watson refs, encoding problems)
// @match https://knowledge.sps.amazon.dev/k?id=*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
"use strict";
// Wait for page content to load
function waitForContent() {
var check = function () {
if (document.querySelector(".WatsonSOPBody") || document.querySelector("h1") || document.querySelector("h2")) {
setTimeout(createUI, 1500); // extra delay for images to load
} else {
setTimeout(check, 1000);
}
};
check();
}
function createUI() {
// Create floating button
var btn = document.createElement("button");
btn.id = "kex-checker-btn";
btn.textContent = "Check Page";
btn.style.cssText = "position:fixed;bottom:20px;right:20px;z-index:999998;background:#232F3E;color:#FF6200;border:none;border-radius:8px;padding:10px 18px;font-size:14px;font-weight:bold;cursor:pointer;box-shadow:0 2px 10px rgba(0,0,0,0.3);font-family:Arial,sans-serif;";
btn.addEventListener("click", runChecker);
document.body.appendChild(btn);
}
function runChecker() {
var results = [];
var highlights = [];
// Clean up previous run
var oldStyle = document.getElementById("kex-checker-style");
if (oldStyle) oldStyle.remove();
var oldPanel = document.getElementById("kex-checker-panel");
if (oldPanel) oldPanel.remove();
document.querySelectorAll(".kex-check-highlight").forEach(function (el) {
el.classList.remove("kex-check-highlight");
el.removeAttribute("data-kex-issue");
});
// Inject styles
var style = document.createElement("style");
style.id = "kex-checker-style";
style.textContent = [
".kex-check-highlight{outline:3px solid red !important;outline-offset:2px;position:relative;}",
".kex-check-highlight::after{content:attr(data-kex-issue);position:absolute;top:-20px;left:0;background:red;color:#fff;font-size:11px;padding:2px 6px;border-radius:3px;white-space:nowrap;z-index:99999;font-family:Arial,sans-serif;}",
"#kex-checker-panel{position:fixed;top:10px;right:10px;width:440px;max-height:80vh;overflow-y:auto;background:#fff;border:2px solid #232F3E;border-radius:10px;box-shadow:0 4px 20px rgba(0,0,0,0.3);z-index:999999;font-family:Arial,sans-serif;font-size:13px;}",
"#kex-checker-panel .chk-header{background:#232F3E;color:#fff;padding:12px 16px;border-radius:8px 8px 0 0;display:flex;justify-content:space-between;align-items:center;}",
"#kex-checker-panel .chk-header h3{margin:0;font-size:15px;color:#FF6200;}",
"#kex-checker-panel .close-btn{cursor:pointer;font-size:18px;color:#fff;font-weight:bold;}",
"#kex-checker-panel .chk-body{padding:12px 16px;}",
"#kex-checker-panel .section{margin-bottom:12px;}",
"#kex-checker-panel .section h4{margin:0 0 6px;font-size:13px;color:#232F3E;}",
"#kex-checker-panel .pass{color:#27ae60;font-weight:bold;}",
"#kex-checker-panel .fail{color:#c0392b;font-weight:bold;}",
"#kex-checker-panel .issue{padding:4px 0;border-bottom:1px solid #f0f0f0;cursor:pointer;font-size:12px;}",
"#kex-checker-panel .issue:hover{background:#f8f8f8;}",
"#kex-checker-panel .summary{padding:10px;background:#f8f9fa;border-radius:6px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;}",
"#kex-checker-panel .export-btn{padding:6px 14px;background:#232F3E;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:bold;cursor:pointer;font-family:Arial,sans-serif;}",
"#kex-checker-panel .export-btn:hover{background:#37475a;}"
].join("");
document.head.appendChild(style);
function flag(el, msg, cat) {
if (el && el.classList) {
el.classList.add("kex-check-highlight");
el.setAttribute("data-kex-issue", msg);
}
highlights.push(el);
results.push({
cat: cat,
msg: msg,
el: el,
detail: el ? (el.getAttribute("href") || el.getAttribute("src") || el.tagName) : "n/a"
});
}
// ── LINKS ──
var links = document.querySelectorAll("a[href]");
links.forEach(function (a) {
var href = a.getAttribute("href") || "";
var text = (a.textContent || "").trim();
if (href.indexOf("share.amazon.com/sites/amazonwatson") !== -1) {
flag(a, "Watson link still present", "links");
}
if (href.indexOf("REPLACE_WITH_GUID") !== -1) {
flag(a, "GUID placeholder not replaced", "links");
}
if (href.match(/[?&]id=(#|$|&)/) || href.match(/[?&]id=$/)) {
flag(a, "Paragon link with empty GUID", "links");
}
if (!text && !a.querySelector("img")) {
flag(a, "Empty link (no text or image)", "links");
}
if (href === "about:blank" || href === "javascript:void(0)" || href === "javascript:void") {
flag(a, "Dead link (about:blank/void)", "links");
}
if (href.startsWith("#") && href.length > 1) {
var anchor = href.substring(1);
if (anchor.startsWith("help-page-header-")) return;
if (!document.getElementById(anchor) && !document.querySelector('[name="' + CSS.escape(anchor) + '"]')) {
flag(a, "Broken anchor: " + href, "links");
}
}
});
// ── IMAGES ──
var imgs = document.querySelectorAll("img");
imgs.forEach(function (img) {
if (img.complete && img.naturalWidth === 0 && img.src && img.src.indexOf("data:") !== 0) {
flag(img, "Broken image (failed to load)", "images");
}
var src = img.getAttribute("src") || "";
if (src.indexOf("Resources/Images/") !== -1 || src.indexOf("Resources\\Images\\") !== -1) {
flag(img, "Local image path (not Media Central)", "images");
}
if (!img.hasAttribute("alt")) {
flag(img, "Missing alt attribute", "images");
}
});
// ── CSS / JS ──
var inlineStyles = document.querySelectorAll("style");
var leftoverLinks = document.querySelectorAll('link[rel="stylesheet"]');
if (inlineStyles.length > 0) {
results.push({ cat: "css_js", msg: inlineStyles.length + ' inline <style> block(s) found (old embed approach?)', el: null, detail: "" });
}
if (leftoverLinks.length > 0) {
leftoverLinks.forEach(function (l) {
flag(l, "Leftover <link stylesheet> tag", "css_js");
});
}
// ── CONTENT QUALITY ──
var allText = document.body.innerText || "";
var mojibakePatterns = [/Ã[\x80-\xBF]/g, /Â[\x80-\xBF]/g, /â€[^\s]/g, /é/g, /ö/g, /ü/g];
var hasMojibake = false;
mojibakePatterns.forEach(function (p) { if (p.test(allText)) hasMojibake = true; });
if (hasMojibake) {
results.push({ cat: "content", msg: "Possible encoding issues (mojibake characters detected)", el: null, detail: "" });
}
var watsonSource = document.getElementById("WatsonSOPSource");
if (watsonSource && watsonSource.offsetHeight > 0) {
flag(watsonSource, "WatsonSOPSource element visible", "content");
}
var siteBanner = document.getElementById("SiteMovedBanner_MessageBar_Site");
if (siteBanner && siteBanner.offsetHeight > 0) {
flag(siteBanner, "SiteMovedBanner still visible", "content");
}
var madcapConds = document.querySelectorAll("[MadCap\\:conditions]");
if (madcapConds.length > 0) {
madcapConds.forEach(function (el) {
flag(el, "MadCap:conditions attribute in rendered HTML", "content");
});
}
var allIds = document.querySelectorAll("[id]");
var idMap = {};
allIds.forEach(function (el) {
var id = el.id;
if (id) {
if (idMap[id]) { flag(el, "Duplicate ID: " + id, "content"); }
else { idMap[id] = true; }
}
});
var emptyCollapsibles = document.querySelectorAll(".collapsible");
emptyCollapsibles.forEach(function (btn) {
var next = btn.nextElementSibling;
if (next && next.classList.contains("collapsed") && (next.textContent || "").trim().length === 0) {
flag(btn, "Empty collapsible section", "content");
}
});
// ── LAYOUT ──
var bodyWidth = document.body.scrollWidth;
var viewWidth = window.innerWidth;
if (bodyWidth > viewWidth + 5) {
results.push({ cat: "layout", msg: "Content wider than viewport (horizontal scroll: " + bodyWidth + "px vs " + viewWidth + "px)", el: null, detail: "" });
}
imgs.forEach(function (img) {
if (img.naturalWidth > 0 && img.offsetWidth > img.parentElement.offsetWidth + 10) {
flag(img, "Image exceeds container width", "layout");
}
});
// ── BUILD PANEL ──
var cats = { links: "Links", images: "Images", css_js: "CSS / JS", content: "Content Quality", layout: "Layout" };
var catResults = {};
results.forEach(function (r) {
if (!catResults[r.cat]) catResults[r.cat] = [];
catResults[r.cat].push(r);
});
var totalIssues = results.length;
var panel = document.createElement("div");
panel.id = "kex-checker-panel";
var html = '<div class="chk-header"><h3>KEX Page Checker</h3><span class="close-btn" id="kex-checker-close">X</span></div>';
html += '<div class="chk-body">';
html += '<div class="summary">';
if (totalIssues === 0) {
html += '<span class="pass">All checks passed! No issues found.</span>';
} else {
html += '<span class="fail">' + totalIssues + ' issue' + (totalIssues > 1 ? 's' : '') + ' found</span>';
}
html += '</div>';
html += '<button class="export-btn" id="kex-export-csv">Export Issues as CSV</button>';
Object.keys(cats).forEach(function (catKey) {
var catName = cats[catKey];
var items = catResults[catKey] || [];
html += '<div class="section"><h4>' + catName + ' ';
if (items.length === 0) {
html += '<span class="pass">PASS</span>';
} else {
html += '<span class="fail">(' + items.length + ')</span>';
}
html += '</h4>';
items.forEach(function (item, idx) {
var elIdx = highlights.indexOf(item.el);
html += '<div class="issue" data-highlight-idx="' + elIdx + '">' + item.msg + '</div>';
});
html += '</div>';
});
html += '</div>';
panel.innerHTML = html;
document.body.appendChild(panel);
// ── Event: close panel ──
document.getElementById("kex-checker-close").addEventListener("click", function () {
document.getElementById("kex-checker-panel").remove();
document.querySelectorAll(".kex-check-highlight").forEach(function (e) {
e.classList.remove("kex-check-highlight");
e.removeAttribute("data-kex-issue");
});
});
// ── Event: click issue to scroll ──
panel.querySelectorAll(".issue").forEach(function (issueEl) {
issueEl.addEventListener("click", function () {
var idx = parseInt(this.getAttribute("data-highlight-idx"));
if (highlights[idx]) {
highlights[idx].scrollIntoView({ behavior: "smooth", block: "center" });
}
});
});
// ── Event: export CSV ──
document.getElementById("kex-export-csv").addEventListener("click", function () {
var csv = "Category,Issue,Detail,Page URL\n";
results.forEach(function (r) {
var cat = cats[r.cat] || r.cat;
var msg = r.msg.replace(/"/g, '""');
var detail = (r.detail || "").replace(/"/g, '""');
csv += '"' + cat + '","' + msg + '","' + detail + '","' + window.location.href + '"\n';
});
var blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "kex_page_check_" + document.title.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 40) + ".csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
}
waitForContent();
})();