New:Socket for Asana Is Now Available.Learn more
Get Started

z.WebKeyBind

Package Overview
Versions
1
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

z-webkeybind@yourdomain.com - firefox Package Compare versions

Comparing version
1.0.2
to
1.0.3
+141
-76
content.js

@@ -8,23 +8,45 @@ // =======================================================

let isSaving = false;
let isLocked = false;
let shortcutCache = [];
// =======================================================
// 2. ACCESSIBILITY ENGINE
// 2. ACCESSIBILITY ENGINE (DUAL-TOGGLE FIX)
// =======================================================
const srAnnouncer = document.createElement('div');
srAnnouncer.id = "webkeybind-announcer";
srAnnouncer.setAttribute('aria-live', 'assertive');
srAnnouncer.setAttribute('aria-atomic', 'true');
srAnnouncer.style.cssText = 'position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;';
document.body.appendChild(srAnnouncer);
const srAnnouncer1 = document.createElement('div');
const srAnnouncer2 = document.createElement('div');
const commonStyles = 'position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;';
srAnnouncer1.id = "wkb-announcer-1";
srAnnouncer1.setAttribute('aria-live', 'assertive');
srAnnouncer1.setAttribute('aria-atomic', 'true');
srAnnouncer1.style.cssText = commonStyles;
srAnnouncer2.id = "wkb-announcer-2";
srAnnouncer2.setAttribute('aria-live', 'assertive');
srAnnouncer2.setAttribute('aria-atomic', 'true');
srAnnouncer2.style.cssText = commonStyles;
document.body.appendChild(srAnnouncer1);
document.body.appendChild(srAnnouncer2);
let announcerToggle = true;
function announceToScreenReader(message, color = "default") {
showNotification(message, color);
if (color !== "hidden") {
showNotification(message, color);
}
const langMap = { "English": "en", "हिंदी": "hi", "मराठी": "mr", "മലയാളം": "ml" };
const isoCode = langMap[window.currentLang] || "en";
srAnnouncer.setAttribute('lang', isoCode);
srAnnouncer1.setAttribute('lang', isoCode);
srAnnouncer2.setAttribute('lang', isoCode);
srAnnouncer.textContent = '';
setTimeout(() => { srAnnouncer.textContent = message; }, 50);
if (announcerToggle) {
srAnnouncer2.textContent = '';
srAnnouncer1.textContent = message;
} else {
srAnnouncer1.textContent = '';
srAnnouncer2.textContent = message;
}
announcerToggle = !announcerToggle;
}

@@ -105,11 +127,13 @@

