mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-08-20 12:06:19 -04:00
feat: Site-Specific Rules stash pop, cleaned up some settings
This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"kiroAgent.configureMCP": "Disabled"
|
||||
}
|
||||
@@ -16,13 +16,7 @@ var tc = {
|
||||
controllerLocation: "top-left",
|
||||
controllerOpacity: 0.3,
|
||||
keyBindings: [],
|
||||
blacklist: `\
|
||||
www.instagram.com
|
||||
twitter.com
|
||||
vine.co
|
||||
imgur.com
|
||||
teams.microsoft.com
|
||||
`.replace(regStrip, ""),
|
||||
siteRules: [],
|
||||
defaultLogLevel: 3,
|
||||
logLevel: 3,
|
||||
enableSubtitleNudge: true, // Enabled by default, but only activates on YouTube
|
||||
@@ -33,7 +27,8 @@ var tc = {
|
||||
isNudging: false,
|
||||
pendingLastSpeedSave: null,
|
||||
pendingLastSpeedValue: null,
|
||||
persistedLastSpeed: 1.0
|
||||
persistedLastSpeed: 1.0,
|
||||
activeSiteRule: null
|
||||
};
|
||||
|
||||
var MIN_SPEED = 0.0625;
|
||||
@@ -46,8 +41,8 @@ var requestIdle =
|
||||
typeof window.requestIdleCallback === "function"
|
||||
? window.requestIdleCallback.bind(window)
|
||||
: function (callback, options) {
|
||||
return setTimeout(callback, (options && options.timeout) || 1);
|
||||
};
|
||||
return setTimeout(callback, (options && options.timeout) || 1);
|
||||
};
|
||||
var controllerLocations = [
|
||||
"top-left",
|
||||
"top-center",
|
||||
@@ -274,7 +269,21 @@ function applyControllerLocationToElement(controller, location) {
|
||||
|
||||
controller.dataset.location = normalizedLocation;
|
||||
controller.dataset.positionMode = "anchored";
|
||||
controller.style.top = styles.top;
|
||||
|
||||
var top = styles.top;
|
||||
// If in fullscreen, move the controller down to avoid overlapping video titles
|
||||
if (
|
||||
document.fullscreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.msFullscreenElement
|
||||
) {
|
||||
if (normalizedLocation.startsWith("top-")) {
|
||||
top = "50px";
|
||||
}
|
||||
}
|
||||
|
||||
controller.style.top = top;
|
||||
controller.style.left = styles.left;
|
||||
controller.style.transform = styles.transform;
|
||||
|
||||
@@ -509,8 +518,8 @@ function tryYouTubeNativeSpeed(video, speed) {
|
||||
function isSubtitleNudgeSupported(video) {
|
||||
return Boolean(
|
||||
video &&
|
||||
((video.currentSrc && video.currentSrc.includes("googlevideo.com")) ||
|
||||
isOnYouTube())
|
||||
((video.currentSrc && video.currentSrc.includes("googlevideo.com")) ||
|
||||
isOnYouTube())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -923,8 +932,7 @@ chrome.storage.sync.get(tc.settings, function (storage) {
|
||||
startHidden: tc.settings.startHidden,
|
||||
enabled: tc.settings.enabled,
|
||||
controllerLocation: tc.settings.controllerLocation,
|
||||
controllerOpacity: tc.settings.controllerOpacity,
|
||||
blacklist: tc.settings.blacklist.replace(regStrip, "")
|
||||
controllerOpacity: tc.settings.controllerOpacity
|
||||
});
|
||||
}
|
||||
tc.settings.lastSpeed = Number(storage.lastSpeed);
|
||||
@@ -947,7 +955,29 @@ chrome.storage.sync.get(tc.settings, function (storage) {
|
||||
storage.controllerLocation
|
||||
);
|
||||
tc.settings.controllerOpacity = Number(storage.controllerOpacity);
|
||||
tc.settings.blacklist = String(storage.blacklist);
|
||||
tc.settings.siteRules = Array.isArray(storage.siteRules)
|
||||
? storage.siteRules
|
||||
: [];
|
||||
|
||||
// Migrate legacy blacklist if present
|
||||
if (storage.blacklist && typeof storage.blacklist === "string" && tc.settings.siteRules.length === 0) {
|
||||
var lines = storage.blacklist.split("\n");
|
||||
lines.forEach((line) => {
|
||||
var pattern = line.replace(regStrip, "");
|
||||
if (pattern.length > 0) {
|
||||
tc.settings.siteRules.push({
|
||||
pattern: pattern,
|
||||
disableExtension: true
|
||||
});
|
||||
}
|
||||
});
|
||||
if (tc.settings.siteRules.length > 0) {
|
||||
chrome.storage.sync.set({ siteRules: tc.settings.siteRules });
|
||||
chrome.storage.sync.remove(["blacklist"]);
|
||||
log("Migrated legacy blacklist to site rules", 4);
|
||||
}
|
||||
}
|
||||
|
||||
tc.settings.enableSubtitleNudge =
|
||||
typeof storage.enableSubtitleNudge !== "undefined"
|
||||
? Boolean(storage.enableSubtitleNudge)
|
||||
@@ -1463,27 +1493,91 @@ function escapeStringRegExp(str) {
|
||||
const m = /[|\\{}()[\]^$+*?.]/g;
|
||||
return str.replace(m, "\\$&");
|
||||
}
|
||||
function isBlacklisted() {
|
||||
let b = false;
|
||||
const l = tc.settings.blacklist ? tc.settings.blacklist.split("\n") : [];
|
||||
l.forEach((m) => {
|
||||
if (b) return;
|
||||
m = m.replace(regStrip, "");
|
||||
if (m.length == 0) return;
|
||||
let r;
|
||||
if (m.startsWith("/") && m.lastIndexOf("/") > 0) {
|
||||
function applySiteRuleOverrides() {
|
||||
if (!Array.isArray(tc.settings.siteRules) || tc.settings.siteRules.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentUrl = location.href;
|
||||
var matchedRule = null;
|
||||
|
||||
for (var i = 0; i < tc.settings.siteRules.length; i++) {
|
||||
var rule = tc.settings.siteRules[i];
|
||||
var pattern = rule.pattern;
|
||||
if (!pattern || pattern.length === 0) continue;
|
||||
|
||||
var regex;
|
||||
if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
|
||||
try {
|
||||
const ls = m.lastIndexOf("/");
|
||||
r = new RegExp(m.substring(1, ls), m.substring(ls + 1));
|
||||
var lastSlash = pattern.lastIndexOf("/");
|
||||
regex = new RegExp(
|
||||
pattern.substring(1, lastSlash),
|
||||
pattern.substring(lastSlash + 1)
|
||||
);
|
||||
} catch (e) {
|
||||
log(`Invalid regex: ${m}. ${e.message}`, 2);
|
||||
return;
|
||||
log(`Invalid site rule regex: ${pattern}. ${e.message}`, 2);
|
||||
continue;
|
||||
}
|
||||
} else r = new RegExp(escapeStringRegExp(m));
|
||||
if (r && r.test(location.href)) b = true;
|
||||
} else {
|
||||
regex = new RegExp(escapeStringRegExp(pattern));
|
||||
}
|
||||
|
||||
if (regex && regex.test(currentUrl)) {
|
||||
matchedRule = rule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedRule) return false;
|
||||
|
||||
tc.activeSiteRule = matchedRule;
|
||||
log(`Matched site rule: ${matchedRule.pattern}`, 4);
|
||||
|
||||
// Check if extension should be enabled/disabled on this site
|
||||
if (matchedRule.enabled === false) {
|
||||
log(`Extension disabled for site: ${currentUrl}`, 4);
|
||||
return true;
|
||||
} else if (matchedRule.disableExtension === true) {
|
||||
// Handle old format
|
||||
log(`Extension disabled (legacy) for site: ${currentUrl}`, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Override general settings with site-specific overrides
|
||||
const siteSettings = [
|
||||
"startHidden",
|
||||
"controllerLocation",
|
||||
"rememberSpeed",
|
||||
"forceLastSavedSpeed",
|
||||
"audioBoolean",
|
||||
"controllerOpacity"
|
||||
];
|
||||
|
||||
siteSettings.forEach((key) => {
|
||||
if (matchedRule[key] !== undefined) {
|
||||
log(`Overriding ${key} for site: ${matchedRule[key]}`, 4);
|
||||
tc.settings[key] = matchedRule[key];
|
||||
}
|
||||
});
|
||||
if (b) log(`Page ${location.href} blacklisted.`, 4);
|
||||
return b;
|
||||
|
||||
// Override key bindings with site-specific shortcuts
|
||||
if (Array.isArray(matchedRule.shortcuts) && matchedRule.shortcuts.length > 0) {
|
||||
var overriddenActions = new Set();
|
||||
matchedRule.shortcuts.forEach((shortcut) => {
|
||||
overriddenActions.add(shortcut.action);
|
||||
});
|
||||
|
||||
// Keep global bindings that aren't overridden, add site-specific ones
|
||||
tc.settings.keyBindings = tc.settings.keyBindings
|
||||
.filter((binding) => !overriddenActions.has(binding.action))
|
||||
.concat(
|
||||
matchedRule.shortcuts.map((shortcut) =>
|
||||
normalizeStoredBinding(shortcut)
|
||||
).filter(Boolean)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldPreserveDesiredSpeed(video, speed) {
|
||||
@@ -1627,7 +1721,7 @@ function attachKeydownListeners(doc) {
|
||||
var docs = [doc];
|
||||
try {
|
||||
if (inIframe() && window.top.document !== doc) docs.push(window.top.document);
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
|
||||
docs.forEach(function (keyDoc) {
|
||||
if (keyDoc.vscKeydownListenerAttached) return;
|
||||
@@ -1662,7 +1756,16 @@ function attachKeydownListeners(doc) {
|
||||
|
||||
if (item) {
|
||||
runAction(item.action, item.value, event);
|
||||
if (item.force === "true") {
|
||||
|
||||
// Check if we should disable website key bindings for this specific shortcut
|
||||
var shouldPreventDefault = false;
|
||||
|
||||
// Check if this shortcut has force enabled (from site-specific shortcuts)
|
||||
if (item.force === true || item.force === "true") {
|
||||
shouldPreventDefault = true;
|
||||
}
|
||||
|
||||
if (shouldPreventDefault) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
@@ -1772,7 +1875,7 @@ function attachIframeListeners(doc) {
|
||||
if (frame.contentDocument) {
|
||||
initializeWhenReady(frame.contentDocument, true);
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
});
|
||||
frame.vscLoadListenerAttached = true;
|
||||
}
|
||||
@@ -1781,7 +1884,7 @@ function attachIframeListeners(doc) {
|
||||
if (frame.contentDocument) {
|
||||
initializeWhenReady(frame.contentDocument);
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1812,7 +1915,9 @@ function attachNavigationListeners() {
|
||||
|
||||
function initializeNow(doc, forceReinit = false) {
|
||||
if ((!forceReinit && vscInitializedDocuments.has(doc)) || !doc.body) return;
|
||||
if (!tc.settings.enabled || isBlacklisted()) return;
|
||||
|
||||
var siteDisabled = applySiteRuleOverrides();
|
||||
if (!tc.settings.enabled || siteDisabled) return;
|
||||
|
||||
if (!doc.body.classList.contains("vsc-initialized")) {
|
||||
doc.body.classList.add("vsc-initialized");
|
||||
@@ -1831,7 +1936,7 @@ function initializeNow(doc, forceReinit = false) {
|
||||
|
||||
function setSpeed(video, speed, isInitialCall = false, isUserKeyPress = false) {
|
||||
const numericSpeed = Number(speed);
|
||||
|
||||
|
||||
if (!isValidSpeed(numericSpeed)) {
|
||||
log(
|
||||
`Invalid speed rejected: ${speed}, must be between ${MIN_SPEED} and ${MAX_SPEED}`,
|
||||
@@ -1839,7 +1944,7 @@ function setSpeed(video, speed, isInitialCall = false, isUserKeyPress = false) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!video || !video.vsc || !video.vsc.speedIndicator) return;
|
||||
|
||||
log(
|
||||
@@ -1975,7 +2080,12 @@ function runAction(action, value, e) {
|
||||
resetSpeed(v, 1.0, false); // Use enhanced resetSpeed
|
||||
break;
|
||||
case "fast":
|
||||
resetSpeed(v, numValue, true); // Use enhanced resetSpeed
|
||||
var preferredSpeed = numValue;
|
||||
// Apply site-specific preferred speed override if available
|
||||
if (tc.activeSiteRule && typeof tc.activeSiteRule.preferredSpeed === "number") {
|
||||
preferredSpeed = tc.activeSiteRule.preferredSpeed;
|
||||
}
|
||||
resetSpeed(v, preferredSpeed, true);
|
||||
break;
|
||||
case "display":
|
||||
if (controller.classList.contains("vsc-hidden")) {
|
||||
@@ -2151,3 +2261,12 @@ function showController(controller, duration = 2000) {
|
||||
controller.showTimeOut = undefined;
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// Add global listener to handle fullscreen transitions and adjust controller positions
|
||||
document.addEventListener("fullscreenchange", () => {
|
||||
tc.mediaElements.forEach((video) => {
|
||||
if (video.vsc) {
|
||||
applyControllerLocation(video.vsc, video.vsc.controllerLocation);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+222
-9
@@ -1,7 +1,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding-left: 15px;
|
||||
padding-top: 53px;
|
||||
padding-top: 80px;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
color: rgb(48, 57, 66);
|
||||
@@ -18,10 +18,10 @@ h3 {
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 21px 0 13px;
|
||||
margin: 21px 0 6px;
|
||||
}
|
||||
.version {
|
||||
margin: 0 0 12px;
|
||||
margin: 0 0 6px;
|
||||
color: #6b6b6b;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
@@ -39,8 +39,10 @@ header {
|
||||
top: 0;
|
||||
left: 15px;
|
||||
right: 0;
|
||||
padding-bottom: 10px;
|
||||
background: white;
|
||||
z-index: 100;
|
||||
border-bottom: 1px solid #eee;
|
||||
background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92));
|
||||
}
|
||||
header,
|
||||
section {
|
||||
@@ -58,6 +60,7 @@ section h3 {
|
||||
|
||||
button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
position: relative;
|
||||
|
||||
margin: 0 1px 0 0;
|
||||
@@ -87,6 +90,66 @@ input[type="text"] {
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
.shortcuts-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.shortcut-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.shortcut-label {
|
||||
display: inline-block;
|
||||
width: 170px;
|
||||
padding: 4px 8px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
line-height: normal;
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.shortcut-row input[type="text"] {
|
||||
height: 26px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.removeParent {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.removeParent:hover {
|
||||
background-color: #ff4444;
|
||||
color: white;
|
||||
border-color: #cc0000;
|
||||
}
|
||||
|
||||
#addShortcutSelector {
|
||||
margin-top: 15px;
|
||||
padding: 4px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 170px;
|
||||
@@ -107,11 +170,6 @@ select {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.customForce {
|
||||
display: none;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.customKey {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 #000000;
|
||||
@@ -177,6 +235,11 @@ select {
|
||||
text-shadow: 0 0 0 #e0e0e0;
|
||||
}
|
||||
|
||||
.shortcut-label {
|
||||
background-color: #2a2a2a;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
#status {
|
||||
color: #888;
|
||||
}
|
||||
@@ -193,3 +256,153 @@ select {
|
||||
border-color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
/* Site Rules Styles */
|
||||
#siteRulesContainer {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.site-rule {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.site-rule-header {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site-rule.collapsed .site-rule-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.toggle-site-rule {
|
||||
min-width: auto;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.site-pattern {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
padding: 6px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.remove-site-rule {
|
||||
min-width: auto;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.site-rule-body {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.site-rule-content.disabled-rule {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.site-rule-option {
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.site-rule-option label {
|
||||
width: auto;
|
||||
line-height: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.site-rule-option input[type="text"],
|
||||
.site-rule-option select {
|
||||
width: 150px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.site-rule-shortcuts {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.site-rule-shortcuts > label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.site-shortcuts-container {
|
||||
margin-left: 20px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .shortcut-row {
|
||||
margin: 6px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .customKey {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .customValue {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .customDo {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .customForce {
|
||||
width: auto;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .force-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-left: 8px;
|
||||
width: auto;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.site-shortcuts-container .force-text {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Dark mode for site rules */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.site-rule {
|
||||
border-color: #444;
|
||||
background-color: #252525;
|
||||
}
|
||||
|
||||
.site-pattern {
|
||||
background-color: #2a2a2a;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.site-rule-shortcuts {
|
||||
border-top-color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
+190
-188
@@ -13,167 +13,120 @@
|
||||
|
||||
<section id="customs">
|
||||
<h3>Shortcuts</h3>
|
||||
<div class="row customs" id="display">
|
||||
<select class="customDo">
|
||||
<option value="display">Show/hide controller</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="move">
|
||||
<select class="customDo">
|
||||
<option value="move">Move controller</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input
|
||||
class="customValue"
|
||||
type="text"
|
||||
placeholder="value (0)"
|
||||
disabled
|
||||
/>
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="slower">
|
||||
<select class="customDo">
|
||||
<option value="slower">Decrease speed</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="faster">
|
||||
<select class="customDo">
|
||||
<option value="faster">Increase speed</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="rewind">
|
||||
<select class="customDo">
|
||||
<option value="rewind">Rewind</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (10)" />
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="advance">
|
||||
<select class="customDo">
|
||||
<option value="advance">Advance</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (10)" />
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="reset">
|
||||
<select class="customDo">
|
||||
<option value="reset">Reset speed</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input
|
||||
class="customValue"
|
||||
type="text"
|
||||
placeholder="value (1.00)"
|
||||
disabled
|
||||
/>
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="fast">
|
||||
<select class="customDo">
|
||||
<option value="fast">Preferred speed</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (1.80)" />
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row customs" id="toggleSubtitleNudge">
|
||||
<select class="customDo">
|
||||
<option value="toggleSubtitleNudge">Toggle subtitle nudge</option>
|
||||
</select>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input
|
||||
class="customValue"
|
||||
type="text"
|
||||
placeholder="value (0)"
|
||||
disabled
|
||||
/>
|
||||
<select class="customForce">
|
||||
<option value="false">Do not disable website key bindings</option>
|
||||
<option value="true">Disable website key bindings</option>
|
||||
</select>
|
||||
<div class="shortcuts-grid">
|
||||
<div class="shortcut-row" id="display" data-action="display">
|
||||
<div class="shortcut-label">Show/hide controller</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value" value="N/A" disabled />
|
||||
</div>
|
||||
<div class="shortcut-row" id="move" data-action="move">
|
||||
<div class="shortcut-label">Move controller</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input
|
||||
class="customValue"
|
||||
type="text"
|
||||
placeholder="value"
|
||||
value="N/A"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="shortcut-row" id="slower" data-action="slower">
|
||||
<div class="shortcut-label">Decrease speed</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||
</div>
|
||||
<div class="shortcut-row" id="faster" data-action="faster">
|
||||
<div class="shortcut-label">Increase speed</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||
</div>
|
||||
<div class="shortcut-row" id="rewind" data-action="rewind">
|
||||
<div class="shortcut-label">Rewind</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (10)" />
|
||||
</div>
|
||||
<div class="shortcut-row" id="advance" data-action="advance">
|
||||
<div class="shortcut-label">Advance</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (10)" />
|
||||
</div>
|
||||
<div class="shortcut-row" id="reset" data-action="reset">
|
||||
<div class="shortcut-label">Reset speed</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input
|
||||
class="customValue"
|
||||
type="text"
|
||||
placeholder="value"
|
||||
value="N/A"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="shortcut-row" id="fast" data-action="fast">
|
||||
<div class="shortcut-label">Preferred speed</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input class="customValue" type="text" placeholder="value (1.80)" />
|
||||
</div>
|
||||
<div class="shortcut-row" id="toggleSubtitleNudge" data-action="toggleSubtitleNudge">
|
||||
<div class="shortcut-label">Toggle subtitle nudge</div>
|
||||
<input
|
||||
class="customKey"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="press a key"
|
||||
/>
|
||||
<input
|
||||
class="customValue"
|
||||
type="text"
|
||||
placeholder="value"
|
||||
value="N/A"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="add">Add New</button>
|
||||
<select id="addShortcutSelector">
|
||||
<option value="">Add shortcut...</option>
|
||||
</select>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -221,23 +174,87 @@
|
||||
<label for="controllerOpacity">Controller opacity</label>
|
||||
<input id="controllerOpacity" type="text" value="" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="blacklist"
|
||||
>Sites on which extension is disabled<br />
|
||||
(one per line)<br />
|
||||
<br />
|
||||
<em>
|
||||
<a href="https://www.regexpal.com/">Regex</a> is supported.<br />
|
||||
Be sure it is in "//g" format.<br />
|
||||
ie: /(.+)youtube\.com(\/*)$/gi
|
||||
</em>
|
||||
</label>
|
||||
<textarea id="blacklist" rows="10" cols="50"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="siteRules">
|
||||
<h3>Site-Specific Settings</h3>
|
||||
<p>
|
||||
<em
|
||||
>Override default settings for specific websites. Supports
|
||||
<a href="https://www.regexpal.com/">Regex</a> patterns (e.g.,
|
||||
/(.+)youtube\.com(\/*)$/gi).</em
|
||||
>
|
||||
</p>
|
||||
<div id="siteRulesContainer"></div>
|
||||
<button id="addSiteRule" type="button">Add Site Rule</button>
|
||||
</section>
|
||||
|
||||
<template id="siteRuleTemplate">
|
||||
<div class="site-rule">
|
||||
<div class="site-rule-header">
|
||||
<button type="button" class="toggle-site-rule" title="Expand/Collapse">+</button>
|
||||
<input
|
||||
type="text"
|
||||
class="site-pattern"
|
||||
placeholder="e.g., youtube.com or /regex/gi"
|
||||
/>
|
||||
<button type="button" class="remove-site-rule">Remove</button>
|
||||
</div>
|
||||
<div class="site-rule-body">
|
||||
<div class="site-rule-option">
|
||||
<label>
|
||||
<input type="checkbox" class="site-enabled" />
|
||||
Enable Video Speed Controller on this site
|
||||
</label>
|
||||
</div>
|
||||
<div class="site-rule-content">
|
||||
<div class="site-rule-option">
|
||||
<label>Hide controller by default:</label>
|
||||
<input type="checkbox" class="site-startHidden" />
|
||||
</div>
|
||||
<div class="site-rule-option">
|
||||
<label>Default controller location:</label>
|
||||
<select class="site-controllerLocation">
|
||||
<option value="top-left">Top left</option>
|
||||
<option value="top-center">Top center</option>
|
||||
<option value="top-right">Top right</option>
|
||||
<option value="middle-right">Middle right</option>
|
||||
<option value="bottom-right">Bottom right</option>
|
||||
<option value="bottom-center">Bottom center</option>
|
||||
<option value="bottom-left">Bottom left</option>
|
||||
<option value="middle-left">Middle left</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="site-rule-option">
|
||||
<label>Remember playback speed:</label>
|
||||
<input type="checkbox" class="site-rememberSpeed" />
|
||||
</div>
|
||||
<div class="site-rule-option">
|
||||
<label>Force last saved speed:</label>
|
||||
<input type="checkbox" class="site-forceLastSavedSpeed" />
|
||||
</div>
|
||||
<div class="site-rule-option">
|
||||
<label>Work on audio:</label>
|
||||
<input type="checkbox" class="site-audioBoolean" />
|
||||
</div>
|
||||
<div class="site-rule-option">
|
||||
<label>Controller opacity:</label>
|
||||
<input type="text" class="site-controllerOpacity" />
|
||||
</div>
|
||||
<div class="site-rule-shortcuts">
|
||||
<label>
|
||||
<input type="checkbox" class="override-shortcuts" />
|
||||
Custom shortcuts for this site
|
||||
</label>
|
||||
<div class="site-shortcuts-container" style="display: none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<section id="nudgeSettings">
|
||||
<h3>Subtitle Nudge Settings (Experimental - YouTube Only)</h3>
|
||||
<h3>Subtitle Nudge Settings</h3>
|
||||
<div class="row">
|
||||
<label for="enableSubtitleNudge"
|
||||
>Enable Subtitle Nudge <br /><em
|
||||
@@ -261,25 +278,10 @@
|
||||
placeholder="50"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="subtitleNudgeAmount"
|
||||
>Nudge Amount (decimal) <br /><em
|
||||
>How much to change speed by (e.g., 0.001). Very small values
|
||||
recommended. Default: 0.001.</em
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="subtitleNudgeAmount"
|
||||
type="text"
|
||||
value=""
|
||||
placeholder="0.001"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button id="save">Save</button>
|
||||
<button id="restore">Restore Defaults</button>
|
||||
<button id="experimental">Show Experimental Features</button>
|
||||
|
||||
<div id="status"></div>
|
||||
|
||||
|
||||
+448
-114
@@ -165,26 +165,82 @@ var tcDefaults = {
|
||||
createDefaultBinding("fast", "G", 71, 1.8),
|
||||
createDefaultBinding("toggleSubtitleNudge", "N", 78, 0)
|
||||
],
|
||||
blacklist: `www.instagram.com
|
||||
twitter.com
|
||||
imgur.com
|
||||
teams.microsoft.com
|
||||
`.replace(regStrip, ""),
|
||||
siteRules: [
|
||||
{ pattern: "www.instagram.com", disableExtension: true },
|
||||
{ pattern: "x.com", disableExtension: true },
|
||||
{ pattern: "imgur.com", disableExtension: true },
|
||||
{ pattern: "teams.microsoft.com", disableExtension: true }
|
||||
],
|
||||
enableSubtitleNudge: true,
|
||||
subtitleNudgeInterval: 50,
|
||||
subtitleNudgeAmount: 0.001
|
||||
};
|
||||
|
||||
var customActionsNoValues = [
|
||||
"pause",
|
||||
"muted",
|
||||
"mark",
|
||||
"jump",
|
||||
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",
|
||||
muted: "Mute",
|
||||
pause: "Pause",
|
||||
mark: "Set marker",
|
||||
jump: "Jump to marker",
|
||||
toggleSubtitleNudge: "Toggle subtitle nudge"
|
||||
};
|
||||
|
||||
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...";
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDefaultBinding(storage, action, key, keyCode, value) {
|
||||
if (storage.keyBindings.some((item) => item.action === action)) return;
|
||||
|
||||
@@ -389,27 +445,16 @@ function appendSelectOptions(select, options) {
|
||||
});
|
||||
}
|
||||
|
||||
function add_shortcut() {
|
||||
var div = document.createElement("div");
|
||||
div.setAttribute("class", "row customs");
|
||||
function add_shortcut(action, value) {
|
||||
if (!action) return;
|
||||
|
||||
var actionSelect = document.createElement("select");
|
||||
actionSelect.className = "customDo";
|
||||
appendSelectOptions(actionSelect, [
|
||||
{ value: "slower", label: "Decrease speed" },
|
||||
{ value: "faster", label: "Increase speed" },
|
||||
{ value: "rewind", label: "Rewind" },
|
||||
{ value: "advance", label: "Advance" },
|
||||
{ value: "reset", label: "Reset speed" },
|
||||
{ value: "fast", label: "Preferred speed" },
|
||||
{ value: "muted", label: "Mute" },
|
||||
{ value: "pause", label: "Pause" },
|
||||
{ value: "mark", label: "Set marker" },
|
||||
{ value: "jump", label: "Jump to marker" },
|
||||
{ value: "display", label: "Show/hide controller" },
|
||||
{ value: "move", label: "Move controller" },
|
||||
{ value: "toggleSubtitleNudge", label: "Toggle subtitle nudge" }
|
||||
]);
|
||||
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";
|
||||
@@ -419,41 +464,32 @@ function add_shortcut() {
|
||||
var valueInput = document.createElement("input");
|
||||
valueInput.className = "customValue";
|
||||
valueInput.type = "text";
|
||||
valueInput.placeholder = "value (0.10)";
|
||||
|
||||
var forceSelect = document.createElement("select");
|
||||
forceSelect.className = "customForce";
|
||||
appendSelectOptions(forceSelect, [
|
||||
{
|
||||
value: "false",
|
||||
label: "Do not disable website key bindings"
|
||||
},
|
||||
{
|
||||
value: "true",
|
||||
label: "Disable website key bindings"
|
||||
}
|
||||
]);
|
||||
valueInput.placeholder = "value";
|
||||
if (customActionsNoValues.includes(action)) {
|
||||
valueInput.value = "N/A";
|
||||
valueInput.disabled = true;
|
||||
} else {
|
||||
valueInput.value = value || 0;
|
||||
}
|
||||
|
||||
var removeButton = document.createElement("button");
|
||||
removeButton.className = "removeParent";
|
||||
removeButton.type = "button";
|
||||
removeButton.textContent = "X";
|
||||
|
||||
div.appendChild(actionSelect);
|
||||
div.appendChild(actionLabel);
|
||||
div.appendChild(keyInput);
|
||||
div.appendChild(valueInput);
|
||||
div.appendChild(forceSelect);
|
||||
div.appendChild(removeButton);
|
||||
|
||||
var customsElement = document.getElementById("customs");
|
||||
customsElement.insertBefore(
|
||||
div,
|
||||
customsElement.children[customsElement.childElementCount - 1]
|
||||
);
|
||||
var customsElement = document.querySelector(".shortcuts-grid");
|
||||
customsElement.appendChild(div);
|
||||
|
||||
refreshAddShortcutSelector();
|
||||
}
|
||||
|
||||
function createKeyBindings(item) {
|
||||
var action = item.querySelector(".customDo").value;
|
||||
var action = item.dataset.action || item.querySelector(".customDo").value;
|
||||
var input = item.querySelector(".customKey");
|
||||
var valueInput = item.querySelector(".customValue");
|
||||
var predefined = !!item.id;
|
||||
@@ -479,7 +515,7 @@ function createKeyBindings(item) {
|
||||
value: customActionsNoValues.includes(action)
|
||||
? 0
|
||||
: Number(valueInput.value),
|
||||
force: item.querySelector(".customForce").value,
|
||||
force: false,
|
||||
predefined: predefined
|
||||
});
|
||||
|
||||
@@ -489,22 +525,27 @@ function createKeyBindings(item) {
|
||||
function validate() {
|
||||
var valid = true;
|
||||
var status = document.getElementById("status");
|
||||
document
|
||||
.getElementById("blacklist")
|
||||
.value.split("\n")
|
||||
.forEach((match) => {
|
||||
match = match.replace(regStrip, "");
|
||||
if (match.startsWith("/")) {
|
||||
try {
|
||||
new RegExp(match);
|
||||
} catch (err) {
|
||||
status.textContent =
|
||||
"Error: Invalid blacklist regex: " + match + ". Unable to save";
|
||||
valid = false;
|
||||
return;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -538,18 +579,13 @@ function save_options() {
|
||||
);
|
||||
settings.controllerOpacity =
|
||||
document.getElementById("controllerOpacity").value;
|
||||
settings.blacklist = document
|
||||
.getElementById("blacklist")
|
||||
.value.replace(regStrip, "");
|
||||
settings.keyBindings = keyBindings;
|
||||
settings.enableSubtitleNudge =
|
||||
document.getElementById("enableSubtitleNudge").checked;
|
||||
settings.subtitleNudgeInterval =
|
||||
parseInt(document.getElementById("subtitleNudgeInterval").value, 10) ||
|
||||
tcDefaults.subtitleNudgeInterval;
|
||||
settings.subtitleNudgeAmount =
|
||||
parseFloat(document.getElementById("subtitleNudgeAmount").value) ||
|
||||
tcDefaults.subtitleNudgeAmount;
|
||||
settings.subtitleNudgeAmount = tcDefaults.subtitleNudgeAmount;
|
||||
|
||||
if (settings.subtitleNudgeInterval < 10) {
|
||||
settings.subtitleNudgeInterval = 10;
|
||||
@@ -557,12 +593,65 @@ function save_options() {
|
||||
if (settings.subtitleNudgeInterval > 1000) {
|
||||
settings.subtitleNudgeInterval = 1000;
|
||||
}
|
||||
if (
|
||||
settings.subtitleNudgeAmount <= 0 ||
|
||||
settings.subtitleNudgeAmount > 0.1
|
||||
) {
|
||||
settings.subtitleNudgeAmount = tcDefaults.subtitleNudgeAmount;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Handle other site settings
|
||||
const siteSettings = [
|
||||
{ key: "startHidden", type: "checkbox" },
|
||||
{ key: "controllerLocation", type: "select" },
|
||||
{ key: "rememberSpeed", type: "checkbox" },
|
||||
{ key: "forceLastSavedSpeed", type: "checkbox" },
|
||||
{ key: "audioBoolean", type: "checkbox" },
|
||||
{ key: "controllerOpacity", type: "text" }
|
||||
];
|
||||
|
||||
siteSettings.forEach((s) => {
|
||||
var input = ruleEl.querySelector(`.site-${s.key}`);
|
||||
if (s.type === "checkbox") {
|
||||
rule[s.key] = input.checked;
|
||||
} else {
|
||||
rule[s.key] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
if (ruleEl.querySelector(".override-shortcuts").checked) {
|
||||
var shortcuts = [];
|
||||
ruleEl.querySelectorAll(".site-shortcuts-container .customs").forEach((shortcutRow) => {
|
||||
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) {
|
||||
shortcuts.push({
|
||||
action: action,
|
||||
key: binding.key,
|
||||
keyCode: binding.keyCode,
|
||||
code: binding.code,
|
||||
disabled: binding.disabled === true,
|
||||
value: customActionsNoValues.includes(action)
|
||||
? 0
|
||||
: Number(valueInput.value),
|
||||
force: forceCheckbox ? forceCheckbox.checked : false
|
||||
});
|
||||
}
|
||||
});
|
||||
if (shortcuts.length > 0) rule.shortcuts = shortcuts;
|
||||
}
|
||||
|
||||
settings.siteRules.push(rule);
|
||||
});
|
||||
|
||||
chrome.storage.sync.remove([
|
||||
"resetSpeed",
|
||||
@@ -575,7 +664,8 @@ function save_options() {
|
||||
"fasterKeyCode",
|
||||
"rewindKeyCode",
|
||||
"advanceKeyCode",
|
||||
"fastKeyCode"
|
||||
"fastKeyCode",
|
||||
"blacklist"
|
||||
]);
|
||||
|
||||
chrome.storage.sync.set(settings, function () {
|
||||
@@ -604,6 +694,194 @@ function ensureSubtitleNudgeBinding(storage) {
|
||||
ensureDefaultBinding(storage, "toggleSubtitleNudge", "N", 78, 0);
|
||||
}
|
||||
|
||||
function migrateLegacyBlacklist(storage) {
|
||||
if (!storage.blacklist || typeof storage.blacklist !== "string") {
|
||||
return [];
|
||||
}
|
||||
|
||||
var siteRules = [];
|
||||
var lines = storage.blacklist.split("\n");
|
||||
|
||||
lines.forEach((line) => {
|
||||
var pattern = line.replace(regStrip, "");
|
||||
if (pattern.length === 0) return;
|
||||
|
||||
siteRules.push({
|
||||
pattern: pattern,
|
||||
disableExtension: true
|
||||
});
|
||||
});
|
||||
|
||||
return siteRules;
|
||||
}
|
||||
|
||||
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",
|
||||
muted: "Mute",
|
||||
pause: "Pause",
|
||||
mark: "Set marker",
|
||||
jump: "Jump to marker",
|
||||
toggleSubtitleNudge: "Toggle subtitle nudge"
|
||||
};
|
||||
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)";
|
||||
valueInput.value = value || 0;
|
||||
if (customActionsNoValues.includes(action)) {
|
||||
valueInput.disabled = true;
|
||||
}
|
||||
|
||||
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
|
||||
var ruleBody = ruleEl.querySelector(".site-rule-body");
|
||||
ruleBody.style.display = "none";
|
||||
ruleEl.classList.add("collapsed");
|
||||
|
||||
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();
|
||||
|
||||
const settings = [
|
||||
{ key: "startHidden", type: "checkbox" },
|
||||
{ key: "controllerLocation", type: "select" },
|
||||
{ key: "rememberSpeed", type: "checkbox" },
|
||||
{ key: "forceLastSavedSpeed", type: "checkbox" },
|
||||
{ key: "audioBoolean", type: "checkbox" },
|
||||
{ key: "controllerOpacity", type: "text" }
|
||||
];
|
||||
|
||||
settings.forEach((s) => {
|
||||
var input = ruleEl.querySelector(`.site-${s.key}`);
|
||||
var value;
|
||||
if (rule && rule[s.key] !== undefined) {
|
||||
value = rule[s.key];
|
||||
} else {
|
||||
// Initialize with current global value
|
||||
if (s.type === "checkbox") {
|
||||
value = document.getElementById(s.key).checked;
|
||||
} else {
|
||||
value = document.getElementById(s.key).value;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.type === "checkbox") {
|
||||
input.checked = value;
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (rule && Array.isArray(rule.shortcuts) && rule.shortcuts.length > 0) {
|
||||
ruleEl.querySelector(".override-shortcuts").checked = true;
|
||||
var container = ruleEl.querySelector(".site-shortcuts-container");
|
||||
container.style.display = "block";
|
||||
|
||||
rule.shortcuts.forEach((shortcut) => {
|
||||
addSiteRuleShortcut(
|
||||
container,
|
||||
shortcut.action,
|
||||
shortcut,
|
||||
shortcut.value,
|
||||
shortcut.force
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("siteRulesContainer").appendChild(ruleEl);
|
||||
}
|
||||
|
||||
function populateDefaultSiteShortcuts(container) {
|
||||
tcDefaults.keyBindings.forEach((binding) => {
|
||||
addSiteRuleShortcut(container, binding.action, binding, binding.value, false);
|
||||
});
|
||||
}
|
||||
|
||||
function restore_options() {
|
||||
chrome.storage.sync.get(tcDefaults, function (storage) {
|
||||
document.getElementById("rememberSpeed").checked = storage.rememberSpeed;
|
||||
@@ -616,13 +894,10 @@ function restore_options() {
|
||||
normalizeControllerLocation(storage.controllerLocation);
|
||||
document.getElementById("controllerOpacity").value =
|
||||
storage.controllerOpacity;
|
||||
document.getElementById("blacklist").value = storage.blacklist;
|
||||
document.getElementById("enableSubtitleNudge").checked =
|
||||
storage.enableSubtitleNudge;
|
||||
document.getElementById("subtitleNudgeInterval").value =
|
||||
storage.subtitleNudgeInterval;
|
||||
document.getElementById("subtitleNudgeAmount").value =
|
||||
storage.subtitleNudgeAmount;
|
||||
|
||||
if (!Array.isArray(storage.keyBindings) || storage.keyBindings.length === 0) {
|
||||
storage.keyBindings = tcDefaults.keyBindings.slice();
|
||||
@@ -639,31 +914,48 @@ function restore_options() {
|
||||
item.action === "display"
|
||||
? storage.displayKeyCode || tcDefaults.displayKeyCode
|
||||
: undefined;
|
||||
var normalizedBinding = normalizeStoredBinding(item, fallbackKeyCode);
|
||||
var row;
|
||||
var row = document.getElementById(item.action);
|
||||
var normalizedBinding = normalizeStoredBinding(item);
|
||||
|
||||
if (item.predefined) {
|
||||
row = document.getElementById(item.action);
|
||||
} else {
|
||||
add_shortcut();
|
||||
row = document.querySelector(".customs:last-of-type");
|
||||
row.querySelector(".customDo").value = item.action;
|
||||
if (!row) {
|
||||
add_shortcut(item.action, item.value);
|
||||
row = document.querySelector(".shortcut-row.customs:last-of-type");
|
||||
}
|
||||
|
||||
if (!row) return;
|
||||
|
||||
var valueInput = row.querySelector(".customValue");
|
||||
if (customActionsNoValues.includes(item.action)) {
|
||||
valueInput.disabled = true;
|
||||
if (valueInput) {
|
||||
valueInput.value = "N/A";
|
||||
valueInput.disabled = true;
|
||||
}
|
||||
} else if (valueInput) {
|
||||
valueInput.value = item.value;
|
||||
}
|
||||
|
||||
updateCustomShortcutInputText(
|
||||
row.querySelector(".customKey"),
|
||||
normalizedBinding || createDisabledBinding()
|
||||
);
|
||||
valueInput.value = item.value;
|
||||
row.querySelector(".customForce").value = String(item.force);
|
||||
});
|
||||
|
||||
refreshAddShortcutSelector();
|
||||
|
||||
// Load site rules (use defaults if none in storage or if storage has empty array)
|
||||
var siteRules = Array.isArray(storage.siteRules) && storage.siteRules.length > 0
|
||||
? storage.siteRules
|
||||
: (storage.blacklist ? migrateLegacyBlacklist(storage) : (tcDefaults.siteRules || []));
|
||||
|
||||
// If we migrated from blacklist, save the new format
|
||||
if (storage.blacklist && siteRules.length > 0) {
|
||||
chrome.storage.sync.set({ siteRules: siteRules });
|
||||
chrome.storage.sync.remove(["blacklist"]);
|
||||
}
|
||||
|
||||
document.getElementById("siteRulesContainer").innerHTML = "";
|
||||
if (siteRules.length > 0) {
|
||||
siteRules.forEach((rule) => {
|
||||
if (rule && rule.pattern) {
|
||||
createSiteRule(rule);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -680,12 +972,6 @@ function restore_defaults() {
|
||||
});
|
||||
}
|
||||
|
||||
function show_experimental() {
|
||||
document
|
||||
.querySelectorAll(".customForce")
|
||||
.forEach((item) => (item.style.display = "inline-block"));
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var manifest = chrome.runtime.getManifest();
|
||||
var versionElement = document.getElementById("app-version");
|
||||
@@ -695,13 +981,24 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
restore_options();
|
||||
document.getElementById("save").addEventListener("click", save_options);
|
||||
document.getElementById("add").addEventListener("click", add_shortcut);
|
||||
|
||||
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("experimental")
|
||||
.addEventListener("click", show_experimental);
|
||||
.getElementById("addSiteRule")
|
||||
.addEventListener("click", function () {
|
||||
createSiteRule(null);
|
||||
});
|
||||
|
||||
function eventCaller(event, className, funcName) {
|
||||
if (!event.target.classList || !event.target.classList.contains(className)) {
|
||||
@@ -722,13 +1019,35 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
document.addEventListener("keydown", (event) =>
|
||||
eventCaller(event, "customKey", recordKeyPress)
|
||||
);
|
||||
document.addEventListener("click", (event) =>
|
||||
eventCaller(event, "removeParent", function () {
|
||||
document.addEventListener("click", (event) => {
|
||||
if (event.target.classList.contains("removeParent")) {
|
||||
event.target.parentNode.remove();
|
||||
})
|
||||
);
|
||||
refreshAddShortcutSelector();
|
||||
return;
|
||||
}
|
||||
if (event.target.classList.contains("remove-site-rule")) {
|
||||
event.target.closest(".site-rule").remove();
|
||||
return;
|
||||
}
|
||||
if (event.target.classList.contains("toggle-site-rule")) {
|
||||
var ruleEl = event.target.closest(".site-rule");
|
||||
var ruleBody = ruleEl.querySelector(".site-rule-body");
|
||||
var isCollapsed = ruleEl.classList.contains("collapsed");
|
||||
|
||||
if (isCollapsed) {
|
||||
ruleBody.style.display = "block";
|
||||
ruleEl.classList.remove("collapsed");
|
||||
event.target.textContent = "-";
|
||||
} else {
|
||||
ruleBody.style.display = "none";
|
||||
ruleEl.classList.add("collapsed");
|
||||
event.target.textContent = "+";
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
document.addEventListener("change", (event) => {
|
||||
eventCaller(event, "customDo", function () {
|
||||
if (event.target.classList.contains("customDo")) {
|
||||
var valueInput = event.target.nextElementSibling.nextElementSibling;
|
||||
if (customActionsNoValues.includes(event.target.value)) {
|
||||
valueInput.disabled = true;
|
||||
@@ -736,6 +1055,21 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
} else {
|
||||
valueInput.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle site rule override checkboxes
|
||||
if (event.target.classList.contains("override-shortcuts")) {
|
||||
var container = event.target
|
||||
.closest(".site-rule-shortcuts")
|
||||
.querySelector(".site-shortcuts-container");
|
||||
if (event.target.checked) {
|
||||
container.style.display = "block";
|
||||
if (container.children.length === 0) {
|
||||
populateDefaultSiteShortcuts(container);
|
||||
}
|
||||
} else {
|
||||
container.style.display = "none";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user