Files
videospeed/options.js
T
2026-04-09 16:17:27 -04:00

1837 lines
56 KiB
JavaScript

var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
var speederShared =
typeof SpeederShared === "object" && SpeederShared ? SpeederShared : {};
var controllerUtils = speederShared.controllerUtils || {};
var keyBindingUtils = speederShared.keyBindings || {};
var popupControlUtils = speederShared.popupControls || {};
var keyBindings = [];
var bindingCodeAliases = {
Space: "Space",
ArrowLeft: "Left",
ArrowUp: "Up",
ArrowRight: "Right",
ArrowDown: "Down",
Numpad0: "Num 0",
Numpad1: "Num 1",
Numpad2: "Num 2",
Numpad3: "Num 3",
Numpad4: "Num 4",
Numpad5: "Num 5",
Numpad6: "Num 6",
Numpad7: "Num 7",
Numpad8: "Num 8",
Numpad9: "Num 9",
NumpadMultiply: "Num *",
NumpadAdd: "Num +",
NumpadSubtract: "Num -",
NumpadDecimal: "Num .",
NumpadDivide: "Num /",
Backquote: "`",
Minus: "-",
Equal: "=",
BracketLeft: "[",
BracketRight: "]",
Backslash: "\\",
Semicolon: ";",
Quote: "'",
Comma: ",",
Period: ".",
Slash: "/"
};
var modifierKeys = new Set([
"Alt",
"AltGraph",
"Control",
"Fn",
"Hyper",
"Meta",
"OS",
"Shift"
]);
var controllerLocations = Array.isArray(controllerUtils.controllerLocations)
? controllerUtils.controllerLocations.slice()
: [
"top-left",
"top-center",
"top-right",
"middle-right",
"bottom-right",
"bottom-center",
"bottom-left",
"middle-left"
];
var controllerButtonDefs = {
rewind: { icon: "\u00AB", name: "Rewind" },
slower: { icon: "\u2212", name: "Decrease speed" },
faster: { icon: "+", name: "Increase speed" },
advance: { icon: "\u00BB", name: "Advance" },
display: { icon: "\u00D7", name: "Close controller" },
reset: { icon: "\u21BB", name: "Reset speed" },
fast: { icon: "\u2605", name: "Preferred speed" },
nudge: { icon: "\u2713", name: "Subtitle nudge" },
pause: { icon: "\u23EF", name: "Play / Pause" },
muted: { icon: "M", name: "Mute / Unmute" },
louder: { icon: "+", name: "Increase volume" },
softer: { icon: "\u2212", name: "Decrease volume" },
mark: { icon: "\u2691", name: "Set marker" },
jump: { icon: "\u21E5", name: "Jump to marker" },
settings: { icon: "\u2699", name: "Settings" },
};
var popupExcludedButtonIds = new Set(["settings"]);
/** Lucide picker only — not control-bar blocks (chip uses subtitleNudgeOn/Off). */
var lucideSubtitleNudgeActionLabels = {
subtitleNudgeOn: "Subtitle nudge — enabled",
subtitleNudgeOff: "Subtitle nudge — disabled"
};
function sanitizePopupButtonOrder(buttonIds) {
return popupControlUtils.sanitizeButtonOrder(
buttonIds,
controllerButtonDefs,
popupExcludedButtonIds
);
}
/** Cached custom Lucide SVGs (mirrors chrome.storage.local customButtonIcons). */
var customButtonIconsLive = {};
function fillControlBarIconElement(icon, buttonId) {
if (!icon || !buttonId) return;
var doc = icon.ownerDocument || document;
if (buttonId === "nudge") {
vscClearElement(icon);
icon.className = "cb-icon cb-icon-nudge-pair";
function nudgeChipMarkup(action) {
var c = customButtonIconsLive[action];
if (c && c.svg) return c.svg;
if (typeof vscIconSvgString === "function") {
return vscIconSvgString(action, 14) || "";
}
return "";
}
function appendChip(action, stateKey) {
var sp = document.createElement("span");
sp.className = "cb-nudge-chip";
sp.setAttribute("data-nudge-state", stateKey);
var inner = nudgeChipMarkup(action);
if (inner) {
var wrap = vscCreateSvgWrap(doc, inner, "vsc-btn-icon");
if (wrap) {
sp.appendChild(wrap);
}
}
icon.appendChild(sp);
}
appendChip("subtitleNudgeOn", "on");
var sep = document.createElement("span");
sep.className = "cb-nudge-sep";
sep.textContent = "/";
icon.appendChild(sep);
appendChip("subtitleNudgeOff", "off");
return;
}
icon.className = "cb-icon";
var custom = customButtonIconsLive[buttonId];
if (custom && custom.svg) {
if (vscSetSvgContent(icon, custom.svg)) return;
}
if (typeof vscIconSvgString === "function") {
var svgHtml = vscIconSvgString(buttonId, 16);
if (svgHtml) {
if (vscSetSvgContent(icon, svgHtml)) return;
}
}
vscClearElement(icon);
var def = controllerButtonDefs[buttonId];
icon.textContent = (def && def.icon) || "?";
}
function createDefaultBinding(action, code, value) {
return {
action: action,
code: code,
value: value,
force: false,
predefined: true
};
}
var tcDefaults = {
speed: 1.0,
lastSpeed: 1.0,
rememberSpeed: false,
audioBoolean: false,
startHidden: false,
hideWithYouTubeControls: false,
hideWithControls: false,
hideWithControlsTimer: 2.0,
controllerLocation: "top-left",
forceLastSavedSpeed: false,
enabled: true,
controllerOpacity: 0.3,
controllerMarginTop: 0,
controllerMarginRight: 0,
controllerMarginBottom: 65,
controllerMarginLeft: 0,
keyBindings: [
createDefaultBinding("display", "KeyV", 0),
createDefaultBinding("move", "KeyP", 0),
createDefaultBinding("slower", "KeyS", 0.1),
createDefaultBinding("faster", "KeyD", 0.1),
createDefaultBinding("rewind", "KeyZ", 10),
createDefaultBinding("advance", "KeyX", 10),
createDefaultBinding("reset", "KeyR", 1),
createDefaultBinding("fast", "KeyG", 1.8),
createDefaultBinding("toggleSubtitleNudge", "KeyN", 0)
],
siteRules: [
{
pattern: "/^https:\\/\\/(www\\.)?youtube\\.com\\/(?!shorts\\/).*/",
enabled: true,
enableSubtitleNudge: true,
subtitleNudgeInterval: 50
},
{
pattern: "/^https:\\/\\/(www\\.)?youtube\\.com\\/shorts\\/.*/",
enabled: true,
rememberSpeed: true,
controllerMarginTop: 60,
controllerMarginBottom: 85
}
],
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
showPopupControlBar: true,
popupMatchHoverControls: true,
popupControllerButtons: ["rewind", "slower", "faster", "advance", "display"],
enableSubtitleNudge: false,
subtitleNudgeInterval: 50,
subtitleNudgeAmount: 0.001
};
const actionLabels = {
display: "Show/hide controller",
move: "Move controller",
slower: "Decrease speed",
faster: "Increase speed",
rewind: "Rewind",
advance: "Advance",
reset: "Reset speed",
fast: "Preferred speed",
toggleSubtitleNudge: "Toggle subtitle nudge",
pause: "Play / Pause",
muted: "Mute / Unmute",
louder: "Increase volume",
softer: "Decrease volume",
mark: "Set marker",
jump: "Jump to marker"
};
const speedBindingActions = ["slower", "faster", "fast", "softer", "louder"];
const requiredShortcutActions = new Set(["display", "slower", "faster"]);
function formatSpeedBindingDisplay(action, value) {
if (!speedBindingActions.includes(action)) {
return value;
}
var n = Number(value);
if (!isFinite(n)) {
return value;
}
return n.toFixed(2);
}
function getDefaultShortcutValue(action) {
if (action === "louder" || action === "softer") {
return 0.1;
}
var defaultBinding = tcDefaults.keyBindings.find(function (binding) {
return binding.action === action;
});
if (defaultBinding && Number.isFinite(Number(defaultBinding.value))) {
return Number(defaultBinding.value);
}
return 0;
}
function resolveShortcutValue(action, value) {
if (value === undefined || value === null) {
return getDefaultShortcutValue(action);
}
var numericValue = Number(value);
if (Number.isFinite(numericValue)) {
return numericValue;
}
return 0;
}
const customActionsNoValues = [
"reset",
"display",
"move",
"muted",
"pause",
"mark",
"jump",
"toggleSubtitleNudge"
];
function refreshAddShortcutSelector() {
const selector = document.getElementById("addShortcutSelector");
if (!selector) return;
// Clear existing options except the first one
while (selector.options.length > 1) {
selector.remove(1);
}
// Find all currently used actions
const usedActions = new Set();
document.querySelectorAll(".shortcut-row").forEach((row) => {
const action = row.dataset.action;
if (action) {
usedActions.add(action);
}
});
// Add all unused actions
Object.keys(actionLabels).forEach((action) => {
if (!usedActions.has(action)) {
const option = document.createElement("option");
option.value = action;
option.text = actionLabels[action];
selector.appendChild(option);
}
});
// If no available actions, hide or disable the selector
if (selector.options.length === 1) {
selector.disabled = true;
selector.options[0].text = "All shortcuts added";
} else {
selector.disabled = false;
selector.options[0].text = "Add shortcut\u2026";
}
}
function ensureDefaultBinding(storage, action, code, value) {
if (storage.keyBindings.some((item) => item.action === action)) return;
storage.keyBindings.push(createDefaultBinding(action, code, value));
}
function normalizeControllerLocation(location) {
return controllerUtils.normalizeControllerLocation(
location,
tcDefaults.controllerLocation
);
}
function clampMarginPxInput(el, fallback) {
return controllerUtils.clampControllerMarginPx(el && el.value, fallback);
}
function parseFiniteNumberOrFallback(value, fallback) {
var numericValue = parseFloat(value);
return Number.isFinite(numericValue) ? numericValue : fallback;
}
function updateSiteRuleToggleIcon(toggleButton, action) {
if (!toggleButton) return;
var iconEl = toggleButton.querySelector(".site-rule-toggle-icon");
if (!iconEl) return;
if (typeof vscIconSvgString === "function" && typeof vscSetSvgContent === "function") {
var svgHtml = vscIconSvgString(action, 16);
if (svgHtml && vscSetSvgContent(iconEl, svgHtml)) {
return;
}
}
iconEl.textContent = action === "chevronUp" ? "\u2212" : "\u2026";
}
function setSiteRuleExpandedState(ruleEl, expanded) {
if (!ruleEl) return;
var ruleBody = ruleEl.querySelector(".site-rule-body");
var toggleButton = ruleEl.querySelector(".toggle-site-rule");
if (ruleBody) {
ruleBody.style.display = expanded ? "block" : "none";
}
ruleEl.classList.toggle("collapsed", !expanded);
if (!toggleButton) return;
var label = expanded ? "Collapse site rule" : "Expand site rule";
toggleButton.title = label;
toggleButton.setAttribute("aria-label", label);
toggleButton.setAttribute("aria-expanded", expanded ? "true" : "false");
updateSiteRuleToggleIcon(toggleButton, expanded ? "chevronUp" : "moreHorizontal");
}
function setSiteOverrideContainerState(container, enabled) {
if (!container) return;
container.classList.toggle("site-override-disabled", !enabled);
container.setAttribute("aria-disabled", enabled ? "false" : "true");
Array.prototype.forEach.call(
container.querySelectorAll("input, select, textarea, button"),
function (control) {
control.disabled = !enabled;
}
);
Array.prototype.forEach.call(
container.querySelectorAll(".cb-block"),
function (block) {
block.draggable = enabled;
}
);
}
function applySiteRuleOverrideState(ruleEl, checkboxClass, containerClass) {
if (!ruleEl) return;
var checkbox = ruleEl.querySelector("." + checkboxClass);
var container = ruleEl.querySelector("." + containerClass);
if (!container) return;
container.style.display = "block";
setSiteOverrideContainerState(container, checkbox ? checkbox.checked : false);
}
function syncSiteRuleField(ruleEl, rule, key, isCheckbox) {
var input = ruleEl.querySelector(".site-" + key);
if (!input) return;
var globalEl = document.getElementById(key);
var value;
if (rule && rule[key] !== undefined) {
value = rule[key];
} else if (globalEl) {
value = isCheckbox ? globalEl.checked : globalEl.value;
} else {
return;
}
if (isCheckbox) input.checked = Boolean(value);
else input.value = value;
}
function normalizeBindingKey(key) {
return keyBindingUtils.normalizeBindingKey(key);
}
function getLegacyKeyCode(binding) {
return keyBindingUtils.getLegacyKeyCode(binding);
}
function legacyBindingKeyToCode(key) {
return keyBindingUtils.legacyBindingKeyToCode(key);
}
function legacyKeyCodeToCode(keyCode) {
return keyBindingUtils.legacyKeyCodeToCode(keyCode);
}
function inferBindingCode(binding, fallbackCode) {
return keyBindingUtils.inferBindingCode(binding, fallbackCode);
}
function createDisabledBinding() {
return {
code: null,
disabled: true
};
}
function normalizeStoredBinding(binding, fallbackCode) {
if (!binding) {
if (!fallbackCode) return null;
return {
code: fallbackCode,
disabled: false
};
}
if (
binding.disabled === true ||
(binding.code === null &&
binding.key === null &&
binding.keyCode === null)
) {
return createDisabledBinding();
}
var normalizedCode = inferBindingCode(binding, fallbackCode);
if (!normalizedCode) {
return null;
}
var normalized = {
code: normalizedCode,
disabled: false
};
return normalized;
}
function formatBindingCode(code) {
if (typeof code !== "string" || code.length === 0) return "";
if (bindingCodeAliases[code]) return bindingCodeAliases[code];
if (/^Key[A-Z]$/.test(code)) return code.substring(3);
if (/^Digit[0-9]$/.test(code)) return code.substring(5);
if (/^F([1-9]|1[0-2])$/.test(code)) return code;
return code;
}
function getBindingLabel(binding) {
if (!binding) return "";
if (binding.disabled) return "";
return formatBindingCode(binding.code);
}
function setShortcutInputBinding(input, binding) {
input.vscBinding = binding ? Object.assign({}, binding) : null;
input.value = getBindingLabel(binding);
}
function captureBindingFromEvent(event) {
if (modifierKeys.has(event.key)) return null;
if (typeof event.code !== "string" || event.code.length === 0) return null;
return {
code: event.code,
disabled: false
};
}
function recordKeyPress(event) {
if (event.key === "Tab") return;
if (event.key === "Backspace") {
setShortcutInputBinding(event.target, null);
event.preventDefault();
event.stopPropagation();
return;
}
if (event.key === "Escape") {
setShortcutInputBinding(event.target, createDisabledBinding());
event.preventDefault();
event.stopPropagation();
return;
}
var binding = captureBindingFromEvent(event);
if (!binding) return;
setShortcutInputBinding(event.target, binding);
event.preventDefault();
event.stopPropagation();
}
function inputFilterNumbersOnly(event) {
var char = event.key;
if (
typeof char !== "string" ||
char.length !== 1 ||
!/[\d\.]$/.test(char) ||
!/^\d+(\.\d*)?$/.test(event.target.value + char)
) {
event.preventDefault();
event.stopPropagation();
}
}
function inputFocus(event) {
event.target.value = "";
}
function inputBlur(event) {
setShortcutInputBinding(event.target, event.target.vscBinding || null);
}
function updateCustomShortcutInputText(inputItem, bindingOrKeyCode) {
if (
bindingOrKeyCode &&
typeof bindingOrKeyCode === "object" &&
!Array.isArray(bindingOrKeyCode)
) {
setShortcutInputBinding(inputItem, bindingOrKeyCode);
return;
}
if (typeof bindingOrKeyCode === "string") {
setShortcutInputBinding(inputItem, { code: bindingOrKeyCode, disabled: false });
return;
}
setShortcutInputBinding(
inputItem,
normalizeStoredBinding({ keyCode: bindingOrKeyCode })
);
}
function appendSelectOptions(select, options) {
options.forEach(function (optionData) {
var option = document.createElement("option");
option.value = optionData.value;
option.textContent = optionData.label;
select.appendChild(option);
});
}
function add_shortcut(action, value) {
if (!action) return;
var div = document.createElement("div");
div.setAttribute("class", "shortcut-row customs");
div.dataset.action = action;
var actionLabel = document.createElement("div");
actionLabel.className = "shortcut-label";
actionLabel.textContent = actionLabels[action] || action;
var keyInput = document.createElement("input");
keyInput.className = "customKey";
keyInput.type = "text";
keyInput.placeholder = "press a key";
var valueInput = document.createElement("input");
valueInput.className = "customValue";
valueInput.type = "text";
valueInput.placeholder = "value";
if (customActionsNoValues.includes(action)) {
valueInput.value = "N/A";
valueInput.disabled = true;
} else {
valueInput.value = formatSpeedBindingDisplay(
action,
resolveShortcutValue(action, value)
);
}
var removeButton = document.createElement("button");
removeButton.className = "removeParent";
removeButton.type = "button";
removeButton.textContent = "\u00d7";
div.appendChild(actionLabel);
div.appendChild(keyInput);
div.appendChild(valueInput);
div.appendChild(removeButton);
var customsElement = document.querySelector(".shortcuts-grid");
customsElement.appendChild(div);
refreshAddShortcutSelector();
}
function createKeyBindings(item) {
var action = item.dataset.action || item.querySelector(".customDo").value;
var input = item.querySelector(".customKey");
var valueInput = item.querySelector(".customValue");
var predefined = !!item.id;
var binding = normalizeStoredBinding(input.vscBinding);
if (!binding) {
if (requiredShortcutActions.has(action)) {
return {
valid: false,
message:
"Error: Shortcut for " +
(actionLabels[action] || action) +
" cannot be empty. Unable to save"
};
}
binding = createDisabledBinding();
}
if (binding.disabled === true && requiredShortcutActions.has(action)) {
return {
valid: false,
message:
"Error: Shortcut for " +
(actionLabels[action] || action) +
" cannot be empty. Unable to save"
};
}
keyBindings.push({
action: action,
code: binding.code,
disabled: binding.disabled === true,
value: customActionsNoValues.includes(action)
? 0
: Number(valueInput.value),
force: false,
predefined: predefined
});
return { valid: true };
}
function validate() {
var valid = true;
var status = document.getElementById("status");
// Validate site rules patterns
document.querySelectorAll(".site-rule").forEach((ruleEl) => {
var pattern = ruleEl.querySelector(".site-pattern").value.trim();
if (pattern.length === 0) return;
if (pattern.startsWith("/")) {
try {
var lastSlash = pattern.lastIndexOf("/");
if (lastSlash > 0) {
new RegExp(pattern.substring(1, lastSlash), pattern.substring(lastSlash + 1));
}
} catch (err) {
status.textContent =
"Error: Invalid site rule regex: " + pattern + ". Unable to save";
valid = false;
return;
}
}
});
return valid;
}
function save_options() {
if (validate() === false) return;
keyBindings = [];
var status = document.getElementById("status");
var saveError = null;
// Collect shortcuts from the main shortcuts section (both default and custom)
Array.from(document.querySelectorAll("#customs .shortcut-row")).forEach((item) => {
if (saveError) return;
var result = createKeyBindings(item);
if (!result.valid) saveError = result.message;
});
if (saveError) {
status.textContent = saveError;
return;
}
var settings = {};
settings.rememberSpeed = document.getElementById("rememberSpeed").checked;
settings.forceLastSavedSpeed =
document.getElementById("forceLastSavedSpeed").checked;
settings.audioBoolean = document.getElementById("audioBoolean").checked;
settings.enabled = document.getElementById("enabled").checked;
settings.startHidden = document.getElementById("startHidden").checked;
settings.hideWithControls = document.getElementById("hideWithControls").checked;
settings.hideWithControlsTimer =
Math.min(15, Math.max(0.1, parseFloat(document.getElementById("hideWithControlsTimer").value) || tcDefaults.hideWithControlsTimer));
// Sync back to the legacy key if it exists, for backward compatibility
settings.hideWithYouTubeControls = settings.hideWithControls;
if (settings.hideWithControlsTimer < 0.1) settings.hideWithControlsTimer = 0.1;
if (settings.hideWithControlsTimer > 15) settings.hideWithControlsTimer = 15;
settings.controllerLocation = normalizeControllerLocation(
document.getElementById("controllerLocation").value
);
settings.controllerOpacity =
parseFiniteNumberOrFallback(
document.getElementById("controllerOpacity").value,
tcDefaults.controllerOpacity
);
settings.controllerMarginTop = clampMarginPxInput(
document.getElementById("controllerMarginTop"),
tcDefaults.controllerMarginTop
);
settings.controllerMarginBottom = clampMarginPxInput(
document.getElementById("controllerMarginBottom"),
tcDefaults.controllerMarginBottom
);
settings.keyBindings = keyBindings;
settings.enableSubtitleNudge =
document.getElementById("enableSubtitleNudge").checked;
settings.subtitleNudgeInterval =
parseInt(document.getElementById("subtitleNudgeInterval").value, 10) ||
tcDefaults.subtitleNudgeInterval;
settings.subtitleNudgeAmount = tcDefaults.subtitleNudgeAmount;
if (settings.subtitleNudgeInterval < 10) {
settings.subtitleNudgeInterval = 10;
}
if (settings.subtitleNudgeInterval > 1000) {
settings.subtitleNudgeInterval = 1000;
}
settings.controllerButtons = getControlBarOrder();
settings.showPopupControlBar =
document.getElementById("showPopupControlBar").checked;
settings.popupMatchHoverControls =
document.getElementById("popupMatchHoverControls").checked;
settings.popupControllerButtons = sanitizePopupButtonOrder(getPopupControlBarOrder());
// Collect site rules
settings.siteRules = [];
document.querySelectorAll(".site-rule").forEach((ruleEl) => {
var pattern = ruleEl.querySelector(".site-pattern").value.trim();
if (pattern.length === 0) return;
var rule = { pattern: pattern };
// Handle Enable toggle
rule.enabled = ruleEl.querySelector(".site-enabled").checked;
if (ruleEl.querySelector(".override-placement").checked) {
rule.controllerLocation = normalizeControllerLocation(
ruleEl.querySelector(".site-controllerLocation").value
);
rule.controllerMarginTop = clampMarginPxInput(
ruleEl.querySelector(".site-controllerMarginTop"),
clampMarginPxInput(
document.getElementById("controllerMarginTop"),
tcDefaults.controllerMarginTop
)
);
rule.controllerMarginBottom = clampMarginPxInput(
ruleEl.querySelector(".site-controllerMarginBottom"),
clampMarginPxInput(
document.getElementById("controllerMarginBottom"),
tcDefaults.controllerMarginBottom
)
);
}
if (ruleEl.querySelector(".override-visibility").checked) {
rule.startHidden = ruleEl.querySelector(".site-startHidden").checked;
}
if (ruleEl.querySelector(".override-autohide").checked) {
rule.hideWithControls = ruleEl.querySelector(".site-hideWithControls").checked;
var st = parseFloat(
ruleEl.querySelector(".site-hideWithControlsTimer").value
);
rule.hideWithControlsTimer = Math.min(
15,
Math.max(0.1, Number.isFinite(st) ? st : settings.hideWithControlsTimer)
);
}
if (ruleEl.querySelector(".override-playback").checked) {
rule.rememberSpeed = ruleEl.querySelector(".site-rememberSpeed").checked;
rule.forceLastSavedSpeed =
ruleEl.querySelector(".site-forceLastSavedSpeed").checked;
rule.audioBoolean = ruleEl.querySelector(".site-audioBoolean").checked;
}
if (ruleEl.querySelector(".override-opacity").checked) {
rule.controllerOpacity =
parseFiniteNumberOrFallback(
ruleEl.querySelector(".site-controllerOpacity").value,
settings.controllerOpacity
);
}
if (ruleEl.querySelector(".override-subtitleNudge").checked) {
rule.enableSubtitleNudge =
ruleEl.querySelector(".site-enableSubtitleNudge").checked;
var nudgeIv = parseInt(
ruleEl.querySelector(".site-subtitleNudgeInterval").value,
10
);
rule.subtitleNudgeInterval = Math.min(
1000,
Math.max(
10,
Number.isFinite(nudgeIv) ? nudgeIv : settings.subtitleNudgeInterval
)
);
}
if (ruleEl.querySelector(".override-controlbar").checked) {
var activeZone = ruleEl.querySelector(".site-cb-active");
if (activeZone) {
rule.controllerButtons = readControlBarOrder(activeZone);
}
}
if (ruleEl.querySelector(".override-popup-controlbar").checked) {
rule.showPopupControlBar =
ruleEl.querySelector(".site-showPopupControlBar").checked;
var popupActiveZone = ruleEl.querySelector(".site-popup-cb-active");
if (popupActiveZone) {
rule.popupControllerButtons = sanitizePopupButtonOrder(
readControlBarOrder(popupActiveZone)
);
}
}
if (ruleEl.querySelector(".override-shortcuts").checked) {
var shortcuts = [];
ruleEl.querySelectorAll(".site-shortcuts-container .customs").forEach((shortcutRow) => {
if (saveError) return;
var action = shortcutRow.dataset.action;
var keyInput = shortcutRow.querySelector(".customKey");
var valueInput = shortcutRow.querySelector(".customValue");
var forceCheckbox = shortcutRow.querySelector(".customForce");
var binding = normalizeStoredBinding(keyInput.vscBinding);
if (!binding) {
if (requiredShortcutActions.has(action)) {
saveError =
"Error: Site rule shortcut for " +
(actionLabels[action] || action) +
" cannot be empty. Unable to save";
return;
}
binding = createDisabledBinding();
}
if (binding.disabled === true && requiredShortcutActions.has(action)) {
saveError =
"Error: Site rule shortcut for " +
(actionLabels[action] || action) +
" cannot be empty. Unable to save";
return;
}
shortcuts.push({
action: action,
code: binding.code,
disabled: binding.disabled === true,
value: customActionsNoValues.includes(action)
? 0
: Number(valueInput.value),
force: forceCheckbox ? forceCheckbox.checked : false
});
});
if (saveError) return;
if (shortcuts.length > 0) rule.shortcuts = shortcuts;
}
settings.siteRules.push(rule);
});
// Legacy keys to remove
const legacyKeys = [
"resetSpeed", "speedStep", "fastSpeed", "rewindTime", "advanceTime",
"resetKeyCode", "slowerKeyCode", "fasterKeyCode", "rewindKeyCode",
"advanceKeyCode", "fastKeyCode", "blacklist"
];
chrome.storage.sync.remove(legacyKeys, function () {
chrome.storage.sync.set(settings, function () {
status.textContent = "Options saved";
setTimeout(function () {
status.textContent = "";
}, 1000);
});
});
}
function ensureAllDefaultBindings(storage) {
tcDefaults.keyBindings.forEach((binding) => {
ensureDefaultBinding(storage, binding.action, binding.code, binding.value);
});
}
function addSiteRuleShortcut(container, action, binding, value, force) {
var div = document.createElement("div");
div.setAttribute("class", "shortcut-row customs");
div.dataset.action = action;
var actionLabel = document.createElement("div");
actionLabel.className = "shortcut-label";
var actionLabels = {
display: "Show/hide controller",
move: "Move controller",
slower: "Decrease speed",
faster: "Increase speed",
rewind: "Rewind",
advance: "Advance",
reset: "Reset speed",
fast: "Preferred speed",
toggleSubtitleNudge: "Toggle subtitle nudge",
pause: "Play / Pause",
muted: "Mute / Unmute",
louder: "Increase volume",
softer: "Decrease volume",
mark: "Set marker",
jump: "Jump to marker"
};
var actionLabelText = actionLabels[action] || action;
if (action === "toggleSubtitleNudge") {
// Check if the site rule is for YouTube.
// We look up the pattern from the site rule element this container belongs to.
var ruleEl = container.closest(".site-rule");
var pattern = ruleEl ? ruleEl.querySelector(".site-pattern").value : "";
if (!pattern.toLowerCase().includes("youtube.com")) {
actionLabelText += " (only for YouTube embeds)";
}
}
actionLabel.textContent = actionLabelText;
var keyInput = document.createElement("input");
keyInput.className = "customKey";
keyInput.type = "text";
keyInput.placeholder = "press a key";
updateCustomShortcutInputText(keyInput, binding || createDisabledBinding());
var valueInput = document.createElement("input");
valueInput.className = "customValue";
valueInput.type = "text";
valueInput.placeholder = "value (0.10)";
if (customActionsNoValues.includes(action)) {
valueInput.value = "N/A";
valueInput.disabled = true;
} else {
valueInput.value = formatSpeedBindingDisplay(
action,
resolveShortcutValue(action, value)
);
}
var forceLabel = document.createElement("label");
forceLabel.className = "force-label";
forceLabel.title = "Prevent website from capturing this key";
var forceCheckbox = document.createElement("input");
forceCheckbox.type = "checkbox";
forceCheckbox.className = "customForce";
forceCheckbox.checked = force === true || force === "true";
var forceText = document.createElement("span");
forceText.textContent = "Block site from capturing keypress";
forceText.className = "force-text";
forceLabel.appendChild(forceCheckbox);
forceLabel.appendChild(forceText);
div.appendChild(actionLabel);
div.appendChild(keyInput);
div.appendChild(valueInput);
div.appendChild(forceLabel);
container.appendChild(div);
}
function createSiteRule(rule) {
var template = document.getElementById("siteRuleTemplate");
var clone = template.content.cloneNode(true);
var ruleEl = clone.querySelector(".site-rule");
var pattern = rule && rule.pattern ? rule.pattern : "";
ruleEl.querySelector(".site-pattern").value = pattern;
// Make the rule body collapsed by default
setSiteRuleExpandedState(ruleEl, false);
var enabledCheckbox = ruleEl.querySelector(".site-enabled");
var contentEl = ruleEl.querySelector(".site-rule-content");
function updateDisabledState() {
if (enabledCheckbox.checked) {
contentEl.classList.remove("disabled-rule");
} else {
contentEl.classList.add("disabled-rule");
}
}
enabledCheckbox.addEventListener("change", updateDisabledState);
if (rule) {
if (rule.enabled !== undefined) {
enabledCheckbox.checked = rule.enabled;
} else if (rule.disableExtension !== undefined) {
enabledCheckbox.checked = !rule.disableExtension;
} else {
enabledCheckbox.checked = true;
}
} else {
enabledCheckbox.checked = true;
}
updateDisabledState();
var placementKeys = [
"controllerLocation",
"controllerMarginTop",
"controllerMarginBottom"
];
var hasPlacementOverride =
rule && placementKeys.some(function (k) { return rule[k] !== undefined; });
ruleEl.querySelector(".override-placement").checked = Boolean(hasPlacementOverride);
syncSiteRuleField(ruleEl, rule, "controllerLocation", false);
syncSiteRuleField(ruleEl, rule, "controllerMarginTop", false);
syncSiteRuleField(ruleEl, rule, "controllerMarginBottom", false);
applySiteRuleOverrideState(ruleEl, "override-placement", "site-placement-container");
ruleEl.querySelector(".override-visibility").checked = Boolean(
rule && rule.startHidden !== undefined
);
syncSiteRuleField(ruleEl, rule, "startHidden", true);
applySiteRuleOverrideState(ruleEl, "override-visibility", "site-visibility-container");
var hasAutohideOverride = Boolean(
rule &&
(rule.hideWithControls !== undefined ||
rule.hideWithControlsTimer !== undefined)
);
ruleEl.querySelector(".override-autohide").checked = hasAutohideOverride;
syncSiteRuleField(ruleEl, rule, "hideWithControls", true);
syncSiteRuleField(ruleEl, rule, "hideWithControlsTimer", false);
applySiteRuleOverrideState(ruleEl, "override-autohide", "site-autohide-container");
var hasPlaybackOverride = Boolean(
rule &&
(rule.rememberSpeed !== undefined ||
rule.forceLastSavedSpeed !== undefined ||
rule.audioBoolean !== undefined)
);
ruleEl.querySelector(".override-playback").checked = hasPlaybackOverride;
syncSiteRuleField(ruleEl, rule, "rememberSpeed", true);
syncSiteRuleField(ruleEl, rule, "forceLastSavedSpeed", true);
syncSiteRuleField(ruleEl, rule, "audioBoolean", true);
applySiteRuleOverrideState(ruleEl, "override-playback", "site-playback-container");
ruleEl.querySelector(".override-opacity").checked = Boolean(
rule && rule.controllerOpacity !== undefined
);
syncSiteRuleField(ruleEl, rule, "controllerOpacity", false);
applySiteRuleOverrideState(ruleEl, "override-opacity", "site-opacity-container");
var hasSubtitleNudgeOverride = Boolean(
rule &&
(rule.enableSubtitleNudge !== undefined ||
rule.subtitleNudgeInterval !== undefined)
);
ruleEl.querySelector(".override-subtitleNudge").checked = hasSubtitleNudgeOverride;
syncSiteRuleField(ruleEl, rule, "enableSubtitleNudge", true);
syncSiteRuleField(ruleEl, rule, "subtitleNudgeInterval", false);
applySiteRuleOverrideState(
ruleEl,
"override-subtitleNudge",
"site-subtitleNudge-container"
);
var hasControlbarOverride = Boolean(rule && Array.isArray(rule.controllerButtons));
ruleEl.querySelector(".override-controlbar").checked = hasControlbarOverride;
populateControlBarZones(
ruleEl.querySelector(".site-cb-active"),
ruleEl.querySelector(".site-cb-available"),
hasControlbarOverride ? rule.controllerButtons : getControlBarOrder()
);
applySiteRuleOverrideState(ruleEl, "override-controlbar", "site-controlbar-container");
var hasPopupControlbarOverride = Boolean(
rule &&
(rule.showPopupControlBar !== undefined ||
Array.isArray(rule.popupControllerButtons))
);
ruleEl.querySelector(".override-popup-controlbar").checked =
hasPopupControlbarOverride;
populateControlBarZones(
ruleEl.querySelector(".site-popup-cb-active"),
ruleEl.querySelector(".site-popup-cb-available"),
hasPopupControlbarOverride && Array.isArray(rule.popupControllerButtons)
? sanitizePopupButtonOrder(rule.popupControllerButtons)
: getPopupControlBarOrder(),
function (id) {
return !popupExcludedButtonIds.has(id);
}
);
syncSiteRuleField(ruleEl, rule, "showPopupControlBar", true);
applySiteRuleOverrideState(
ruleEl,
"override-popup-controlbar",
"site-popup-controlbar-container"
);
var hasShortcutOverride = Boolean(
rule && Array.isArray(rule.shortcuts) && rule.shortcuts.length > 0
);
ruleEl.querySelector(".override-shortcuts").checked = hasShortcutOverride;
var container = ruleEl.querySelector(".site-shortcuts-container");
if (hasShortcutOverride) {
rule.shortcuts.forEach((shortcut) => {
addSiteRuleShortcut(
container,
shortcut.action,
shortcut,
shortcut.value,
shortcut.force
);
});
} else {
populateDefaultSiteShortcuts(container);
}
applySiteRuleOverrideState(ruleEl, "override-shortcuts", "site-shortcuts-container");
document.getElementById("siteRulesContainer").appendChild(ruleEl);
}
function populateDefaultSiteShortcuts(container) {
var bindings = [];
document.querySelectorAll("#customs .shortcut-row").forEach((row) => {
var action = row.dataset.action;
if (!action) return;
var keyInput = row.querySelector(".customKey");
var binding = normalizeStoredBinding(keyInput && keyInput.vscBinding);
if (!binding) return;
var valueInput = row.querySelector(".customValue");
bindings.push({
action: action,
code: binding.code,
disabled: binding.disabled === true,
value: customActionsNoValues.includes(action)
? 0
: Number(valueInput && valueInput.value),
force: false
});
});
if (bindings.length === 0) {
bindings = tcDefaults.keyBindings.slice();
}
bindings.forEach((binding) => {
addSiteRuleShortcut(container, binding.action, binding, binding.value, false);
});
}
function createControlBarBlock(buttonId) {
var def = controllerButtonDefs[buttonId];
if (!def) return null;
var block = document.createElement("div");
block.className = "cb-block";
block.dataset.buttonId = buttonId;
block.draggable = true;
var grip = document.createElement("span");
grip.className = "cb-grip";
var icon = document.createElement("span");
icon.className = "cb-icon";
fillControlBarIconElement(icon, buttonId);
var label = document.createElement("span");
label.className = "cb-label";
label.textContent = def.name;
block.appendChild(grip);
block.appendChild(icon);
block.appendChild(label);
return block;
}
function populateControlBarZones(activeZone, availableZone, activeIds, allowButtonId) {
vscClearElement(activeZone);
vscClearElement(availableZone);
var allowed = function (id) {
if (!controllerButtonDefs[id]) return false;
return typeof allowButtonId === "function" ? Boolean(allowButtonId(id)) : true;
};
activeIds.forEach(function (id) {
if (!allowed(id)) return;
var block = createControlBarBlock(id);
if (block) activeZone.appendChild(block);
});
Object.keys(controllerButtonDefs).forEach(function (id) {
if (!allowed(id)) return;
if (!activeIds.includes(id)) {
var block = createControlBarBlock(id);
if (block) availableZone.appendChild(block);
}
});
}
function readControlBarOrder(activeZone) {
var blocks = activeZone.querySelectorAll(".cb-block");
return Array.from(blocks).map(function (block) {
return block.dataset.buttonId;
});
}
function populateControlBarEditor(activeIds) {
populateControlBarZones(
document.getElementById("controlBarActive"),
document.getElementById("controlBarAvailable"),
activeIds
);
}
function getControlBarOrder() {
return readControlBarOrder(document.getElementById("controlBarActive"));
}
function populatePopupControlBarEditor(activeIds) {
var popupActiveIds = sanitizePopupButtonOrder(activeIds);
populateControlBarZones(
document.getElementById("popupControlBarActive"),
document.getElementById("popupControlBarAvailable"),
popupActiveIds,
function (id) {
return !popupExcludedButtonIds.has(id);
}
);
}
function getPopupControlBarOrder() {
return sanitizePopupButtonOrder(
readControlBarOrder(document.getElementById("popupControlBarActive"))
);
}
function updatePopupEditorDisabledState() {
var checkbox = document.getElementById("popupMatchHoverControls");
var wrap = document.getElementById("popupCbEditorWrap");
if (!checkbox || !wrap) return;
if (checkbox.checked) {
wrap.classList.add("cb-editor-disabled");
} else {
wrap.classList.remove("cb-editor-disabled");
}
}
function getDragAfterElement(container, x, y) {
var elements = Array.from(
container.querySelectorAll(".cb-block:not(.cb-dragging)")
);
for (var i = 0; i < elements.length; i++) {
var box = elements[i].getBoundingClientRect();
var centerX = box.left + box.width / 2;
var centerY = box.top + box.height / 2;
var rowThresh = box.height * 0.5;
if (y - centerY > rowThresh) continue;
if (centerY - y > rowThresh) return elements[i];
if (x < centerX) return elements[i];
}
return undefined;
}
function initControlBarEditor() {
var draggedBlock = null;
function clearControlBarDropTargets(activeZone) {
document.querySelectorAll(".cb-dropzone.cb-over").forEach(function (zone) {
if (zone !== activeZone) {
zone.classList.remove("cb-over");
}
});
}
document.addEventListener("dragstart", function (e) {
var block = e.target.closest(".cb-block");
if (!block) return;
draggedBlock = block;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", block.dataset.buttonId);
requestAnimationFrame(function () {
block.classList.add("cb-dragging");
});
});
document.addEventListener("dragend", function (e) {
var block = e.target.closest(".cb-block");
if (!block) return;
block.classList.remove("cb-dragging");
draggedBlock = null;
clearControlBarDropTargets(null);
});
document.addEventListener("dragover", function (e) {
var zone = e.target.closest(".cb-dropzone");
if (!zone) {
clearControlBarDropTargets(null);
return;
}
e.preventDefault();
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "move";
}
clearControlBarDropTargets(zone);
zone.classList.add("cb-over");
if (!draggedBlock) return;
var afterEl = getDragAfterElement(zone, e.clientX, e.clientY);
if (afterEl) {
zone.insertBefore(draggedBlock, afterEl);
} else {
zone.appendChild(draggedBlock);
}
});
document.addEventListener("drop", function (e) {
var zone = e.target.closest(".cb-dropzone");
if (zone) {
e.preventDefault();
}
clearControlBarDropTargets(null);
});
}
var lucidePickerSelectedSlug = null;
var lucideSearchTimer = null;
function setLucideStatus(msg) {
var el = document.getElementById("lucideIconStatus");
if (el) el.textContent = msg || "";
}
function repaintAllCbIconsFromCustomMap() {
document.querySelectorAll(".cb-block .cb-icon").forEach(function (icon) {
var block = icon.closest(".cb-block");
if (!block) return;
fillControlBarIconElement(icon, block.dataset.buttonId);
});
}
function persistCustomButtonIcons(map, callback) {
chrome.storage.local.set({ customButtonIcons: map }, function () {
if (chrome.runtime.lastError) {
setLucideStatus(
"Could not save icons: " + chrome.runtime.lastError.message
);
return;
}
customButtonIconsLive = map;
if (callback) callback();
repaintAllCbIconsFromCustomMap();
});
}
function initLucideButtonIconsUI() {
var actionSel = document.getElementById("lucideIconActionSelect");
var searchInput = document.getElementById("lucideIconSearch");
var resultsEl = document.getElementById("lucideIconResults");
var previewEl = document.getElementById("lucideIconPreview");
if (!actionSel || !searchInput || !resultsEl || !previewEl) return;
if (typeof getLucideTagsMap !== "function") return;
if (!actionSel.dataset.lucideInit) {
actionSel.dataset.lucideInit = "1";
vscClearElement(actionSel);
Object.keys(controllerButtonDefs).forEach(function (aid) {
if (aid === "nudge") {
Object.keys(lucideSubtitleNudgeActionLabels).forEach(function (subId) {
var o2 = document.createElement("option");
o2.value = subId;
o2.textContent =
lucideSubtitleNudgeActionLabels[subId] + " (" + subId + ")";
actionSel.appendChild(o2);
});
return;
}
var o = document.createElement("option");
o.value = aid;
o.textContent =
controllerButtonDefs[aid].name + " (" + aid + ")";
actionSel.appendChild(o);
});
}
function renderResults(slugs) {
vscClearElement(resultsEl);
slugs.forEach(function (slug) {
var b = document.createElement("button");
b.type = "button";
b.className = "lucide-result-tile";
b.dataset.slug = slug;
b.title = slug;
b.setAttribute("aria-label", slug);
if (slug === lucidePickerSelectedSlug) {
b.classList.add("lucide-picked");
}
var url =
typeof lucideIconSvgUrl === "function" ? lucideIconSvgUrl(slug) : "";
if (url) {
var img = document.createElement("img");
img.className = "lucide-result-thumb";
img.src = url;
img.alt = "";
img.loading = "lazy";
b.appendChild(img);
} else {
b.textContent = slug.slice(0, 3);
}
b.addEventListener("click", function () {
lucidePickerSelectedSlug = slug;
Array.prototype.forEach.call(
resultsEl.querySelectorAll("button"),
function (x) {
x.classList.toggle("lucide-picked", x.dataset.slug === slug);
}
);
fetchLucideSvg(slug)
.then(function (txt) {
var safe = sanitizeLucideSvg(txt);
if (!safe) throw new Error("Bad SVG");
if (!vscSetSvgContent(previewEl, safe)) {
throw new Error("Preview render failed");
}
setLucideStatus("Preview: " + slug);
})
.catch(function (e) {
vscClearElement(previewEl);
setLucideStatus(
"Could not load: " + slug + " — " + e.message
);
});
});
resultsEl.appendChild(b);
});
}
if (!searchInput.dataset.lucideBound) {
searchInput.dataset.lucideBound = "1";
searchInput.addEventListener("input", function () {
clearTimeout(lucideSearchTimer);
lucideSearchTimer = setTimeout(function () {
getLucideTagsMap(chrome.storage.local, false)
.then(function (map) {
var q = searchInput.value;
if (!q.trim()) {
vscClearElement(resultsEl);
return;
}
renderResults(searchLucideSlugs(map, q, 48));
})
.catch(function (e) {
setLucideStatus("Icon list error: " + e.message);
});
}, 200);
});
}
var applyBtn = document.getElementById("lucideIconApply");
if (applyBtn && !applyBtn.dataset.lucideBound) {
applyBtn.dataset.lucideBound = "1";
applyBtn.addEventListener("click", function () {
var action = actionSel.value;
var slug = lucidePickerSelectedSlug;
if (!action || !slug) {
setLucideStatus("Pick an action and click an icon first.");
return;
}
fetchLucideSvg(slug)
.then(function (txt) {
var safe = sanitizeLucideSvg(txt);
if (!safe) throw new Error("Sanitize failed");
var next = Object.assign({}, customButtonIconsLive);
next[action] = { slug: slug, svg: safe };
persistCustomButtonIcons(next, function () {
setLucideStatus(
"Saved " +
slug +
" for " +
action +
". Reload pages for the hover bar."
);
});
})
.catch(function (e) {
setLucideStatus("Apply failed: " + e.message);
});
});
}
var clrOne = document.getElementById("lucideIconClearAction");
if (clrOne && !clrOne.dataset.lucideBound) {
clrOne.dataset.lucideBound = "1";
clrOne.addEventListener("click", function () {
var action = actionSel.value;
if (!action) return;
var next = Object.assign({}, customButtonIconsLive);
delete next[action];
persistCustomButtonIcons(next, function () {
setLucideStatus("Cleared custom icon for " + action + ".");
});
});
}
var clrAll = document.getElementById("lucideIconClearAll");
if (clrAll && !clrAll.dataset.lucideBound) {
clrAll.dataset.lucideBound = "1";
clrAll.addEventListener("click", function () {
persistCustomButtonIcons({}, function () {
setLucideStatus("All custom icons cleared.");
});
});
}
var reloadTags = document.getElementById("lucideIconReloadTags");
if (reloadTags && !reloadTags.dataset.lucideBound) {
reloadTags.dataset.lucideBound = "1";
reloadTags.addEventListener("click", function () {
getLucideTagsMap(chrome.storage.local, true)
.then(function () {
setLucideStatus("Icon name list refreshed.");
})
.catch(function (e) {
setLucideStatus("Refresh failed: " + e.message);
});
});
}
}
function restore_options() {
chrome.storage.sync.get(tcDefaults, function (storage) {
chrome.storage.local.get(["customButtonIcons"], function (loc) {
customButtonIconsLive =
loc && loc.customButtonIcons && typeof loc.customButtonIcons === "object"
? loc.customButtonIcons
: {};
document.getElementById("rememberSpeed").checked = storage.rememberSpeed;
document.getElementById("forceLastSavedSpeed").checked =
storage.forceLastSavedSpeed;
document.getElementById("audioBoolean").checked = storage.audioBoolean;
document.getElementById("enabled").checked = storage.enabled;
document.getElementById("startHidden").checked = storage.startHidden;
// Migration/Normalization for hideWithControls
const hideWithControls = typeof storage.hideWithControls !== "undefined"
? storage.hideWithControls
: storage.hideWithYouTubeControls;
document.getElementById("hideWithControls").checked = hideWithControls;
document.getElementById("hideWithControlsTimer").value =
storage.hideWithControlsTimer || tcDefaults.hideWithControlsTimer;
document.getElementById("controllerLocation").value =
normalizeControllerLocation(storage.controllerLocation);
document.getElementById("controllerOpacity").value =
storage.controllerOpacity;
document.getElementById("controllerMarginTop").value =
storage.controllerMarginTop ?? tcDefaults.controllerMarginTop;
document.getElementById("controllerMarginBottom").value =
storage.controllerMarginBottom ?? tcDefaults.controllerMarginBottom;
document.getElementById("showPopupControlBar").checked =
storage.showPopupControlBar !== false;
document.getElementById("enableSubtitleNudge").checked =
storage.enableSubtitleNudge;
document.getElementById("subtitleNudgeInterval").value =
storage.subtitleNudgeInterval;
if (!Array.isArray(storage.keyBindings) || storage.keyBindings.length === 0) {
storage.keyBindings = tcDefaults.keyBindings.slice();
}
ensureAllDefaultBindings(storage);
document.querySelectorAll(".customs:not([id])").forEach((row) => row.remove());
storage.keyBindings.forEach((item) => {
var row = document.getElementById(item.action);
var normalizedBinding = normalizeStoredBinding(item);
if (!row) {
add_shortcut(item.action, item.value);
row = document.querySelector(".shortcut-row.customs:last-of-type");
}
if (!row) return;
var keyInput = row.querySelector(".customKey");
if (keyInput) {
updateCustomShortcutInputText(keyInput, normalizedBinding || null);
}
var valueInput = row.querySelector(".customValue");
if (customActionsNoValues.includes(item.action)) {
if (valueInput) {
valueInput.value = "N/A";
valueInput.disabled = true;
}
} else if (valueInput) {
valueInput.value = formatSpeedBindingDisplay(item.action, item.value);
}
});
refreshAddShortcutSelector();
// Load site rules (use defaults if none in storage or empty array)
var siteRules =
Array.isArray(storage.siteRules) && storage.siteRules.length > 0
? storage.siteRules
: tcDefaults.siteRules || [];
vscClearElement(document.getElementById("siteRulesContainer"));
if (siteRules.length > 0) {
siteRules.forEach((rule) => {
if (rule && rule.pattern) {
createSiteRule(rule);
}
});
}
var controllerButtons = Array.isArray(storage.controllerButtons)
? storage.controllerButtons
: tcDefaults.controllerButtons;
populateControlBarEditor(controllerButtons);
document.getElementById("popupMatchHoverControls").checked =
storage.popupMatchHoverControls !== false;
var popupButtons = Array.isArray(storage.popupControllerButtons)
? storage.popupControllerButtons
: tcDefaults.popupControllerButtons;
populatePopupControlBarEditor(popupButtons);
updatePopupEditorDisabledState();
initLucideButtonIconsUI();
});
});
}
function restore_defaults() {
document.querySelectorAll(".customs:not([id])").forEach((el) => el.remove());
chrome.storage.local.remove(
["customButtonIcons", "lucideTagsCacheV1", "lucideTagsCacheV1At"],
function () {}
);
chrome.storage.sync.set(tcDefaults, function () {
restore_options();
var status = document.getElementById("status");
status.textContent = "Default options restored";
setTimeout(function () {
status.textContent = "";
}, 1000);
});
}
document.addEventListener("DOMContentLoaded", function () {
var manifest = chrome.runtime.getManifest();
var versionElement = document.getElementById("app-version");
if (versionElement) {
versionElement.textContent = manifest.version;
}
restore_options();
initControlBarEditor();
document.getElementById("popupMatchHoverControls")
.addEventListener("change", updatePopupEditorDisabledState);
document.getElementById("save").addEventListener("click", save_options);
const addSelector = document.getElementById("addShortcutSelector");
if (addSelector) {
addSelector.addEventListener("change", function (e) {
if (e.target.value) {
add_shortcut(e.target.value);
e.target.value = ""; // Reset selector
}
});
}
document
.getElementById("restore")
.addEventListener("click", restore_defaults);
document
.getElementById("addSiteRule")
.addEventListener("click", function () {
createSiteRule(null);
});
function eventCaller(event, className, funcName) {
if (!event.target.classList || !event.target.classList.contains(className)) {
return;
}
funcName(event);
}
document.addEventListener("keypress", (event) =>
eventCaller(event, "customValue", inputFilterNumbersOnly)
);
document.addEventListener("focus", (event) =>
eventCaller(event, "customKey", inputFocus)
);
document.addEventListener("blur", (event) =>
eventCaller(event, "customKey", inputBlur)
);
document.addEventListener("keydown", (event) =>
eventCaller(event, "customKey", recordKeyPress)
);
document.addEventListener("click", (event) => {
var target = event.target;
var targetEl = target && target.closest ? target : target.parentElement;
if (!targetEl) return;
var removeParentButton = targetEl.closest(".removeParent");
if (removeParentButton) {
removeParentButton.parentNode.remove();
refreshAddShortcutSelector();
return;
}
var removeSiteRuleButton = targetEl.closest(".remove-site-rule");
if (removeSiteRuleButton) {
removeSiteRuleButton.closest(".site-rule").remove();
return;
}
var toggleButton = targetEl.closest(".toggle-site-rule");
if (toggleButton) {
var ruleEl = toggleButton.closest(".site-rule");
var isCollapsed = ruleEl.classList.contains("collapsed");
setSiteRuleExpandedState(ruleEl, isCollapsed);
return;
}
});
document.addEventListener("change", (event) => {
if (event.target.classList.contains("customDo")) {
var valueInput = event.target.nextElementSibling.nextElementSibling;
if (customActionsNoValues.includes(event.target.value)) {
valueInput.disabled = true;
valueInput.value = 0;
} else {
valueInput.disabled = false;
}
}
// Site rule: show/hide optional override sections
var siteOverrideContainers = {
"override-placement": "site-placement-container",
"override-visibility": "site-visibility-container",
"override-autohide": "site-autohide-container",
"override-playback": "site-playback-container",
"override-opacity": "site-opacity-container",
"override-subtitleNudge": "site-subtitleNudge-container",
"override-controlbar": "site-controlbar-container",
"override-popup-controlbar": "site-popup-controlbar-container",
"override-shortcuts": "site-shortcuts-container"
};
for (var ocb in siteOverrideContainers) {
if (event.target.classList.contains(ocb)) {
var siteRuleRoot = event.target.closest(".site-rule");
var targetBox = siteRuleRoot.querySelector(
"." + siteOverrideContainers[ocb]
);
if (targetBox) {
setSiteOverrideContainerState(targetBox, event.target.checked);
}
return;
}
}
});
});