document.addEventListener('mouseover', (e) => {
if (isLocked) return;
if (currentMode !== 'mouse' && currentMode !== 'creation') return;
if (currentMode !== 'creation') return;
lastInteractionType = 'mouse';
const target = getClickableTarget(e.target);
if (target !== activeHoverElement) updateHighlight(target);
if (target !== activeHoverElement) {
updateHighlight(target);
target.focus(); // Pull focus so SR recognizes the target
}
}, true);
document.addEventListener('focus', (e) => {
if (currentMode !== 'keyboard' && currentMode !== 'creation') return;
if (currentMode !== 'creation') return;
lastInteractionType = 'keyboard';

@@ -121,14 +145,9 @@ const target = getClickableTarget(e.target);

document.addEventListener('click', (e) => {
if (currentMode !== 'mouse' && currentMode !== 'creation') return;
const target = getClickableTarget(e.target);
if (target) {
e.preventDefault();
e.stopImmediatePropagation();
isLocked = true;
updateHighlight(target);
target.style.outline = "4px solid #FF9800";
announceToScreenReader("Button selected. Press a key to save shortcut.", "orange");
// Prevent accidental clicks on links when in creation mode
if (currentMode === 'creation') {
const target = getClickableTarget(e.target);
if (target) {
e.preventDefault();
e.stopImmediatePropagation();
}
}

@@ -141,11 +160,27 @@ }, true);

const key = event.key.toUpperCase();
if (event.key === 'Escape' || event.keyCode === 27) {
if (currentMode !== null) {
event.preventDefault();
event.stopPropagation();
switchMode(currentMode);
return;
}
}
if (['CONTROL', 'SHIFT', 'ALT', 'TAB', 'CAPSLOCK'].includes(key)) return;
if (event.altKey && event.shiftKey) {
// NOTE: 'S' is completely removed from here.
// Chrome will now handle Alt+Shift+S natively via manifest.json
if (key === 'M') { event.preventDefault(); event.stopImmediatePropagation(); switchMode('mouse'); return; }
if (key === 'K') { event.preventDefault(); event.stopImmediatePropagation(); switchMode('keyboard'); return; }
if (key === 'C') { event.preventDefault(); event.stopImmediatePropagation(); switchMode('creation'); return; }
if (key === 'A') { event.preventDefault(); event.stopImmediatePropagation(); readAllShortcuts();return; }
if (key === 'A') {
event.preventDefault();
event.stopImmediatePropagation();
readAllShortcuts();
return;
}
if (key === 'C') {
event.preventDefault();
event.stopImmediatePropagation();
switchMode('creation');
return;
}
}

@@ -155,4 +190,4 @@

// SAVE LOGIC
if (currentMode !== null) {
// === SAVE LOGIC ===
if (currentMode === 'creation') {
if (key.match(/^[A-Z0-9]$/)) {

@@ -165,2 +200,3 @@ if (isInputActive()) return;

// Save the key instantly to whatever is highlighted
if (activeHoverElement) {

@@ -173,3 +209,3 @@ saveShortcut(activeHoverElement, key);

} else {
// EXECUTE SHORTCUT
// === EXECUTE LOGIC ===
if (event.altKey || (event.ctrlKey && event.shiftKey)) {

@@ -187,3 +223,3 @@ const match = shortcutCache.find(s => s.key === key);

// =======================================================
// 5. HIGHLIGHT ENGINE
// 5. HIGHLIGHT ENGINE & ACCESSIBILITY BYPASS
// =======================================================

@@ -208,2 +244,10 @@ function updateHighlight(newElement) {

el.setAttribute('data-webkeybind-highlight', 'true');
// === FIX: Forces Screen Reader to pass keys through ONLY for this element ===
if (currentMode === 'creation') {
if (el.dataset.originalRole === undefined) {
el.dataset.originalRole = el.getAttribute('role') || "null";
}
el.setAttribute('role', 'application');
}
}

@@ -218,2 +262,13 @@

}
// Restore the element's original role so the site behaves normally again
if (el.dataset.originalRole !== undefined) {
if (el.dataset.originalRole === "null") {
el.removeAttribute('role');
} else {
el.setAttribute('role', el.dataset.originalRole);
}
delete el.dataset.originalRole;
}
el.removeAttribute('data-webkeybind-highlight');

@@ -227,15 +282,8 @@ }

if (!chrome.runtime?.id) { announceToScreenReader("Please refresh the page.", "red"); return; }
isLocked = false;
if (currentMode === newMode) {
let modeName = "Teach Mode";
if (currentMode === 'mouse') modeName = "Mouse Mode";
if (currentMode === 'keyboard') modeName = "Keyboard Mode";
if (currentMode === 'creation') modeName = "Creation Mode";
currentMode = null;
document.body.removeAttribute('role');
updateHighlight(null);
announceToScreenReader(`${modeName} Disabled.`, "red");
announceToScreenReader("Creation Mode Disabled", "red");
document.body.style.cursor = "default";

@@ -246,16 +294,12 @@ return;

currentMode = newMode;
document.body.setAttribute('role', 'application');
if (newMode === 'mouse') lastInteractionType = 'mouse';
if (newMode === 'mouse' || newMode === 'creation') lastInteractionType = 'mouse';
if (newMode === 'keyboard') lastInteractionType = 'keyboard';
if (newMode === 'mouse' && activeHoverElement) updateHighlight(activeHoverElement);
if (newMode === 'keyboard') {
const focused = getClickableTarget(document.activeElement);
updateHighlight(focused);
if (newMode === 'creation' && activeHoverElement) updateHighlight(activeHoverElement);
if (newMode === 'creation') {
announceToScreenReader("Creation Mode Enabled", "orange");
document.body.style.cursor = "crosshair";
}
if (newMode === 'mouse') { announceToScreenReader("Mouse Mode Enabled.", "blue"); document.body.style.cursor = "crosshair"; }
else if (newMode === 'keyboard') { announceToScreenReader("Keyboard Mode Enabled.", "purple"); document.body.style.cursor = "default"; }
else if (newMode === 'creation') { announceToScreenReader("Creation Mode Enabled.", "orange"); document.body.style.cursor = "crosshair"; }
}

@@ -277,14 +321,23 @@

const currentName = (profile.aria || profile.text || "Element").trim();
const currentName = (profile.aria || profile.text || profile.tag || "Element").trim();
const currentId = profile.id || "";
const currentPath = profile.path || "";
let simpleId = profile.path;
if (profile.id) {
simpleId = `#${CSS.escape(profile.id)}`;
} else if (profile.testId) {
simpleId = `[data-testid="${CSS.escape(profile.testId)}"]`;
} else if (element.className && typeof element.className === 'string' && element.className.trim()) {
const safeClasses = element.className.trim().split(/\s+/).map(c => CSS.escape(c)).join('.');
simpleId = `${profile.tag}.${safeClasses}`;
}
chrome.storage.local.get(null, (items) => {
const userLang = items.ui_language || "English";
const t = window.translations?.[userLang] || window.translations?.['English'] || {
key_already_used: "Key '{key}' is already used for '{name}'."
};
const t = window.translations?.[userLang] || window.translations?.['English'] || {};
const keyAlreadyUsedStr = t.key_already_used || "Key '{key}' is already used by '{name}'.";
const allItems = Object.values(items);
// --- 1. CHECK: IS THIS KEY ALREADY USED ANYWHERE ON THIS SITE? ---
const keyConflict = allItems.find(item =>

@@ -299,5 +352,5 @@ item.key === key &&

if (!isSameButtonId && !isSameButtonPath) {
isLocked = false;
const existingName = keyConflict.name || "another button";
const msg = t.key_already_used.replace("{key}", key).replace("{name}", existingName);
const existingName = keyConflict.name || "another element";
const msg = keyAlreadyUsedStr.replace("{key}", key).replace("{name}", existingName);
announceToScreenReader(msg, "red");

@@ -310,3 +363,2 @@ element.style.outline = "4px solid #DC3545";

// --- 2. SAVE NEW SHORTCUT ---
isSaving = true;

@@ -317,3 +369,2 @@ if (keyConflict) {

const uniqueId = Date.now().toString();
const simpleId = profile.id ? `#${profile.id}` : (profile.text || profile.path);
const data = { id: uniqueId, url: currentHost, name: currentName, profile: profile, elementId: simpleId, key: key };

@@ -323,8 +374,12 @@

isSaving = false;
isLocked = false;
announceToScreenReader(`Saved shortcut Alt ${key}`, "green");
element.style.outline = "4px solid #00E676";
setTimeout(() => {
if(currentMode) addHighlight(element);
else removeHighlight(element);
if(currentMode) {
// STAYS ACTIVE! Restore blue highlight so user can bind the next key instantly
addHighlight(element);
} else {
removeHighlight(element);
}
}, 1000);

@@ -350,4 +405,3 @@ });

// --- NEW: ANNOUNCE SUCCESSFUL EXECUTION ---
announceToScreenReader(`Executing: ${match.name || "Shortcut"}`, "blue");
announceToScreenReader(`Executing shortcut: ${match.name || "Action"}`, "blue");

@@ -358,7 +412,15 @@ executeShortcut(result.element);

match.profile = generateRobustProfile(result.element);
match.elementId = match.profile.id ? `#${match.profile.id}` : (match.profile.text || match.profile.path);
let safeRebuild = match.profile.path;
if (match.profile.id) safeRebuild = `#${CSS.escape(match.profile.id)}`;
else if (result.element.className && typeof result.element.className === 'string' && result.element.className.trim()) {
const c = result.element.className.trim().split(/\s+/).map(i => CSS.escape(i)).join('.');
safeRebuild = `${match.profile.tag}.${c}`;
}
match.elementId = safeRebuild;
chrome.storage.local.set({ [`shortcut_${match.id}`]: match });
}
} else {
announceToScreenReader("Element not found.", "red");
announceToScreenReader("Element not found on page.", "red");
}

@@ -429,2 +491,3 @@ }

}
function findElementBySelector(selector) { try { return document.querySelector(selector); } catch { return null; } }

@@ -466,3 +529,3 @@

// =======================================================
// 9. AUDIO READER
// 9. ARIA-ONLY SHORTCUT ANNOUNCER
// =======================================================

@@ -472,2 +535,3 @@ function readAllShortcuts() {

const currentHost = window.location.hostname;
chrome.storage.local.get(null, (items) => {

@@ -477,4 +541,5 @@ const siteShortcuts = Object.values(items).filter(s =>

);
if (siteShortcuts.length === 0) {
announceToScreenReader("No shortcuts saved for this page.");
announceToScreenReader("No shortcuts are assigned for this webpage.", "red");
} else {

@@ -484,5 +549,5 @@ const spokenText = siteShortcuts

.join(". ");
announceToScreenReader(`Found ${siteShortcuts.length} shortcuts. ${spokenText}`);
announceToScreenReader(`Found ${siteShortcuts.length} shortcuts. ${spokenText}`, "blue");
}
});
}

@@ -0,1 +1,4 @@

// =======================================================
// IMPORT & EXPORT LOGIC
// =======================================================
document.addEventListener('DOMContentLoaded', () => {

@@ -21,6 +24,4 @@ const btnExportSite = document.getElementById('btn-export-site');

// --- CHECK IF WE ARE IN THE "BROWSE ONLY" FULL TAB ---
const isFullTabMode = new URLSearchParams(window.location.search).get('importMode') === 'true';
// --- 1. FOCUS TRAP LOGIC ---
function handleFocusTrap(e) {

@@ -41,5 +42,7 @@ if (e.key !== 'Tab') return;

// --- 2. MENU TOGGLE ---
// --- 3-BAR MENU FIX ---
// Because menuBurger is a <button> tag in your HTML, it natively handles Enter/Space.
// Adding keydown listeners causes it to fire twice. We ONLY use 'click' here.
if (menuBurger && menuDropdown) {
menuBurger.addEventListener('click', (e) => {
const toggleMenu = (e) => {
e.stopPropagation();

@@ -53,2 +56,3 @@ const langMenu = document.getElementById('lang-menu');

menuDropdown.style.display = isVisible ? 'none' : 'block';
menuBurger.setAttribute('aria-expanded', isVisible ? 'false' : 'true');

@@ -58,4 +62,6 @@ if(window.showAccessibleAlert) {

}
});
};
menuBurger.addEventListener('click', toggleMenu);
if (menuContainer) {

@@ -65,2 +71,3 @@ menuContainer.addEventListener('focusout', (event) => {

menuDropdown.style.display = 'none';
menuBurger.setAttribute('aria-expanded', 'false');
if(window.showAccessibleAlert) window.showAccessibleAlert("Import Export menu closed.", "info");

@@ -72,6 +79,12 @@ }

if (closeMenuBtn) closeMenuBtn.addEventListener('click', (e) => {
e.stopPropagation(); menuDropdown.style.display = 'none';
if(window.showAccessibleAlert) window.showAccessibleAlert("Import Export menu closed.", "info");
});
if (closeMenuBtn) {
const closeMenu = (e) => {
e.stopPropagation();
menuDropdown.style.display = 'none';
if (menuBurger) menuBurger.setAttribute('aria-expanded', 'false');
if(window.showAccessibleAlert) window.showAccessibleAlert("Import Export menu closed.", "info");
};
closeMenuBtn.addEventListener('click', closeMenu);
}

@@ -81,2 +94,3 @@ document.addEventListener('click', () => {

menuDropdown.style.display = 'none';
if (menuBurger) menuBurger.setAttribute('aria-expanded', 'false');
if(window.showAccessibleAlert) window.showAccessibleAlert("Import Export menu closed.", "info");

@@ -86,3 +100,2 @@ }

// --- 3. EXPORT LOGIC ---
function exportShortcuts(exportAll) {

@@ -109,9 +122,14 @@ const hostname = window.currentSiteHostname || "";

if (btnExportSite) btnExportSite.addEventListener('click', () => exportShortcuts(false));
if (btnExportAll) btnExportAll.addEventListener('click', () => exportShortcuts(true));
if (btnExportSite) {
btnExportSite.addEventListener('click', (e) => { e.preventDefault(); exportShortcuts(false); });
}
// --- 4. THE FULL TAB IMPORT TRICK ---
if (btnExportAll) {
btnExportAll.addEventListener('click', (e) => { e.preventDefault(); exportShortcuts(true); });
}
function openModal() {
importModal.style.display = 'flex';
if (menuDropdown) menuDropdown.style.display = 'none';
if (menuBurger) menuBurger.setAttribute('aria-expanded', 'false');
document.addEventListener('keydown', handleFocusTrap);

@@ -129,3 +147,2 @@ setTimeout(() => { document.getElementById('silent-start')?.focus(); }, 50);

// If in the full tab, closing the modal should close the entire tab
if (isFullTabMode) {

@@ -139,10 +156,10 @@ window.close();

if (btnImport) {
btnImport.addEventListener('click', () => {
btnImport.addEventListener('click', (e) => {
e.preventDefault();
if (isFullTabMode) {
openModal();
} else {
// Open THIS file in a new tab to bypass Firefox security
const currentHtmlFile = window.location.pathname;
chrome.tabs.create({ url: currentHtmlFile + "?importMode=true" });
window.close(); // Close the tiny popup
window.close();
}

@@ -159,8 +176,5 @@ });

// --- ISOLATE THE BROWSE BOX IN FULL TAB MODE ---
if (isFullTabMode) {
// 1. Move the import modal to the very top level of the body
document.body.appendChild(importModal);
// 2. Hide everything else in the extension so it looks like a clean, dedicated page
Array.from(document.body.children).forEach(child => {

@@ -172,3 +186,2 @@ if (child !== importModal && child.tagName !== 'SCRIPT') {

// 3. Make the modal fill the screen with a clean background
importModal.style.display = 'flex';

@@ -180,20 +193,40 @@ importModal.style.position = 'fixed';

importModal.style.height = '100vh';
importModal.style.backgroundColor = '#f8f9fa'; // Clean light grey background
importModal.style.backgroundColor = '#f8f9fa';
importModal.style.zIndex = '999999';
// 4. Hide the "Close" button since they can just close the browser tab to cancel
if (btnCloseImport) btnCloseImport.style.display = 'none';
// Trigger the focus trap for accessibility
setTimeout(openModal, 100);
}
// --- 5. STANDARD FILE CLICK ---
if (dropZone) {
dropZone.setAttribute('role', 'button');
dropZone.setAttribute('tabindex', '0');
dropZone.setAttribute('aria-label', 'Upload JSON file. Press Enter to browse, or drag and drop a file here.');
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fileInput.click(); } });
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.backgroundColor = '#f0ebff';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.backgroundColor = '';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.backgroundColor = '';
if (e.dataTransfer.files.length) {
processFile(e.dataTransfer.files[0]);
}
});
}
if (fileInput) fileInput.addEventListener('change', (e) => { if (e.target.files.length) processFile(e.target.files[0]); fileInput.value = ''; });
if (fileInput) {
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) processFile(e.target.files[0]);
fileInput.value = '';
});
}
// --- ACCESSIBLE CONFLICT MODAL ---
function showConflictResolutionModal(msg, onReplace, onReplaceAll, onSkip, onSkipAll, onCancel) {

@@ -280,3 +313,2 @@ let popupAnnouncer = document.getElementById('wkb-conflict-announcer');

// --- 6. FILE PROCESSOR (FIREFOX SAFE BATCH SAVE) ---
function processFile(file) {

@@ -307,14 +339,15 @@ if (!file.name.endsWith('.json')) {

const finalizeBatchSave = () => {
importModal.style.display = 'none';
document.removeEventListener('keydown', handleFocusTrap);
const finishUp = () => {
if (window.showAccessibleAlert) window.showAccessibleAlert(`Import Complete! Added: ${importCount}, Replaced: ${replaceCount}, Skipped: ${skipCount}`, "success");
// FIX: Announce EXACTLY "Shortcuts imported"
if (window.showAccessibleAlert) window.showAccessibleAlert("Shortcuts imported", "success");
if (window.loadShortcuts) window.loadShortcuts();
// Wait 1.5 seconds so they can see/hear the success alert, then close the tab!
if (isFullTabMode) {
setTimeout(() => { window.close(); }, 1500);
} else if (btnImport) {
btnImport.focus();
// FIX: Auto close window after 2 seconds
setTimeout(() => { window.close(); }, 2000);
} else {
importModal.style.display = 'none';
document.removeEventListener('keydown', handleFocusTrap);
if (btnImport) setTimeout(() => btnImport.focus(), 100);
}

@@ -338,8 +371,7 @@ };

document.removeEventListener('keydown', handleFocusTrap);
if (window.showAccessibleAlert) window.showAccessibleAlert(`Import Cancelled. Added: ${importCount}, Replaced: ${replaceCount}, Skipped: ${skipCount}`, "info");
if (window.showAccessibleAlert) window.showAccessibleAlert(`Import Cancelled.`, "info");
if (window.loadShortcuts) window.loadShortcuts();
if (isFullTabMode) {
window.close();
} else if (btnImport) {
if (!isFullTabMode && btnImport) {
btnImport.focus();

@@ -366,3 +398,2 @@ }

// Guarantee new unique ID
const newId = Date.now().toString() + Math.random().toString(36).substring(2, 6);

@@ -369,0 +400,0 @@ item.id = newId;

@@ -62,3 +62,3 @@ <!DOCTYPE html>

<td>Open or Close Settings Window</td>
<td><strong>Alt + Shift + S</strong></td>
<td><strong>Alt + Shift + Z</strong></td>
</tr>

@@ -65,0 +65,0 @@ <tr>

+112
-586
// =======================================================
// VALIDATION & HELPER UTILITIES
// =======================================================
function isValidURL(string) {
if (!string) return false;
try {
new URL(string);
return true;
} catch (_) {
try {
new URL('https://' + string);
return true;
} catch (__) {
return false;
}
}
}
function normalizeUrl(url) {
return url.replace(/^(?:https?:\/\/)?(?:www\.)?/i, "").split('/')[0].toLowerCase();
}
// =======================================================
// INITIALIZATION & MAIN LOGIC
// =======================================================
document.addEventListener('DOMContentLoaded', () => {
window.currentLang = "English";
// --- 1. ACCESSIBILITY ANNOUNCER FOR POPUP ---
const popupAnnouncer = document.createElement('div');
popupAnnouncer.setAttribute('aria-live', 'assertive');
popupAnnouncer.setAttribute('aria-atomic', 'true');
popupAnnouncer.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;';
document.body.appendChild(popupAnnouncer);
function showAccessibleAlert(msg, type = "error") {
popupAnnouncer.textContent = '';
setTimeout(() => { popupAnnouncer.textContent = msg; }, 50);
const existing = document.getElementById('webkeybind-popup-alert');
if (existing) existing.remove();
const alertDiv = document.createElement('div');
alertDiv.id = 'webkeybind-popup-alert';
alertDiv.setAttribute('aria-hidden', 'true');
alertDiv.textContent = msg; // FIX: innerText -> textContent
let bgColor = "#007BFF";
if (type === "error") bgColor = "#DC3545";
if (type === "success") bgColor = "#28A745";
if (type === "info") bgColor = "#17a2b8";
alertDiv.style.cssText = `
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
background-color: ${bgColor}; color: white; padding: 12px 20px;
border-radius: 8px; font-family: sans-serif; font-size: 14px; font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 2147483647;
text-align: center; max-width: 90%; word-wrap: break-word;
animation: popup-fadein 0.3s ease-out;
`;
document.body.appendChild(alertDiv);
if (!document.getElementById('popup-alert-styles')) {
const style = document.createElement('style');
style.id = 'popup-alert-styles';
// FIX: innerHTML -> textContent
style.textContent = `
@keyframes popup-fadein { from { opacity: 0; transform: translate(-50%, 10px); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes modal-fadein { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
`;
document.head.appendChild(style);
}
setTimeout(() => {
if (document.body.contains(alertDiv)) {
alertDiv.style.opacity = "0";
alertDiv.style.transition = "opacity 0.3s";
setTimeout(() => { if (document.body.contains(alertDiv)) alertDiv.remove(); }, 300);
}
}, 3000);
}
window.showAccessibleAlert = showAccessibleAlert;
function showAccessibleConfirm(msg, onConfirmCallback, onCancelCallback = null, customYesTxt = null, customNoTxt = null) {
const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};
popupAnnouncer.textContent = '';
setTimeout(() => { popupAnnouncer.textContent = msg + " Press Tab to select options."; }, 50);
const existing = document.getElementById('wkb-confirm-modal');
if (existing) existing.remove();
const overlay = document.createElement('div');
overlay.id = 'wkb-confirm-modal';
overlay.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); z-index: 2147483647;
display: flex; justify-content: center; align-items: center;
backdrop-filter: blur(2px);
`;
const modal = document.createElement('div');
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.style.cssText = `
background: white; padding: 24px; border-radius: 8px; width: 300px; max-width: 90%;
box-shadow: 0 10px 25px rgba(0,0,0,0.2); text-align: center; font-family: sans-serif;
animation: modal-fadein 0.2s ease-out;
`;
const text = document.createElement('p');
text.textContent = msg; // FIX: innerText -> textContent
text.style.cssText = "margin: 0 0 20px 0; color: #333; font-size: 15px; line-height: 1.5; font-weight: 500; word-break: break-word;";
const btnContainer = document.createElement('div');
btnContainer.style.cssText = "display: flex; justify-content: center; gap: 12px;";
const btnCancel = document.createElement('button');
btnCancel.textContent = customNoTxt || t.cancel || "Cancel"; // FIX: innerText -> textContent
btnCancel.style.cssText = "padding: 8px 16px; border: 1px solid #ccc; background: #f8f9fa; border-radius: 4px; cursor: pointer; color: #333; font-weight: bold; flex: 1;";
const btnYes = document.createElement('button');
btnYes.textContent = customYesTxt || t.yes_delete || "Yes, Delete"; // FIX: innerText -> textContent
btnYes.style.cssText = "padding: 8px 16px; border: none; background: #DC3545; color: white; border-radius: 4px; cursor: pointer; font-weight: bold; flex: 1;";
if (customYesTxt === "Replace") btnYes.style.background = "#FF9800";
btnCancel.onclick = () => { overlay.remove(); showAccessibleAlert("Action cancelled.", "info"); if(onCancelCallback) onCancelCallback(); };
btnYes.onclick = () => { overlay.remove(); onConfirmCallback(); };
btnContainer.appendChild(btnCancel);
btnContainer.appendChild(btnYes);
modal.appendChild(text);
modal.appendChild(btnContainer);
overlay.appendChild(modal);
document.body.appendChild(overlay);
overlay.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey) {
if (document.activeElement === btnCancel) { e.preventDefault(); btnYes.focus(); }
} else {
if (document.activeElement === btnYes) { e.preventDefault(); btnCancel.focus(); }
}
} else if (e.key === 'Escape') {
e.preventDefault();
overlay.remove();
showAccessibleAlert("Action cancelled.", "info");
if(onCancelCallback) onCancelCallback();
}
});
btnCancel.focus();
}
window.showAccessibleConfirm = showAccessibleConfirm;
// --- 2. ADD SHORTCUT MODAL ---
function showAddShortcutModal() {
const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};
const existing = document.getElementById('wkb-add-modal');
if (existing) existing.remove();
const overlay = document.createElement('div');
overlay.id = 'wkb-add-modal';
overlay.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.6); z-index: 2147483647;
display: flex; justify-content: center; align-items: center;
backdrop-filter: blur(3px);
`;
const modal = document.createElement('div');
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-labelledby', 'add-modal-title');
modal.style.cssText = `
background: white; padding: 24px; border-radius: 8px; width: 340px; max-width: 90%;
box-shadow: 0 10px 25px rgba(0,0,0,0.2); font-family: sans-serif;
animation: modal-fadein 0.2s ease-out; display: flex; flex-direction: column; gap: 12px;
`;
const title = document.createElement('h3');
title.id = 'add-modal-title';
title.textContent = t.addBtn || "Add Shortcut Manually"; // FIX: innerText -> textContent
title.style.cssText = "margin: 0 0 10px 0; color: #333; font-size: 18px; text-align: center;";
function createInput(placeholder, val, isReadonly, labelTxt) {
const wrapper = document.createElement('div');
wrapper.style.display = "flex"; wrapper.style.flexDirection = "column"; wrapper.style.gap = "4px";
const lbl = document.createElement('label');
lbl.textContent = labelTxt; // FIX: innerText -> textContent
lbl.style.cssText = "font-size: 12px; color: #555; font-weight: bold;";
const inp = document.createElement('input');
inp.type = "text"; inp.value = val; inp.placeholder = placeholder; inp.readOnly = isReadonly;
inp.style.cssText = `
width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px;
font-size: 14px; box-sizing: border-box; outline: none; transition: border 0.2s;
${isReadonly ? 'background-color: #f1f3f4; color: #5f6368;' : ''}
`;
inp.addEventListener('focus', () => { if(!isReadonly) inp.style.borderColor = "#007BFF"; });
inp.addEventListener('blur', () => { if(!isReadonly) inp.style.borderColor = "#ccc"; });
wrapper.appendChild(lbl);
wrapper.appendChild(inp);
return { wrapper, input: inp };
}
const urlField = createInput(t.p_url || "URL", window.currentSiteHostname || "", true, "Site URL");
const nameField = createInput(t.p_name || "Name", "", false, "Action Name");
const idField = createInput(t.p_id || "ID/Class", "", false, "Element ID or Class");
const keyField = createInput(t.p_key || "Key", "", false, "Trigger Key (e.g. K)");
keyField.input.maxLength = 1;
keyField.input.addEventListener('input', (e) => {
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
});
const btnContainer = document.createElement('div');
btnContainer.style.cssText = "display: flex; justify-content: space-between; gap: 10px; margin-top: 10px;";
const btnCancel = document.createElement('button');
btnCancel.textContent = t.cancelBtn || "Cancel"; // FIX: innerText -> textContent
btnCancel.style.cssText = "padding: 10px; border: 1px solid #ccc; background: #f8f9fa; border-radius: 4px; cursor: pointer; flex: 1; font-weight: bold; color: #333;";
const btnSave = document.createElement('button');
btnSave.textContent = "Save Shortcut"; // FIX: innerText -> textContent
btnSave.style.cssText = "padding: 10px; border: none; background: #007BFF; color: white; border-radius: 4px; cursor: pointer; flex: 1; font-weight: bold;";
const closeModal = (isCancel = false) => {
overlay.remove();
if(addBtn) addBtn.focus();
if(isCancel) showAccessibleAlert("Add shortcut cancelled.", "info");
};
btnCancel.onclick = () => closeModal(true);
btnSave.onclick = () => {
const n = nameField.input.value.trim();
const i = idField.input.value.trim();
const k = keyField.input.value.trim();
if (!n || !i || !k) {
showAccessibleAlert("All fields are required.", "error");
if(!n) nameField.input.style.borderColor = "#DC3545";
if(!i) idField.input.style.borderColor = "#DC3545";
if(!k) keyField.input.style.borderColor = "#DC3545";
return;
}
chrome.storage.local.get(null, (items) => {
const currentHost = window.currentSiteHostname;
const duplicate = Object.values(items).find(item => item.key === k && (normalizeUrl(item.url) === normalizeUrl(currentHost)));
if (duplicate) {
const btnName = duplicate.name || duplicate.elementId || "Unknown";
const errTemp = t.duplicate_error || "Key '{key}' is already saved for: {name}";
showAccessibleAlert(errTemp.replace("{key}", k).replace("{name}", btnName), "error");
keyField.input.style.borderColor = "#DC3545";
return;
}
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (!tabs[0] || !tabs[0].id) {
saveData(n, i, k);
return;
}
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: (selector) => {
try { if (document.querySelector(selector)) return true; } catch(e){}
if (document.getElementById(selector)) return true;
try { if (document.querySelector(`[aria-label="${selector.replace(/"/g, '\\"')}"]`)) return true; } catch(e){}
try { if (document.querySelector(`[data-testid="${selector.replace(/"/g, '\\"')}"]`)) return true; } catch(e){}
return false;
},
args: [i]
}, (results) => {
if (chrome.runtime.lastError || !results || !results[0] || !results[0].result) {
showAccessibleAlert(t.invalid_id || `Element "${i}" not found on page.`, "error");
idField.input.style.borderColor = "#DC3545";
} else {
saveData(n, i, k);
}
});
});
});
};
function saveData(name, elementId, key) {
const uniqueId = Date.now().toString();
const data = { id: uniqueId, url: window.currentSiteHostname, name: name, elementId: elementId, key: key, profile: { path: elementId } };
chrome.storage.local.set({ [`shortcut_${uniqueId}`]: data }, () => {
closeModal();
window.loadShortcuts();
showAccessibleAlert("Shortcut saved successfully.", "success");
});
}
btnContainer.append(btnCancel, btnSave);
modal.append(title, urlField.wrapper, nameField.wrapper, idField.wrapper, keyField.wrapper, btnContainer);
overlay.appendChild(modal);
document.body.appendChild(overlay);
const focusables = [nameField.input, idField.input, keyField.input, btnCancel, btnSave];
overlay.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
const first = focusables[0];
const last = focusables[focusables.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault(); first.focus();
}
} else if (e.key === 'Escape') {
e.preventDefault(); closeModal(true);
} else if (e.key === 'Enter' && document.activeElement !== btnCancel && document.activeElement !== btnSave) {
e.preventDefault(); btnSave.click();
}
});
setTimeout(() => { popupAnnouncer.textContent = "Add Shortcut dialog opened. Enter Name, ID, and Key."; }, 50);
nameField.input.focus();
}
// --- 3. UI REFERENCES & CORE LOGIC ---
const shortcutList = document.querySelector('.shortcut-list');
const addBtn = document.querySelector('.btn-add');
const showAllBtn = document.querySelector('.btn-show-all');
const deleteAllBtn = document.querySelector('.btn-delete-all') || document.getElementById('btn-delete-all');
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]?.id) chrome.tabs.connect(tabs[0].id, { name: "z-webkeybind-popup" });
});
window.currentSiteHostname = "";
let isShowingAll = false;
chrome.storage.local.get(['ui_language'], (result) => {
if (result.ui_language) window.currentLang = result.ui_language;
if (result.ui_language && window.updateLanguageUI) window.updateLanguageUI(result.ui_language);
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0] && tabs[0].url) {
try { window.currentSiteHostname = new URL(tabs[0].url).hostname; }
catch (e) { window.currentSiteHostname = "local"; }
}
loadShortcuts();
});
});
window.loadShortcuts = function () {
shortcutList.textContent = ''; // FIX: innerHTML -> textContent
const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};
if (showAllBtn) {
// FIX: Removed innerHTML, used textContent and nodes
showAllBtn.textContent = '';
const textNode = document.createTextNode((isShowingAll ? (t.showCurrent || "Show Current") : (t.showAll || "Show All")) + " ");
const arrowSpan = document.createElement('span');
arrowSpan.className = 'arrow-circle';
arrowSpan.textContent = isShowingAll ? '⌃' : '⌄';
showAllBtn.appendChild(textNode);
showAllBtn.appendChild(arrowSpan);
}
chrome.storage.local.get(null, (items) => {
const allShortcuts = Object.values(items).filter(item => item.id);
const currentNorm = normalizeUrl(window.currentSiteHostname);
const displayList = isShowingAll ? allShortcuts : allShortcuts.filter(s => {
const shortcutNorm = normalizeUrl(s.url);
return shortcutNorm.includes(currentNorm) || currentNorm.includes(shortcutNorm) || s.url === "<URL>";
});
displayList.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
if (displayList.length === 0) {
const msg = document.createElement('div');
msg.style.cssText = "text-align:center; padding:20px; color:#999; font-size:13px; font-style:italic;";
msg.textContent = `${t.no_shortcuts || "No shortcuts"} ${isShowingAll ? '' : window.currentSiteHostname}`; // FIX: innerText -> textContent
shortcutList.appendChild(msg);
} else {
displayList.forEach((data, index) => createRow(data, index + 1));
}
});
};
function createRow(data, index) {
const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};
const row = document.createElement('div');
row.className = 'shortcut-row';
row.setAttribute('data-id', data.id);
const currentNorm = normalizeUrl(window.currentSiteHostname);
const shortcutNorm = normalizeUrl(data.url);
if (shortcutNorm.includes(currentNorm) || currentNorm.includes(shortcutNorm) || data.url === "<URL>") {
row.style.backgroundColor = "#e8f0fe";
row.style.borderLeft = "4px solid #1a73e8";
row.title = "Active on this website";
}
// FIX: Replaced innerHTML with safe document.createElement
const indexSpan = document.createElement('span');
indexSpan.className = 'index';
indexSpan.textContent = index;
const urlInput = document.createElement('input');
urlInput.type = 'text';
urlInput.value = data.url;
urlInput.className = 'input-field url-input';
urlInput.readOnly = true;
urlInput.title = `Site: ${data.url}`;
urlInput.style.cssText = "background-color: #f1f3f4; color: #5f6368; cursor: default; border: 1px solid transparent; font-weight: 600;";
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.value = data.name;
nameInput.className = 'input-field';
nameInput.setAttribute('data-field', 'name');
nameInput.placeholder = t.p_name || 'Name';
const idInput = document.createElement('input');
idInput.type = 'text';
idInput.value = data.elementId;
idInput.className = 'input-field';
idInput.setAttribute('data-field', 'elementId');
idInput.placeholder = t.p_id || 'ID/Class';
const keyInput = document.createElement('input');
keyInput.type = 'text';
keyInput.value = data.key;
keyInput.className = 'input-field key-input';
keyInput.setAttribute('data-field', 'key');
keyInput.placeholder = t.p_key || 'Key';
keyInput.style.cssText = "text-align:center;";
keyInput.maxLength = 1;
const btnRemove = document.createElement('button');
btnRemove.className = 'btn-remove';
btnRemove.title = 'Delete';
btnRemove.textContent = '×';
row.append(indexSpan, urlInput, nameInput, idInput, keyInput, btnRemove);
row.querySelectorAll('input').forEach(input => {
if (input.value.trim() === "" && !input.readOnly) input.classList.add('input-error');
input.addEventListener('input', (e) => {
const field = e.target.dataset.field;
let value = e.target.value;
if (field === 'key') {
value = value.toUpperCase().replace(/[^A-Z0-9]/g, '');
e.target.value = value;
}
if (value.trim() === "") {
e.target.classList.add('input-error');
if (field === 'key') showAccessibleAlert("Key field cleared.", "info");
}
else e.target.classList.remove('input-error');
if (field === 'key' && value.trim() !== "") {
chrome.storage.local.get(null, (items) => {
const currentHost = data.url || window.currentSiteHostname;
const duplicate = Object.values(items).find(item => item.id !== data.id && item.key === value && (normalizeUrl(item.url) === normalizeUrl(currentHost)));
if (duplicate) {
const btnName = duplicate.name || duplicate.elementId || "Unknown";
const errTemp = t.duplicate_error || "The key '{key}' is already saved for: {name}";
showAccessibleAlert(errTemp.replace("{key}", value).replace("{name}", btnName), "error");
e.target.classList.add('input-error');
e.target.value = "";
}
});
}
});
});
const idInputEl = row.querySelector('input[data-field="elementId"]');
if (idInputEl) {
idInputEl.addEventListener('change', (e) => {
const val = e.target.value.trim();
if (val === "") {
e.target.classList.add('input-error');
return;
}
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (!tabs[0] || !tabs[0].id) {
return;
}
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: (selector) => {
try { if (document.querySelector(selector)) return true; } catch (e) { }
if (document.getElementById(selector)) return true;
try { if (document.querySelector(`[aria-label="${selector.replace(/"/g, '\\"')}"]`)) return true; } catch (e) { }
try { if (document.querySelector(`[data-testid="${selector.replace(/"/g, '\\"')}"]`)) return true; } catch (e) { }
return false;
},
args: [val]
}, (results) => {
if (chrome.runtime.lastError || !results || !results[0] || !results[0].result) {
showAccessibleAlert(t.invalid_id || `The Button ID / Selector "${val}" was not found on this webpage.`, "error");
e.target.classList.add('input-error');
} else {
e.target.classList.remove('input-error');
}
});
});
});
}
row.querySelector('.btn-remove').addEventListener('click', () => {
showAccessibleConfirm(t.delete_confirm || "Delete this shortcut?", () => {
chrome.storage.local.remove(`shortcut_${data.id}`, () => {
row.remove();
window.loadShortcuts();
showAccessibleAlert(t.deleted_success || "Shortcut deleted successfully.", "success");
});
});
});
shortcutList.appendChild(row);
}
if (showAllBtn) {
showAllBtn.addEventListener('click', () => {
isShowingAll = !isShowingAll;
window.loadShortcuts();
showAccessibleAlert(isShowingAll ? "Showing all shortcuts" : "Showing current site shortcuts", "info");
});
}
if (addBtn) {
addBtn.addEventListener('click', () => {
showAddShortcutModal();
});
}
document.querySelectorAll('.language-dropdown, .menu-container').forEach(el => el.removeAttribute('tabindex'));
if (deleteAllBtn) {
deleteAllBtn.addEventListener('click', () => {
const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};
const host = window.currentSiteHostname || "";
showAccessibleConfirm(t.delete_all_confirm || "Delete these shortcuts?", () => {
chrome.storage.local.get(null, (items) => {
const keysToRemove = isShowingAll ? Object.keys(items).filter(key => key.startsWith('shortcut_')) : Object.keys(items).filter(key => key.startsWith('shortcut_') && (normalizeUrl(items[key].url) === normalizeUrl(host)));
if (keysToRemove.length > 0) {
chrome.storage.local.remove(keysToRemove, () => {
window.loadShortcuts();
showAccessibleAlert(isShowingAll ? (t.deleted_all_success || "All shortcuts deleted successfully.") : (t.deleted_site_success || `Shortcuts for ${host} deleted.`), "success");
});
} else {
showAccessibleAlert((t.no_shortcuts || "No shortcuts") + " " + host, "info");
}
});
});
});
}
});
// =======================================================
// GLOBAL TRANSLATIONS

@@ -759,11 +199,11 @@ // =======================================================

if (saveAllBtn) {
saveAllBtn.textContent = t.saveChanges; // FIX: innerText -> textContent
saveAllBtn.textContent = t.saveChanges;
}
document.getElementById('current-lang').textContent = lang; // FIX: innerText -> textContent
document.querySelector('.logo').textContent = t.settingsTitle; // FIX: innerText -> textContent
document.getElementById('current-lang').textContent = lang;
document.querySelector('.logo').textContent = t.settingsTitle;
const titles = document.querySelectorAll('.section-title');
if (titles.length >= 2) {
titles[0].textContent = t.defaultTitle; // FIX: innerText -> textContent
titles[1].textContent = t.savedTitle; // FIX: innerText -> textContent
titles[0].textContent = t.defaultTitle;
titles[1].textContent = t.savedTitle;
}

@@ -774,5 +214,5 @@

const btnExportAll = document.getElementById('btn-export-all');
if (btnImport) btnImport.textContent = t.importBtn; // FIX: innerText -> textContent
if (btnExportSite) btnExportSite.textContent = t.exportBtn; // FIX: innerText -> textContent
if (btnExportAll) btnExportAll.textContent = t.exportAllBtn; // FIX: innerText -> textContent
if (btnImport) btnImport.textContent = t.importBtn;
if (btnExportSite) btnExportSite.textContent = t.exportBtn;
if (btnExportAll) btnExportAll.textContent = t.exportAllBtn;

@@ -784,5 +224,4 @@ const modalTitle = document.querySelector('.modal-content h3');

if (modalTitle) modalTitle.textContent = t.importTitle; // FIX: innerText -> textContent
if (modalTitle) modalTitle.textContent = t.importTitle;
// FIX: REPLACED innerHTML with safe node generation
if (dropMainText) {

@@ -802,4 +241,4 @@ dropMainText.textContent = '';

if (dropSubText) dropSubText.textContent = t.browseText; // FIX: innerText -> textContent
if (closeBtn) closeBtn.textContent = t.cancelBtn; // FIX: innerText -> textContent
if (dropSubText) dropSubText.textContent = t.browseText;
if (closeBtn) closeBtn.textContent = t.cancelBtn;

@@ -809,3 +248,2 @@ const btnAdd = document.querySelector('.btn-add');

// FIX: REPLACED innerHTML with safe node generation
if (btnAdd) {

@@ -820,3 +258,3 @@ btnAdd.textContent = '';

if (btnDeleteAll) btnDeleteAll.textContent = t.deleteAll; // FIX: innerText -> textContent
if (btnDeleteAll) btnDeleteAll.textContent = t.deleteAll;

@@ -838,5 +276,5 @@ const burger = document.getElementById('burger-label');

if (rows.length > 0) {
if (rows[0]) rows[0].cells[0].textContent = t.def_row1; // FIX: innerText -> textContent
if (rows[1]) rows[1].cells[0].textContent = t.def_row2; // FIX: innerText -> textContent
if (rows[2]) rows[2].cells[0].textContent = t.def_row3; // FIX: innerText -> textContent
if (rows[0]) rows[0].cells[0].textContent = t.def_row1;
if (rows[1]) rows[1].cells[0].textContent = t.def_row2;
if (rows[2]) rows[2].cells[0].textContent = t.def_row3;
}

@@ -847,6 +285,5 @@

if (headerAction) headerAction.textContent = t.headerAction; // FIX: innerText -> textContent
if (headerShortcut) headerShortcut.textContent = t.headerShortcut; // FIX: innerText -> textContent
if (headerAction) headerAction.textContent = t.headerAction;
if (headerShortcut) headerShortcut.textContent = t.headerShortcut;
// --- UPDATED GUIDE BOX ---
const guideTitle = document.getElementById('guideTitle');

@@ -857,12 +294,91 @@ const guideP1 = document.getElementById('guideP1');

if (guideTitle) guideTitle.textContent = t.guideTitle; // FIX: innerText -> textContent
if (guideP1) guideP1.textContent = t.guideP1; // FIX: innerText -> textContent
if (guideP2) guideP2.textContent = t.guideP2; // FIX: innerText -> textContent
if (guideEx) guideEx.textContent = t.guideEx; // FIX: innerText -> textContent
if (guideTitle) guideTitle.textContent = t.guideTitle;
if (guideP1) guideP1.textContent = t.guideP1;
if (guideP2) guideP2.textContent = t.guideP2;
if (guideEx) guideEx.textContent = t.guideEx;
const langMap = { "English": "en", "हिंदी": "hi", "मराठी": "mr", "മലയാളം": "ml" };
const langMap = { "English": "en", "हिंदी": "hi", "मराठी": "hi", "മലയാളം": "ml" };
document.documentElement.lang = langMap[lang] || "en";
};
// =======================================================
// DEDICATED LANGUAGE SCREEN READER ANNOUNCER
// =======================================================
const srLang1 = document.createElement('div');
const srLang2 = document.createElement('div');
srLang1.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;';
srLang2.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;';
srLang1.setAttribute('aria-live', 'assertive');
srLang2.setAttribute('aria-live', 'assertive');
srLang1.setAttribute('aria-atomic', 'true');
srLang2.setAttribute('aria-atomic', 'true');
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(srLang1);
document.body.appendChild(srLang2);
});
let srLangToggle = true;
function announceLanguageChange(lang, msg) {
const langMap = { "English": "en", "हिंदी": "hi", "मराठी": "hi", "മലയാളം": "ml" };
const isoCode = langMap[lang] || "en";
srLang1.setAttribute('lang', isoCode);
srLang2.setAttribute('lang', isoCode);
setTimeout(() => {
// === SECURITY FIX: Use safe DOM nodes instead of innerHTML ===
const safeSpan = document.createElement('span');
safeSpan.setAttribute('lang', isoCode);
safeSpan.textContent = msg;
if (srLangToggle) {
srLang2.textContent = '';
srLang1.textContent = '';
srLang1.appendChild(safeSpan);
} else {
srLang1.textContent = '';
srLang2.textContent = '';
srLang2.appendChild(safeSpan);
}
srLangToggle = !srLangToggle;
}, 50);
showLangVisualAlert(msg);
}
function showLangVisualAlert(msg) {
const existing = document.getElementById('wkb-lang-alert');
if (existing) existing.remove();
const alertDiv = document.createElement('div');
alertDiv.id = 'wkb-lang-alert';
alertDiv.setAttribute('aria-hidden', 'true');
alertDiv.textContent = msg;
alertDiv.style.cssText = `
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
background-color: #28A745; color: white; padding: 12px 20px;
border-radius: 8px; font-family: sans-serif; font-size: 14px; font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 2147483647;
text-align: center; max-width: 90%; word-wrap: break-word;
animation: popup-fadein 0.3s ease-out;
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (document.body.contains(alertDiv)) {
alertDiv.style.opacity = "0";
alertDiv.style.transition = "opacity 0.3s";
setTimeout(() => { if (document.body.contains(alertDiv)) alertDiv.remove(); }, 300);
}
}, 3000);
}
// =======================================================
// DROPDOWN EVENT LISTENERS
// =======================================================
document.addEventListener('DOMContentLoaded', () => {
const langTrigger = document.querySelector('.dropdown-trigger');

@@ -895,2 +411,5 @@ const langMenu = document.getElementById('lang-menu');

chrome.storage.local.set({ 'ui_language': selectedLang });
window.currentLang = selectedLang;
if (window.updateLanguageUI) window.updateLanguageUI(selectedLang);

@@ -904,3 +423,10 @@ if (window.loadShortcuts) window.loadShortcuts();

if(window.showAccessibleAlert) window.showAccessibleAlert(`Language changed to ${selectedLang}`, "success");
const alertMsgs = {
"English": "Language changed to English",
"हिंदी": "भाषा हिंदी में बदल दी गई है",
"मराठी": "भाषा मराठीत बदलली आहे",
"മലയാളം": "ഭാഷ മലയാളത്തിലേക്ക് മാറ്റി"
};
announceLanguageChange(selectedLang, alertMsgs[selectedLang] || `Language changed to ${selectedLang}`);
};

@@ -907,0 +433,0 @@ item.addEventListener('click', handleSelect);

{
"manifest_version": 3,
"name": "z.WebKeyBind",
"version": "1.0.2",
"version": "1.0.3",
"description": "Use Custom Shortcut key to Trigger the button of the page. ",

@@ -66,4 +66,4 @@ "permissions": [

"suggested_key": {
"default": "Alt+Shift+S",
"mac": "Alt+Shift+S"
"default": "Alt+Shift+Z",
"mac": "Alt+Shift+Z"
},

@@ -70,0 +70,0 @@ "description": "Open WebKeyBind Settings"

+198
-57

@@ -29,12 +29,56 @@ // =======================================================

// --- 1. ACCESSIBILITY ANNOUNCER FOR POPUP ---
const popupAnnouncer = document.createElement('div');
popupAnnouncer.setAttribute('aria-live', 'assertive');
popupAnnouncer.setAttribute('aria-atomic', 'true');
popupAnnouncer.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;';
document.body.appendChild(popupAnnouncer);
// --- 1. ACCESSIBILITY FOR STATIC TEXT ---
const staticTextElements = document.querySelectorAll('.logo, .section-title, .selector-info-box, .selector-info-box p, .selector-example');
staticTextElements.forEach(el => el.setAttribute('tabindex', '0'));
// --- 2. FIX: MAKE DEFAULT SHORTCUT TABLE READABLE (NO "ROW" ANNOUNCEMENT) ---
const defaultRows = document.querySelectorAll('.default-table tbody tr');
defaultRows.forEach((row) => {
row.setAttribute('tabindex', '0');
row.setAttribute('role', 'listitem'); // Forces SR to treat it as a list item, not a table row!
const cells = row.querySelectorAll('td');
if (cells.length >= 2) {
const action = cells[0].textContent.trim();
const keys = cells[1].textContent.trim();
row.setAttribute('aria-label', `Default Shortcut: ${action}. Key combination: ${keys}`);
}
});
// --- 3. ACCESSIBILITY ENGINE (DUAL-TOGGLE FIX FOR INSTANT READ) ---
const srAnnouncer1 = document.createElement('div');
const srAnnouncer2 = document.createElement('div');
const commonStyles = 'position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;';
srAnnouncer1.id = "wkb-popup-announcer-1";
srAnnouncer1.setAttribute('aria-live', 'assertive');
srAnnouncer1.setAttribute('aria-atomic', 'true');
srAnnouncer1.style.cssText = commonStyles;
srAnnouncer2.id = "wkb-popup-announcer-2";
srAnnouncer2.setAttribute('aria-live', 'assertive');
srAnnouncer2.setAttribute('aria-atomic', 'true');
srAnnouncer2.style.cssText = commonStyles;
document.body.appendChild(srAnnouncer1);
document.body.appendChild(srAnnouncer2);
let announcerToggle = true;
function announceToScreenReader(message) {
if (announcerToggle) {
srAnnouncer2.textContent = '';
srAnnouncer1.textContent = message;
} else {
srAnnouncer1.textContent = '';
srAnnouncer2.textContent = message;
}
announcerToggle = !announcerToggle;
}
// === INSTANT ANNOUNCEMENT - NO TIMEOUT DELAY ===
announceToScreenReader("Settings window is opened");
function showAccessibleAlert(msg, type = "error") {
popupAnnouncer.textContent = '';
setTimeout(() => { popupAnnouncer.textContent = msg; }, 50);
announceToScreenReader(msg);

@@ -86,4 +130,3 @@ const existing = document.getElementById('webkeybind-popup-alert');

const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};
popupAnnouncer.textContent = '';
setTimeout(() => { popupAnnouncer.textContent = msg + " Press Tab to select options."; }, 50);
announceToScreenReader(msg + " Press Tab to select options.");

@@ -156,3 +199,3 @@ const existing = document.getElementById('wkb-confirm-modal');

// --- 2. ADD SHORTCUT MODAL ---
// --- 4. ADD SHORTCUT MODAL ---
function showAddShortcutModal() {

@@ -186,14 +229,28 @@ const t = window.translations?.[window.currentLang] || window.translations?.['English'] || {};

title.textContent = t.addBtn || "Add Shortcut Manually";
title.setAttribute('tabindex', '0');
title.style.cssText = "margin: 0 0 10px 0; color: #333; font-size: 18px; text-align: center;";
function createInput(placeholder, val, isReadonly, labelTxt) {
function createInput(placeholder, val, isReadonly, labelTxt, uniqueSuffix) {
const wrapper = document.createElement('div');
wrapper.style.display = "flex"; wrapper.style.flexDirection = "column"; wrapper.style.gap = "4px";
const inputId = `wkb-input-${uniqueSuffix}`;
const lbl = document.createElement('label');
lbl.textContent = labelTxt;
lbl.setAttribute('for', inputId);
lbl.style.cssText = "font-size: 12px; color: #555; font-weight: bold;";
const inp = document.createElement('input');
inp.type = "text"; inp.value = val; inp.placeholder = placeholder; inp.readOnly = isReadonly;
inp.id = inputId;
inp.type = "text";
inp.value = val;
inp.placeholder = placeholder;
inp.readOnly = isReadonly;
inp.setAttribute('aria-label', labelTxt);
if (!isReadonly) {
inp.setAttribute('aria-required', 'true');
}
inp.style.cssText = `

@@ -212,6 +269,6 @@ width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px;

const urlField = createInput(t.p_url || "URL", window.currentSiteHostname || "", true, "Site URL");
const nameField = createInput(t.p_name || "Name", "", false, "Action Name");
const idField = createInput(t.p_id || "ID/Class", "", false, "Element ID or Class");
const keyField = createInput(t.p_key || "Key", "", false, "Trigger Key (e.g. K)");
const urlField = createInput(t.p_url || "URL", window.currentSiteHostname || "", true, "Site URL", "url");
const nameField = createInput(t.p_name || "Name", "", false, "Action Name", "name");
const idField = createInput(t.p_id || "ID/Class", "", false, "Element ID or Class", "elementId");
const keyField = createInput(t.p_key || "Key", "", false, "Trigger Key (e.g. K)", "key");

@@ -221,3 +278,6 @@ keyField.input.maxLength = 1;

e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
e.target.removeAttribute('aria-invalid');
});
nameField.input.addEventListener('input', (e) => e.target.removeAttribute('aria-invalid'));
idField.input.addEventListener('input', (e) => e.target.removeAttribute('aria-invalid'));

@@ -237,2 +297,3 @@ const btnContainer = document.createElement('div');

overlay.remove();
const addBtn = document.querySelector('.btn-add');
if(addBtn) addBtn.focus();

@@ -250,3 +311,2 @@ if(isCancel) showAccessibleAlert("Add shortcut cancelled.", "info");

btnSave.onclick = () => {
// Anti-double-click guard
btnSave.disabled = true;

@@ -262,5 +322,5 @@ btnSave.style.opacity = '0.5';

showAccessibleAlert("All fields are required.", "error");
if(!n) nameField.input.style.borderColor = "#DC3545";
if(!i) idField.input.style.borderColor = "#DC3545";
if(!k) keyField.input.style.borderColor = "#DC3545";
if(!n) { nameField.input.style.borderColor = "#DC3545"; nameField.input.setAttribute('aria-invalid', 'true'); }
if(!i) { idField.input.style.borderColor = "#DC3545"; idField.input.setAttribute('aria-invalid', 'true'); }
if(!k) { keyField.input.style.borderColor = "#DC3545"; keyField.input.setAttribute('aria-invalid', 'true'); }
enableSaveButton();

@@ -278,2 +338,3 @@ return;

keyField.input.style.borderColor = "#DC3545";
keyField.input.setAttribute('aria-invalid', 'true');
enableSaveButton();

@@ -302,2 +363,3 @@ return;

idField.input.style.borderColor = "#DC3545";
idField.input.setAttribute('aria-invalid', 'true');
enableSaveButton();

@@ -313,3 +375,2 @@ } else {

function saveData(name, elementId, key) {
// Guaranteeing a unique ID even if clicked fast
const uniqueId = Date.now().toString() + Math.random().toString(36).substring(2, 6);

@@ -329,3 +390,3 @@ const data = { id: uniqueId, url: window.currentSiteHostname, name: name, elementId: elementId, key: key, profile: { path: elementId } };

const focusables = [nameField.input, idField.input, keyField.input, btnCancel, btnSave];
const focusables = [title, urlField.input, nameField.input, idField.input, keyField.input, btnCancel, btnSave];
overlay.addEventListener('keydown', (e) => {

@@ -347,7 +408,7 @@ if (e.key === 'Tab') {

setTimeout(() => { popupAnnouncer.textContent = "Add Shortcut dialog opened. Enter Name, ID, and Key."; }, 50);
announceToScreenReader("Add Shortcut dialog opened. Enter Name, ID, and Key. Press Escape to cancel.");
nameField.input.focus();
}
// --- 3. UI REFERENCES & CORE LOGIC ---
// --- 5. UI REFERENCES & CORE LOGIC ---
const shortcutList = document.querySelector('.shortcut-list');

@@ -358,3 +419,43 @@ const addBtn = document.querySelector('.btn-add');

// Make sure popup auto-updates if "Capture Mode" saves something in the background
if (addBtn) {
addBtn.setAttribute('tabindex', '0');
addBtn.setAttribute('role', 'button');
addBtn.setAttribute('aria-label', 'Add a new shortcut manually');
const handleAdd = (e) => {
e?.preventDefault();
showAddShortcutModal();
};
addBtn.addEventListener('click', handleAdd);
addBtn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') handleAdd(e);
});
}
if (showAllBtn) {
showAllBtn.setAttribute('tabindex', '0');
showAllBtn.setAttribute('role', 'button');
const handleShowAll = (e) => {
e?.preventDefault();
isShowingAll = !isShowingAll;
window.loadShortcuts();
showAccessibleAlert(isShowingAll ? "Showing all shortcuts" : "Showing current site shortcuts", "info");
};
showAllBtn.addEventListener('click', handleShowAll);
showAllBtn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') handleShowAll(e);
});
}
if (shortcutList && shortcutList.parentNode) {
if (!document.getElementById('wkb-creation-tip')) {
const helpText = document.createElement('div');
helpText.id = 'wkb-creation-tip';
helpText.setAttribute('aria-live', 'polite');
helpText.setAttribute('tabindex', '0');
helpText.style.cssText = "font-size: 12.5px; color: #444; text-align: center; margin-bottom: 12px; padding: 8px; background: #f8f9fa; border-radius: 4px; border: 1px solid #ddd; font-weight: 500;";
helpText.textContent = "💡 Tip: Press Alt+Shift+C to enable/disable Creation Mode. Press Escape to cancel.";
shortcutList.parentNode.insertBefore(helpText, shortcutList);
}
}
chrome.storage.onChanged.addListener((changes, area) => {

@@ -396,5 +497,5 @@ if (area === 'local') {

if (!nameInp.value.trim()) { hasError = true; nameInp.classList.add('input-error'); }
if (!idInp.value.trim()) { hasError = true; idInp.classList.add('input-error'); }
if (!keyInp.value.trim()) { hasError = true; keyInp.classList.add('input-error'); }
if (!nameInp.value.trim()) { hasError = true; nameInp.classList.add('input-error'); nameInp.setAttribute('aria-invalid', 'true'); }
if (!idInp.value.trim()) { hasError = true; idInp.classList.add('input-error'); idInp.setAttribute('aria-invalid', 'true'); }
if (!keyInp.value.trim()) { hasError = true; keyInp.classList.add('input-error'); keyInp.setAttribute('aria-invalid', 'true'); }
});

@@ -423,3 +524,5 @@

duplicateFound = true;
row.querySelector('input[data-field="key"]').classList.add('input-error');
const keyInp = row.querySelector('input[data-field="key"]');
keyInp.classList.add('input-error');
keyInp.setAttribute('aria-invalid', 'true');
if (window.showAccessibleAlert) window.showAccessibleAlert(`Key '${key}' is already used by '${duplicate.name || duplicate.elementId}'.`, "error");

@@ -468,3 +571,3 @@ } else {

if (showAllBtn) {
showAllBtn.replaceChildren(); // Safe and Warning Free
showAllBtn.replaceChildren();
const btnText = document.createTextNode((isShowingAll ? (t.showCurrent || "Show Current") : (t.showAll || "Show All")) + " ");

@@ -479,5 +582,3 @@ const arrowSpan = document.createElement('span');

chrome.storage.local.get(null, (items) => {
// CRITICAL FIX: The UI clearing MUST happen inside the async callback
// to prevent the 1, 2, 1, 2 race condition duplication bug.
shortcutList.replaceChildren();
if (shortcutList) shortcutList.replaceChildren();

@@ -494,6 +595,9 @@ const allShortcuts = Object.values(items).filter(item => item.id);

if (displayList.length === 0) {
const msg = document.createElement('div');
msg.style.cssText = "text-align:center; padding:20px; color:#999; font-size:13px; font-style:italic;";
msg.textContent = `${t.no_shortcuts || "No shortcuts"} ${isShowingAll ? '' : window.currentSiteHostname}`;
shortcutList.appendChild(msg);
if (shortcutList) {
const msg = document.createElement('div');
msg.style.cssText = "text-align:center; padding:20px; color:#999; font-size:13px; font-style:italic;";
msg.textContent = `${t.no_shortcuts || "No shortcuts"} ${isShowingAll ? '' : window.currentSiteHostname}`;
msg.setAttribute('tabindex', '0');
shortcutList.appendChild(msg);
}
} else {

@@ -522,2 +626,5 @@ displayList.forEach((data, index) => createRow(data, index + 1));

indexSpan.textContent = index;
indexSpan.setAttribute('tabindex', '0');
indexSpan.setAttribute('role', 'listitem');
indexSpan.setAttribute('aria-label', `Shortcut ${index}`);

@@ -530,2 +637,3 @@ const urlInput = document.createElement('input');

urlInput.title = `Site: ${data.url}`;
urlInput.setAttribute('aria-label', `Site URL: ${data.url}`);
urlInput.style.cssText = "background-color: #f1f3f4; color: #5f6368; cursor: default; border: 1px solid transparent; font-weight: 600;";

@@ -538,2 +646,3 @@

nameInput.setAttribute('data-field', 'name');
nameInput.setAttribute('aria-label', 'Action Name');
nameInput.placeholder = t.p_name || 'Name';

@@ -546,2 +655,3 @@

idInput.setAttribute('data-field', 'elementId');
idInput.setAttribute('aria-label', 'Element ID or Class');
idInput.placeholder = t.p_id || 'ID/Class';

@@ -554,2 +664,3 @@

keyInput.setAttribute('data-field', 'key');
keyInput.setAttribute('aria-label', 'Trigger Key');
keyInput.placeholder = t.p_key || 'Key';

@@ -563,2 +674,3 @@ keyInput.style.cssText = "text-align:center;";

btnRemove.textContent = '×';
btnRemove.setAttribute('aria-label', `Delete shortcut for ${data.name || 'action'}`);

@@ -568,3 +680,6 @@ row.append(indexSpan, urlInput, nameInput, idInput, keyInput, btnRemove);

row.querySelectorAll('input').forEach(input => {
if (input.value.trim() === "" && !input.readOnly) input.classList.add('input-error');
if (input.value.trim() === "" && !input.readOnly) {
input.classList.add('input-error');
input.setAttribute('aria-invalid', 'true');
}

@@ -582,5 +697,8 @@ input.addEventListener('input', (e) => {

e.target.classList.add('input-error');
e.target.setAttribute('aria-invalid', 'true');
if (field === 'key') showAccessibleAlert("Key field cleared.", "info");
} else {
e.target.classList.remove('input-error');
e.target.removeAttribute('aria-invalid');
}
else e.target.classList.remove('input-error');

@@ -596,2 +714,3 @@ if (field === 'key' && value.trim() !== "") {

e.target.classList.add('input-error');
e.target.setAttribute('aria-invalid', 'true');
e.target.value = "";

@@ -610,2 +729,3 @@ }

e.target.classList.add('input-error');
e.target.setAttribute('aria-invalid', 'true');
return;

@@ -631,4 +751,6 @@ }

e.target.classList.add('input-error');
e.target.setAttribute('aria-invalid', 'true');
} else {
e.target.classList.remove('input-error');
e.target.removeAttribute('aria-invalid');
}

@@ -650,21 +772,5 @@ });

shortcutList.appendChild(row);
if (shortcutList) shortcutList.appendChild(row);
}
if (showAllBtn) {
showAllBtn.addEventListener('click', () => {
isShowingAll = !isShowingAll;
window.loadShortcuts();
showAccessibleAlert(isShowingAll ? "Showing all shortcuts" : "Showing current site shortcuts", "info");
});
}
if (addBtn) {
addBtn.addEventListener('click', () => {
showAddShortcutModal();
});
}
document.querySelectorAll('.language-dropdown, .menu-container').forEach(el => el.removeAttribute('tabindex'));
if (deleteAllBtn) {

@@ -689,2 +795,37 @@ deleteAllBtn.addEventListener('click', () => {

}
document.querySelectorAll('.language-dropdown, .menu-container').forEach(el => el.removeAttribute('tabindex'));
// === GLOBAL TAB LOOP ===
document.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
const addModal = document.getElementById('wkb-add-modal');
const confirmModal = document.getElementById('wkb-confirm-modal');
const importModalElement = document.getElementById('import-modal');
if (addModal || confirmModal || (importModalElement && window.getComputedStyle(importModalElement).display !== 'none')) {
return;
}
const focusables = Array.from(document.querySelectorAll('button, a[href], input, select, textarea, [tabindex="0"]'))
.filter(el => {
const style = window.getComputedStyle(el);
return !el.disabled && el.offsetWidth > 0 && el.offsetHeight > 0 && style.visibility !== 'hidden' && style.display !== 'none';
});
if (focusables.length === 0) return;
const first = focusables[0];
const last = focusables[focusables.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
});

@@ -53,4 +53,4 @@ # z.WebKeyBind

### 1. Opening the Interface
- **Chrome:** `Alt + Shift + S`
- **Firefox:** `Alt + Shift + E`
- **Chrome:** `Alt + Shift + W`
- **Firefox:** `Alt + Shift + W`

@@ -101,3 +101,3 @@ ---

- Microsoft edge
- Safari

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet