mirror of
https://github.com/SoPat712/videospeed.git
synced 2026-04-21 04:42:35 -04:00
feat: top/bottom margin setting, fix: site-specific rule overrides, refactor: wording for settings
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
z-index: 2147483646 !important;
|
z-index: 2147483646 !important;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Use minimal z-index for non-YouTube sites to avoid overlapping modals */
|
/* Use minimal z-index for non-YouTube sites to avoid overlapping modals */
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ var tc = {
|
|||||||
hideWithControlsTimer: 2.0,
|
hideWithControlsTimer: 2.0,
|
||||||
controllerLocation: "top-left",
|
controllerLocation: "top-left",
|
||||||
controllerOpacity: 0.3,
|
controllerOpacity: 0.3,
|
||||||
|
controllerMarginTop: 0,
|
||||||
|
controllerMarginRight: 0,
|
||||||
|
controllerMarginBottom: 65,
|
||||||
|
controllerMarginLeft: 0,
|
||||||
keyBindings: [],
|
keyBindings: [],
|
||||||
siteRules: [],
|
siteRules: [],
|
||||||
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
|
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
|
||||||
@@ -32,7 +36,8 @@ var tc = {
|
|||||||
pendingLastSpeedSave: null,
|
pendingLastSpeedSave: null,
|
||||||
pendingLastSpeedValue: null,
|
pendingLastSpeedValue: null,
|
||||||
persistedLastSpeed: 1.0,
|
persistedLastSpeed: 1.0,
|
||||||
activeSiteRule: null
|
activeSiteRule: null,
|
||||||
|
siteRuleBase: null
|
||||||
};
|
};
|
||||||
|
|
||||||
var MIN_SPEED = 0.0625;
|
var MIN_SPEED = 0.0625;
|
||||||
@@ -80,17 +85,17 @@ var controllerLocationStyles = {
|
|||||||
transform: "translate(-100%, -50%)"
|
transform: "translate(-100%, -50%)"
|
||||||
},
|
},
|
||||||
"bottom-right": {
|
"bottom-right": {
|
||||||
top: "calc(100% - 65px)",
|
top: "calc(100% - 0px)",
|
||||||
left: "calc(100% - 10px)",
|
left: "calc(100% - 10px)",
|
||||||
transform: "translate(-100%, -100%)"
|
transform: "translate(-100%, -100%)"
|
||||||
},
|
},
|
||||||
"bottom-center": {
|
"bottom-center": {
|
||||||
top: "calc(100% - 65px)",
|
top: "calc(100% - 0px)",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -100%)"
|
transform: "translate(-50%, -100%)"
|
||||||
},
|
},
|
||||||
"bottom-left": {
|
"bottom-left": {
|
||||||
top: "calc(100% - 65px)",
|
top: "calc(100% - 0px)",
|
||||||
left: "15px",
|
left: "15px",
|
||||||
transform: "translate(0, -100%)"
|
transform: "translate(0, -100%)"
|
||||||
},
|
},
|
||||||
@@ -254,6 +259,52 @@ function normalizeControllerLocation(location) {
|
|||||||
return defaultControllerLocation;
|
return defaultControllerLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CONTROLLER_MARGIN_MAX_PX = 200;
|
||||||
|
|
||||||
|
function normalizeControllerMarginPx(value, fallback) {
|
||||||
|
var n = Number(value);
|
||||||
|
if (!Number.isFinite(n)) return fallback;
|
||||||
|
return Math.min(
|
||||||
|
CONTROLLER_MARGIN_MAX_PX,
|
||||||
|
Math.max(0, Math.round(n))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyControllerMargins(controller) {
|
||||||
|
if (!controller) return;
|
||||||
|
var d = tc.settings;
|
||||||
|
var loc = controller.dataset.location;
|
||||||
|
var manual = controller.dataset.positionMode === "manual";
|
||||||
|
var isTopAnchored =
|
||||||
|
!manual &&
|
||||||
|
(loc === "top-left" ||
|
||||||
|
loc === "top-center" ||
|
||||||
|
loc === "top-right");
|
||||||
|
var isBottomAnchored =
|
||||||
|
!manual &&
|
||||||
|
(loc === "bottom-right" ||
|
||||||
|
loc === "bottom-center" ||
|
||||||
|
loc === "bottom-left");
|
||||||
|
var isMiddleRow =
|
||||||
|
!manual && (loc === "middle-left" || loc === "middle-right");
|
||||||
|
var mt = normalizeControllerMarginPx(d.controllerMarginTop, 0);
|
||||||
|
var mb = normalizeControllerMarginPx(d.controllerMarginBottom, 65);
|
||||||
|
if (isTopAnchored || isBottomAnchored || isMiddleRow) {
|
||||||
|
mt = 0;
|
||||||
|
mb = 0;
|
||||||
|
}
|
||||||
|
controller.style.marginTop = mt + "px";
|
||||||
|
var ml = normalizeControllerMarginPx(d.controllerMarginLeft, 0);
|
||||||
|
var mr = normalizeControllerMarginPx(d.controllerMarginRight, 0);
|
||||||
|
if (!manual) {
|
||||||
|
ml = 0;
|
||||||
|
mr = 0;
|
||||||
|
}
|
||||||
|
controller.style.marginRight = mr + "px";
|
||||||
|
controller.style.marginBottom = mb + "px";
|
||||||
|
controller.style.marginLeft = ml + "px";
|
||||||
|
}
|
||||||
|
|
||||||
function getNextControllerLocation(location) {
|
function getNextControllerLocation(location) {
|
||||||
var normalizedLocation = normalizeControllerLocation(location);
|
var normalizedLocation = normalizeControllerLocation(location);
|
||||||
var currentIndex = controllerLocations.indexOf(normalizedLocation);
|
var currentIndex = controllerLocations.indexOf(normalizedLocation);
|
||||||
@@ -290,6 +341,28 @@ function applyControllerLocationToElement(controller, location) {
|
|||||||
controller.dataset.positionMode = "anchored";
|
controller.dataset.positionMode = "anchored";
|
||||||
|
|
||||||
var top = styles.top;
|
var top = styles.top;
|
||||||
|
if (
|
||||||
|
normalizedLocation === "top-left" ||
|
||||||
|
normalizedLocation === "top-center" ||
|
||||||
|
normalizedLocation === "top-right"
|
||||||
|
) {
|
||||||
|
var insetTop = normalizeControllerMarginPx(
|
||||||
|
tc.settings.controllerMarginTop,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
top = "calc(10px + " + insetTop + "px)";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
normalizedLocation === "bottom-right" ||
|
||||||
|
normalizedLocation === "bottom-center" ||
|
||||||
|
normalizedLocation === "bottom-left"
|
||||||
|
) {
|
||||||
|
var lift = normalizeControllerMarginPx(
|
||||||
|
tc.settings.controllerMarginBottom,
|
||||||
|
65
|
||||||
|
);
|
||||||
|
top = "calc(100% - " + lift + "px)";
|
||||||
|
}
|
||||||
// If in fullscreen, move the controller down to avoid overlapping video titles
|
// If in fullscreen, move the controller down to avoid overlapping video titles
|
||||||
if (
|
if (
|
||||||
document.fullscreenElement ||
|
document.fullscreenElement ||
|
||||||
@@ -298,14 +371,40 @@ function applyControllerLocationToElement(controller, location) {
|
|||||||
document.msFullscreenElement
|
document.msFullscreenElement
|
||||||
) {
|
) {
|
||||||
if (normalizedLocation.startsWith("top-")) {
|
if (normalizedLocation.startsWith("top-")) {
|
||||||
top = "63px";
|
var insetTopFs = normalizeControllerMarginPx(
|
||||||
|
tc.settings.controllerMarginTop,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
top = "calc(63px + " + insetTopFs + "px)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.style.top = top;
|
controller.style.top = top;
|
||||||
controller.style.left = styles.left;
|
|
||||||
|
var left = styles.left;
|
||||||
|
switch (normalizedLocation) {
|
||||||
|
case "top-left":
|
||||||
|
case "middle-left":
|
||||||
|
case "bottom-left":
|
||||||
|
left = "15px";
|
||||||
|
break;
|
||||||
|
case "top-right":
|
||||||
|
case "middle-right":
|
||||||
|
case "bottom-right":
|
||||||
|
left = "calc(100% - 10px)";
|
||||||
|
break;
|
||||||
|
case "top-center":
|
||||||
|
case "bottom-center":
|
||||||
|
left = "50%";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
controller.style.left = left;
|
||||||
controller.style.transform = styles.transform;
|
controller.style.transform = styles.transform;
|
||||||
|
|
||||||
|
applyControllerMargins(controller);
|
||||||
|
|
||||||
return normalizedLocation;
|
return normalizedLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +420,56 @@ function applyControllerLocation(videoController, location) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function captureSiteRuleBase() {
|
||||||
|
tc.siteRuleBase = {
|
||||||
|
startHidden: tc.settings.startHidden,
|
||||||
|
hideWithControls: tc.settings.hideWithControls,
|
||||||
|
hideWithControlsTimer: tc.settings.hideWithControlsTimer,
|
||||||
|
controllerLocation: tc.settings.controllerLocation,
|
||||||
|
rememberSpeed: tc.settings.rememberSpeed,
|
||||||
|
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed,
|
||||||
|
audioBoolean: tc.settings.audioBoolean,
|
||||||
|
controllerOpacity: tc.settings.controllerOpacity,
|
||||||
|
controllerMarginTop: tc.settings.controllerMarginTop,
|
||||||
|
controllerMarginBottom: tc.settings.controllerMarginBottom,
|
||||||
|
enableSubtitleNudge: tc.settings.enableSubtitleNudge,
|
||||||
|
subtitleNudgeInterval: tc.settings.subtitleNudgeInterval,
|
||||||
|
controllerButtons: Array.isArray(tc.settings.controllerButtons)
|
||||||
|
? tc.settings.controllerButtons.slice()
|
||||||
|
: tc.settings.controllerButtons,
|
||||||
|
keyBindings: Array.isArray(tc.settings.keyBindings)
|
||||||
|
? tc.settings.keyBindings.map(function (binding) {
|
||||||
|
return Object.assign({}, binding);
|
||||||
|
})
|
||||||
|
: tc.settings.keyBindings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSettingsFromSiteRuleBase() {
|
||||||
|
if (!tc.siteRuleBase) return;
|
||||||
|
var base = tc.siteRuleBase;
|
||||||
|
tc.settings.startHidden = base.startHidden;
|
||||||
|
tc.settings.hideWithControls = base.hideWithControls;
|
||||||
|
tc.settings.hideWithControlsTimer = base.hideWithControlsTimer;
|
||||||
|
tc.settings.controllerLocation = base.controllerLocation;
|
||||||
|
tc.settings.rememberSpeed = base.rememberSpeed;
|
||||||
|
tc.settings.forceLastSavedSpeed = base.forceLastSavedSpeed;
|
||||||
|
tc.settings.audioBoolean = base.audioBoolean;
|
||||||
|
tc.settings.controllerOpacity = base.controllerOpacity;
|
||||||
|
tc.settings.controllerMarginTop = base.controllerMarginTop;
|
||||||
|
tc.settings.controllerMarginBottom = base.controllerMarginBottom;
|
||||||
|
tc.settings.enableSubtitleNudge = base.enableSubtitleNudge;
|
||||||
|
tc.settings.subtitleNudgeInterval = base.subtitleNudgeInterval;
|
||||||
|
tc.settings.controllerButtons = Array.isArray(base.controllerButtons)
|
||||||
|
? base.controllerButtons.slice()
|
||||||
|
: base.controllerButtons;
|
||||||
|
tc.settings.keyBindings = Array.isArray(base.keyBindings)
|
||||||
|
? base.keyBindings.map(function (binding) {
|
||||||
|
return Object.assign({}, binding);
|
||||||
|
})
|
||||||
|
: base.keyBindings;
|
||||||
|
}
|
||||||
|
|
||||||
function clearManualControllerPosition(videoController) {
|
function clearManualControllerPosition(videoController) {
|
||||||
if (!videoController) return;
|
if (!videoController) return;
|
||||||
applyControllerLocation(
|
applyControllerLocation(
|
||||||
@@ -483,10 +632,36 @@ function getVideoSourceKey(video) {
|
|||||||
|
|
||||||
function getControllerTargetSpeed(video) {
|
function getControllerTargetSpeed(video) {
|
||||||
if (!video || !video.vsc) return null;
|
if (!video || !video.vsc) return null;
|
||||||
return isValidSpeed(video.vsc.targetSpeed) ? video.vsc.targetSpeed : null;
|
if (!isValidSpeed(video.vsc.targetSpeed)) return null;
|
||||||
|
|
||||||
|
var currentSourceKey = getVideoSourceKey(video);
|
||||||
|
var targetSourceKey = video.vsc.targetSpeedSourceKey;
|
||||||
|
|
||||||
|
// SPA sites (e.g. YouTube) can reuse the same <video> element.
|
||||||
|
// Don't carry controller target speed across a source swap.
|
||||||
|
if (
|
||||||
|
targetSourceKey &&
|
||||||
|
currentSourceKey === "unknown_src" &&
|
||||||
|
targetSourceKey !== "unknown_src"
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
targetSourceKey &&
|
||||||
|
currentSourceKey !== "unknown_src" &&
|
||||||
|
targetSourceKey !== currentSourceKey
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return video.vsc.targetSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRememberedSpeed(video) {
|
function getRememberedSpeed(video) {
|
||||||
|
if (!tc.settings.rememberSpeed && !tc.settings.forceLastSavedSpeed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var sourceKey = getVideoSourceKey(video);
|
var sourceKey = getVideoSourceKey(video);
|
||||||
if (sourceKey !== "unknown_src") {
|
if (sourceKey !== "unknown_src") {
|
||||||
var videoSpeed = tc.settings.speeds[sourceKey];
|
var videoSpeed = tc.settings.speeds[sourceKey];
|
||||||
@@ -987,6 +1162,22 @@ chrome.storage.sync.get(tc.settings, function (storage) {
|
|||||||
storage.controllerLocation
|
storage.controllerLocation
|
||||||
);
|
);
|
||||||
tc.settings.controllerOpacity = Number(storage.controllerOpacity);
|
tc.settings.controllerOpacity = Number(storage.controllerOpacity);
|
||||||
|
tc.settings.controllerMarginTop = normalizeControllerMarginPx(
|
||||||
|
storage.controllerMarginTop,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
tc.settings.controllerMarginRight = normalizeControllerMarginPx(
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
tc.settings.controllerMarginBottom = normalizeControllerMarginPx(
|
||||||
|
storage.controllerMarginBottom,
|
||||||
|
typeof storage.controllerMarginBottom !== "undefined" ? 0 : 65
|
||||||
|
);
|
||||||
|
tc.settings.controllerMarginLeft = normalizeControllerMarginPx(
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
tc.settings.siteRules = Array.isArray(storage.siteRules)
|
tc.settings.siteRules = Array.isArray(storage.siteRules)
|
||||||
? storage.siteRules
|
? storage.siteRules
|
||||||
: [];
|
: [];
|
||||||
@@ -1041,6 +1232,7 @@ chrome.storage.sync.get(tc.settings, function (storage) {
|
|||||||
if (addedDefaultBinding) {
|
if (addedDefaultBinding) {
|
||||||
chrome.storage.sync.set({ keyBindings: tc.settings.keyBindings });
|
chrome.storage.sync.set({ keyBindings: tc.settings.keyBindings });
|
||||||
}
|
}
|
||||||
|
captureSiteRuleBase();
|
||||||
patchAttachShadow();
|
patchAttachShadow();
|
||||||
// Add a listener for messages from the popup.
|
// Add a listener for messages from the popup.
|
||||||
// We use a global flag to ensure the listener is only attached once.
|
// We use a global flag to ensure the listener is only attached once.
|
||||||
@@ -1135,6 +1327,7 @@ function defineVideoController() {
|
|||||||
|
|
||||||
let storedSpeed = sanitizeSpeed(resolveTargetSpeed(target), 1.0);
|
let storedSpeed = sanitizeSpeed(resolveTargetSpeed(target), 1.0);
|
||||||
this.targetSpeed = storedSpeed;
|
this.targetSpeed = storedSpeed;
|
||||||
|
this.targetSpeedSourceKey = getVideoSourceKey(target);
|
||||||
if (!tc.settings.rememberSpeed && !tc.settings.forceLastSavedSpeed) {
|
if (!tc.settings.rememberSpeed && !tc.settings.forceLastSavedSpeed) {
|
||||||
setKeyBindings("reset", getKeyBindings("fast"));
|
setKeyBindings("reset", getKeyBindings("fast"));
|
||||||
}
|
}
|
||||||
@@ -1725,6 +1918,8 @@ function escapeStringRegExp(str) {
|
|||||||
return str.replace(m, "\\$&");
|
return str.replace(m, "\\$&");
|
||||||
}
|
}
|
||||||
function applySiteRuleOverrides() {
|
function applySiteRuleOverrides() {
|
||||||
|
resetSettingsFromSiteRuleBase();
|
||||||
|
|
||||||
if (!Array.isArray(tc.settings.siteRules) || tc.settings.siteRules.length === 0) {
|
if (!Array.isArray(tc.settings.siteRules) || tc.settings.siteRules.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1784,6 +1979,8 @@ function applySiteRuleOverrides() {
|
|||||||
"forceLastSavedSpeed",
|
"forceLastSavedSpeed",
|
||||||
"audioBoolean",
|
"audioBoolean",
|
||||||
"controllerOpacity",
|
"controllerOpacity",
|
||||||
|
"controllerMarginTop",
|
||||||
|
"controllerMarginBottom",
|
||||||
"enableSubtitleNudge",
|
"enableSubtitleNudge",
|
||||||
"subtitleNudgeInterval"
|
"subtitleNudgeInterval"
|
||||||
];
|
];
|
||||||
@@ -1795,6 +1992,13 @@ function applySiteRuleOverrides() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
"controllerMarginTop",
|
||||||
|
"controllerMarginBottom"
|
||||||
|
].forEach(function (key) {
|
||||||
|
tc.settings[key] = normalizeControllerMarginPx(tc.settings[key], 0);
|
||||||
|
});
|
||||||
|
|
||||||
if (Array.isArray(matchedRule.controllerButtons)) {
|
if (Array.isArray(matchedRule.controllerButtons)) {
|
||||||
log(`Overriding controllerButtons for site`, 4);
|
log(`Overriding controllerButtons for site`, 4);
|
||||||
tc.settings.controllerButtons = matchedRule.controllerButtons;
|
tc.settings.controllerButtons = matchedRule.controllerButtons;
|
||||||
@@ -1842,6 +2046,7 @@ function setupListener(root) {
|
|||||||
var speed = video.playbackRate; // Preserve full precision (e.g. 0.0625)
|
var speed = video.playbackRate; // Preserve full precision (e.g. 0.0625)
|
||||||
video.vsc.speedIndicator.textContent = speed.toFixed(2);
|
video.vsc.speedIndicator.textContent = speed.toFixed(2);
|
||||||
video.vsc.targetSpeed = speed;
|
video.vsc.targetSpeed = speed;
|
||||||
|
video.vsc.targetSpeedSourceKey = getVideoSourceKey(video);
|
||||||
var sourceKey = getVideoSourceKey(video);
|
var sourceKey = getVideoSourceKey(video);
|
||||||
if (sourceKey !== "unknown_src") {
|
if (sourceKey !== "unknown_src") {
|
||||||
tc.settings.speeds[sourceKey] = speed;
|
tc.settings.speeds[sourceKey] = speed;
|
||||||
@@ -2161,6 +2366,14 @@ function initializeNow(doc, forceReinit = false) {
|
|||||||
|
|
||||||
if (forceReinit) {
|
if (forceReinit) {
|
||||||
log("Force re-initialization requested", 4);
|
log("Force re-initialization requested", 4);
|
||||||
|
tc.mediaElements.forEach(function (video) {
|
||||||
|
if (!video || !video.vsc) return;
|
||||||
|
applyControllerLocation(video.vsc, tc.settings.controllerLocation);
|
||||||
|
var controllerEl = getControllerElement(video.vsc);
|
||||||
|
if (controllerEl) {
|
||||||
|
controllerEl.style.opacity = String(tc.settings.controllerOpacity);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vscInitializedDocuments.add(doc);
|
vscInitializedDocuments.add(doc);
|
||||||
@@ -2188,6 +2401,7 @@ function setSpeed(video, speed, isInitialCall = false, isUserKeyPress = false) {
|
|||||||
|
|
||||||
// Update the target speed for nudge so it knows what to revert to
|
// Update the target speed for nudge so it knows what to revert to
|
||||||
video.vsc.targetSpeed = numericSpeed;
|
video.vsc.targetSpeed = numericSpeed;
|
||||||
|
video.vsc.targetSpeedSourceKey = getVideoSourceKey(video);
|
||||||
|
|
||||||
if (isUserKeyPress && !isInitialCall && video.vsc && video.vsc.div) {
|
if (isUserKeyPress && !isInitialCall && video.vsc && video.vsc.div) {
|
||||||
runAction("blink", 1000, null, video); // Pass video to blink
|
runAction("blink", 1000, null, video); // Pass video to blink
|
||||||
|
|||||||
+72
@@ -304,6 +304,78 @@ label em {
|
|||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row.row-controller-margin {
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 260px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller-margin-inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-pad-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-pad-mini {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller-margin-inputs input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-rule-option.site-rule-margin-option {
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 220px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-rule-override-section {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-rule-content > .site-rule-override-section:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-override-lead {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-override-lead input {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-rule-override-section .site-override-fields,
|
||||||
|
.site-rule-override-section .site-placement-container,
|
||||||
|
.site-rule-override-section .site-visibility-container,
|
||||||
|
.site-rule-override-section .site-autohide-container,
|
||||||
|
.site-rule-override-section .site-playback-container,
|
||||||
|
.site-rule-override-section .site-opacity-container,
|
||||||
|
.site-rule-override-section .site-subtitleNudge-container {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.cb-editor {
|
.cb-editor {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
+142
-56
@@ -231,6 +231,25 @@
|
|||||||
<label for="controllerOpacity">Controller opacity</label>
|
<label for="controllerOpacity">Controller opacity</label>
|
||||||
<input id="controllerOpacity" type="text" value="" />
|
<input id="controllerOpacity" type="text" value="" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row row-controller-margin">
|
||||||
|
<label for="controllerMarginTop"
|
||||||
|
>Controller margin (px)<br />
|
||||||
|
<em
|
||||||
|
>Shifts the whole control from its preset position (CSS
|
||||||
|
margins). Top and bottom. 0–200.</em
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<div class="controller-margin-inputs" aria-label="Controller margin in pixels">
|
||||||
|
<div class="margin-pad-cell">
|
||||||
|
<span class="margin-pad-mini">Top</span>
|
||||||
|
<input id="controllerMarginTop" type="text" inputmode="numeric" placeholder="0" />
|
||||||
|
</div>
|
||||||
|
<div class="margin-pad-cell">
|
||||||
|
<span class="margin-pad-mini">Bottom</span>
|
||||||
|
<input id="controllerMarginBottom" type="text" inputmode="numeric" placeholder="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="hideWithControls"
|
<label for="hideWithControls"
|
||||||
>Hide with controls<br />
|
>Hide with controls<br />
|
||||||
@@ -349,7 +368,9 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>Regex</a
|
>Regex</a
|
||||||
>
|
>
|
||||||
patterns like <code>/(.+)youtube\.com(\/*)$/gi</code>.
|
patterns like <code>/(.+)youtube\.com(\/*)$/gi</code>. Turn on a
|
||||||
|
row only when you want that group to override the general defaults
|
||||||
|
above.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="siteRulesContainer"></div>
|
<div id="siteRulesContainer"></div>
|
||||||
@@ -375,65 +396,126 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-content">
|
<div class="site-rule-content">
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-override-section">
|
||||||
<label>Hide controller by default:</label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="site-startHidden" />
|
<input type="checkbox" class="override-placement" />
|
||||||
|
Override placement for this site
|
||||||
|
</label>
|
||||||
|
<div class="site-placement-container" style="display: none">
|
||||||
|
<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 site-rule-margin-option">
|
||||||
|
<label
|
||||||
|
>Controller margin (px):<br /><em
|
||||||
|
>Shifts the whole control. 0–200.</em
|
||||||
|
></label
|
||||||
|
>
|
||||||
|
<div class="controller-margin-inputs">
|
||||||
|
<div class="margin-pad-cell">
|
||||||
|
<span class="margin-pad-mini">T</span>
|
||||||
|
<input type="text" class="site-controllerMarginTop" inputmode="numeric" placeholder="0" />
|
||||||
|
</div>
|
||||||
|
<div class="margin-pad-cell">
|
||||||
|
<span class="margin-pad-mini">B</span>
|
||||||
|
<input type="text" class="site-controllerMarginBottom" inputmode="numeric" placeholder="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-override-section">
|
||||||
<label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="site-hideWithControls" />
|
<input type="checkbox" class="override-visibility" />
|
||||||
Hide with controls (idle-based)
|
Override hide-by-default for this site
|
||||||
</label>
|
</label>
|
||||||
</div>
|
<div class="site-visibility-container" style="display: none">
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-option">
|
||||||
<label>Auto-hide timer (0.1–15s):</label>
|
<label>Hide controller by default:</label>
|
||||||
<input type="text" class="site-hideWithControlsTimer" />
|
<input type="checkbox" class="site-startHidden" />
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-option">
|
</div>
|
||||||
<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>
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-override-section">
|
||||||
<label>Remember playback speed:</label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="site-rememberSpeed" />
|
<input type="checkbox" class="override-autohide" />
|
||||||
|
Override auto-hide for this site
|
||||||
|
</label>
|
||||||
|
<div class="site-autohide-container" style="display: none">
|
||||||
|
<div class="site-rule-option">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="site-hideWithControls" />
|
||||||
|
Hide with controls (idle-based)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="site-rule-option">
|
||||||
|
<label>Auto-hide timer (0.1–15s):</label>
|
||||||
|
<input type="text" class="site-hideWithControlsTimer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-override-section">
|
||||||
<label>Force last saved speed:</label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="site-forceLastSavedSpeed" />
|
<input type="checkbox" class="override-playback" />
|
||||||
|
Override playback for this site
|
||||||
|
</label>
|
||||||
|
<div class="site-playback-container" style="display: none">
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-override-section">
|
||||||
<label>Work on audio:</label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="site-audioBoolean" />
|
<input type="checkbox" class="override-opacity" />
|
||||||
|
Override opacity for this site
|
||||||
|
</label>
|
||||||
|
<div class="site-opacity-container" style="display: none">
|
||||||
|
<div class="site-rule-option">
|
||||||
|
<label>Controller opacity:</label>
|
||||||
|
<input type="text" class="site-controllerOpacity" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-option">
|
<div class="site-rule-override-section">
|
||||||
<label>Controller opacity:</label>
|
<label class="site-override-lead">
|
||||||
<input type="text" class="site-controllerOpacity" />
|
<input type="checkbox" class="override-subtitleNudge" />
|
||||||
</div>
|
Override subtitle nudge for this site
|
||||||
<div class="site-rule-option">
|
</label>
|
||||||
<label>Show popup control bar:</label>
|
<div class="site-subtitleNudge-container" style="display: none">
|
||||||
<input type="checkbox" class="site-showPopupControlBar" />
|
<div class="site-rule-option">
|
||||||
</div>
|
<label>Enable subtitle nudge:</label>
|
||||||
<div class="site-rule-option">
|
<input type="checkbox" class="site-enableSubtitleNudge" />
|
||||||
<label>Enable subtitle nudge:</label>
|
</div>
|
||||||
<input type="checkbox" class="site-enableSubtitleNudge" />
|
<div class="site-rule-option">
|
||||||
</div>
|
<label>Nudge interval (10–1000ms):</label>
|
||||||
<div class="site-rule-option">
|
<input type="text" class="site-subtitleNudgeInterval" placeholder="50" />
|
||||||
<label>Nudge interval (10–1000ms):</label>
|
</div>
|
||||||
<input type="text" class="site-subtitleNudgeInterval" placeholder="50" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-controlbar">
|
<div class="site-rule-controlbar">
|
||||||
<label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="override-controlbar" />
|
<input type="checkbox" class="override-controlbar" />
|
||||||
Custom control bar for this site
|
Override in-player control bar for this site
|
||||||
</label>
|
</label>
|
||||||
<div class="site-controlbar-container" style="display: none">
|
<div class="site-controlbar-container" style="display: none">
|
||||||
<div class="cb-editor">
|
<div class="cb-editor">
|
||||||
@@ -449,11 +531,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-controlbar">
|
<div class="site-rule-controlbar">
|
||||||
<label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="override-popup-controlbar" />
|
<input type="checkbox" class="override-popup-controlbar" />
|
||||||
Custom popup control bar for this site
|
Override extension popup for this site
|
||||||
</label>
|
</label>
|
||||||
<div class="site-popup-controlbar-container" style="display: none">
|
<div class="site-popup-controlbar-container" style="display: none">
|
||||||
|
<div class="site-rule-option">
|
||||||
|
<label>Show popup control bar</label>
|
||||||
|
<input type="checkbox" class="site-showPopupControlBar" />
|
||||||
|
</div>
|
||||||
<div class="cb-editor">
|
<div class="cb-editor">
|
||||||
<div class="cb-zone">
|
<div class="cb-zone">
|
||||||
<div class="cb-zone-label">Active</div>
|
<div class="cb-zone-label">Active</div>
|
||||||
@@ -467,9 +553,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="site-rule-shortcuts">
|
<div class="site-rule-shortcuts">
|
||||||
<label>
|
<label class="site-override-lead">
|
||||||
<input type="checkbox" class="override-shortcuts" />
|
<input type="checkbox" class="override-shortcuts" />
|
||||||
Custom shortcuts for this site
|
Override shortcuts for this site
|
||||||
</label>
|
</label>
|
||||||
<div class="site-shortcuts-container" style="display: none"></div>
|
<div class="site-shortcuts-container" style="display: none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+221
-78
@@ -173,6 +173,10 @@ var tcDefaults = {
|
|||||||
forceLastSavedSpeed: false,
|
forceLastSavedSpeed: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
controllerOpacity: 0.3,
|
controllerOpacity: 0.3,
|
||||||
|
controllerMarginTop: 0,
|
||||||
|
controllerMarginRight: 0,
|
||||||
|
controllerMarginBottom: 65,
|
||||||
|
controllerMarginLeft: 0,
|
||||||
keyBindings: [
|
keyBindings: [
|
||||||
createDefaultBinding("display", "V", 86, 0),
|
createDefaultBinding("display", "V", 86, 0),
|
||||||
createDefaultBinding("move", "P", 80, 0),
|
createDefaultBinding("move", "P", 80, 0),
|
||||||
@@ -185,10 +189,18 @@ var tcDefaults = {
|
|||||||
createDefaultBinding("toggleSubtitleNudge", "N", 78, 0)
|
createDefaultBinding("toggleSubtitleNudge", "N", 78, 0)
|
||||||
],
|
],
|
||||||
siteRules: [
|
siteRules: [
|
||||||
{ pattern: "youtube.com", enabled: true, enableSubtitleNudge: true },
|
{
|
||||||
{ pattern: "example1.com", enabled: false },
|
pattern: "/^https:\\/\\/(www\\.)?youtube\\.com\\/(?!shorts\\/).*/",
|
||||||
{ pattern: "/example2\\.com/i", enabled: false },
|
enabled: true,
|
||||||
{ pattern: "/(example3|sample3)\\.com/gi", enabled: false }
|
enableSubtitleNudge: true,
|
||||||
|
subtitleNudgeInterval: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "/^https:\\/\\/(www\\.)?youtube\\.com\\/shorts\\/.*/",
|
||||||
|
enabled: true,
|
||||||
|
controllerMarginTop: 60,
|
||||||
|
controllerMarginBottom: 85
|
||||||
|
}
|
||||||
],
|
],
|
||||||
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
|
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
|
||||||
showPopupControlBar: true,
|
showPopupControlBar: true,
|
||||||
@@ -275,6 +287,28 @@ function normalizeControllerLocation(location) {
|
|||||||
return tcDefaults.controllerLocation;
|
return tcDefaults.controllerLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampMarginPxInput(el, fallback) {
|
||||||
|
var n = parseInt(el && el.value, 10);
|
||||||
|
if (!Number.isFinite(n)) return fallback;
|
||||||
|
return Math.min(200, Math.max(0, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
function normalizeBindingKey(key) {
|
||||||
if (typeof key !== "string" || key.length === 0) return null;
|
if (typeof key !== "string" || key.length === 0) return null;
|
||||||
if (key === "Spacebar") return " ";
|
if (key === "Spacebar") return " ";
|
||||||
@@ -614,6 +648,16 @@ function save_options() {
|
|||||||
settings.controllerOpacity =
|
settings.controllerOpacity =
|
||||||
parseFloat(document.getElementById("controllerOpacity").value) ||
|
parseFloat(document.getElementById("controllerOpacity").value) ||
|
||||||
tcDefaults.controllerOpacity;
|
tcDefaults.controllerOpacity;
|
||||||
|
|
||||||
|
settings.controllerMarginTop = clampMarginPxInput(
|
||||||
|
document.getElementById("controllerMarginTop"),
|
||||||
|
tcDefaults.controllerMarginTop
|
||||||
|
);
|
||||||
|
settings.controllerMarginBottom = clampMarginPxInput(
|
||||||
|
document.getElementById("controllerMarginBottom"),
|
||||||
|
tcDefaults.controllerMarginBottom
|
||||||
|
);
|
||||||
|
|
||||||
settings.keyBindings = keyBindings;
|
settings.keyBindings = keyBindings;
|
||||||
settings.enableSubtitleNudge =
|
settings.enableSubtitleNudge =
|
||||||
document.getElementById("enableSubtitleNudge").checked;
|
document.getElementById("enableSubtitleNudge").checked;
|
||||||
@@ -647,40 +691,69 @@ function save_options() {
|
|||||||
// Handle Enable toggle
|
// Handle Enable toggle
|
||||||
rule.enabled = ruleEl.querySelector(".site-enabled").checked;
|
rule.enabled = ruleEl.querySelector(".site-enabled").checked;
|
||||||
|
|
||||||
// Handle other site settings
|
if (ruleEl.querySelector(".override-placement").checked) {
|
||||||
const siteSettings = [
|
rule.controllerLocation = normalizeControllerLocation(
|
||||||
{ key: "startHidden", type: "checkbox" },
|
ruleEl.querySelector(".site-controllerLocation").value
|
||||||
{ key: "hideWithControls", type: "checkbox" },
|
);
|
||||||
{ key: "hideWithControlsTimer", type: "text" },
|
rule.controllerMarginTop = clampMarginPxInput(
|
||||||
{ key: "controllerLocation", type: "select" },
|
ruleEl.querySelector(".site-controllerMarginTop"),
|
||||||
{ key: "rememberSpeed", type: "checkbox" },
|
clampMarginPxInput(
|
||||||
{ key: "forceLastSavedSpeed", type: "checkbox" },
|
document.getElementById("controllerMarginTop"),
|
||||||
{ key: "audioBoolean", type: "checkbox" },
|
tcDefaults.controllerMarginTop
|
||||||
{ key: "controllerOpacity", type: "text" },
|
)
|
||||||
{ key: "showPopupControlBar", type: "checkbox" },
|
);
|
||||||
{ key: "enableSubtitleNudge", type: "checkbox" },
|
rule.controllerMarginBottom = clampMarginPxInput(
|
||||||
{ key: "subtitleNudgeInterval", type: "text" }
|
ruleEl.querySelector(".site-controllerMarginBottom"),
|
||||||
];
|
clampMarginPxInput(
|
||||||
|
document.getElementById("controllerMarginBottom"),
|
||||||
|
tcDefaults.controllerMarginBottom
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
siteSettings.forEach((s) => {
|
if (ruleEl.querySelector(".override-visibility").checked) {
|
||||||
var input = ruleEl.querySelector(`.site-${s.key}`);
|
rule.startHidden = ruleEl.querySelector(".site-startHidden").checked;
|
||||||
if (!input) return;
|
}
|
||||||
var siteValue;
|
|
||||||
if (s.type === "checkbox") {
|
if (ruleEl.querySelector(".override-autohide").checked) {
|
||||||
siteValue = input.checked;
|
rule.hideWithControls = ruleEl.querySelector(".site-hideWithControls").checked;
|
||||||
} else {
|
var st = parseFloat(
|
||||||
siteValue = input.value;
|
ruleEl.querySelector(".site-hideWithControlsTimer").value
|
||||||
}
|
);
|
||||||
var globalInput = document.getElementById(s.key);
|
rule.hideWithControlsTimer = Math.min(
|
||||||
if (globalInput) {
|
15,
|
||||||
var globalValue = s.type === "checkbox" ? globalInput.checked : globalInput.value;
|
Math.max(0.1, Number.isFinite(st) ? st : settings.hideWithControlsTimer)
|
||||||
if (String(siteValue) !== String(globalValue)) {
|
);
|
||||||
rule[s.key] = siteValue;
|
}
|
||||||
}
|
|
||||||
} else {
|
if (ruleEl.querySelector(".override-playback").checked) {
|
||||||
rule[s.key] = siteValue;
|
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 =
|
||||||
|
parseFloat(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) {
|
if (ruleEl.querySelector(".override-controlbar").checked) {
|
||||||
var activeZone = ruleEl.querySelector(".site-cb-active");
|
var activeZone = ruleEl.querySelector(".site-cb-active");
|
||||||
@@ -690,6 +763,8 @@ function save_options() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ruleEl.querySelector(".override-popup-controlbar").checked) {
|
if (ruleEl.querySelector(".override-popup-controlbar").checked) {
|
||||||
|
rule.showPopupControlBar =
|
||||||
|
ruleEl.querySelector(".site-showPopupControlBar").checked;
|
||||||
var popupActiveZone = ruleEl.querySelector(".site-popup-cb-active");
|
var popupActiveZone = ruleEl.querySelector(".site-popup-cb-active");
|
||||||
if (popupActiveZone) {
|
if (popupActiveZone) {
|
||||||
rule.popupControllerButtons = readControlBarOrder(popupActiveZone);
|
rule.popupControllerButtons = readControlBarOrder(popupActiveZone);
|
||||||
@@ -892,45 +967,68 @@ function createSiteRule(rule) {
|
|||||||
}
|
}
|
||||||
updateDisabledState();
|
updateDisabledState();
|
||||||
|
|
||||||
const settings = [
|
var placementKeys = [
|
||||||
{ key: "startHidden", type: "checkbox" },
|
"controllerLocation",
|
||||||
{ key: "hideWithControls", type: "checkbox" },
|
"controllerMarginTop",
|
||||||
{ key: "hideWithControlsTimer", type: "text" },
|
"controllerMarginBottom"
|
||||||
{ key: "controllerLocation", type: "select" },
|
|
||||||
{ key: "rememberSpeed", type: "checkbox" },
|
|
||||||
{ key: "forceLastSavedSpeed", type: "checkbox" },
|
|
||||||
{ key: "audioBoolean", type: "checkbox" },
|
|
||||||
{ key: "controllerOpacity", type: "text" },
|
|
||||||
{ key: "showPopupControlBar", type: "checkbox" },
|
|
||||||
{ key: "enableSubtitleNudge", type: "checkbox" },
|
|
||||||
{ key: "subtitleNudgeInterval", type: "text" }
|
|
||||||
];
|
];
|
||||||
|
var hasPlacementOverride =
|
||||||
|
rule && placementKeys.some(function (k) { return rule[k] !== undefined; });
|
||||||
|
if (hasPlacementOverride) {
|
||||||
|
ruleEl.querySelector(".override-placement").checked = true;
|
||||||
|
ruleEl.querySelector(".site-placement-container").style.display = "block";
|
||||||
|
}
|
||||||
|
syncSiteRuleField(ruleEl, rule, "controllerLocation", false);
|
||||||
|
syncSiteRuleField(ruleEl, rule, "controllerMarginTop", false);
|
||||||
|
syncSiteRuleField(ruleEl, rule, "controllerMarginBottom", false);
|
||||||
|
|
||||||
settings.forEach((s) => {
|
if (rule && rule.startHidden !== undefined) {
|
||||||
var input = ruleEl.querySelector(`.site-${s.key}`);
|
ruleEl.querySelector(".override-visibility").checked = true;
|
||||||
if (!input) return;
|
ruleEl.querySelector(".site-visibility-container").style.display = "block";
|
||||||
|
}
|
||||||
|
syncSiteRuleField(ruleEl, rule, "startHidden", true);
|
||||||
|
|
||||||
var value;
|
if (
|
||||||
if (rule && rule[s.key] !== undefined) {
|
rule &&
|
||||||
value = rule[s.key];
|
(rule.hideWithControls !== undefined ||
|
||||||
} else {
|
rule.hideWithControlsTimer !== undefined)
|
||||||
// Initialize with current global value
|
) {
|
||||||
var globalInput = document.getElementById(s.key);
|
ruleEl.querySelector(".override-autohide").checked = true;
|
||||||
if (globalInput) {
|
ruleEl.querySelector(".site-autohide-container").style.display = "block";
|
||||||
if (s.type === "checkbox") {
|
}
|
||||||
value = globalInput.checked;
|
syncSiteRuleField(ruleEl, rule, "hideWithControls", true);
|
||||||
} else {
|
syncSiteRuleField(ruleEl, rule, "hideWithControlsTimer", false);
|
||||||
value = globalInput.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.type === "checkbox") {
|
if (
|
||||||
input.checked = value;
|
rule &&
|
||||||
} else {
|
(rule.rememberSpeed !== undefined ||
|
||||||
input.value = value;
|
rule.forceLastSavedSpeed !== undefined ||
|
||||||
}
|
rule.audioBoolean !== undefined)
|
||||||
});
|
) {
|
||||||
|
ruleEl.querySelector(".override-playback").checked = true;
|
||||||
|
ruleEl.querySelector(".site-playback-container").style.display = "block";
|
||||||
|
}
|
||||||
|
syncSiteRuleField(ruleEl, rule, "rememberSpeed", true);
|
||||||
|
syncSiteRuleField(ruleEl, rule, "forceLastSavedSpeed", true);
|
||||||
|
syncSiteRuleField(ruleEl, rule, "audioBoolean", true);
|
||||||
|
|
||||||
|
if (rule && rule.controllerOpacity !== undefined) {
|
||||||
|
ruleEl.querySelector(".override-opacity").checked = true;
|
||||||
|
ruleEl.querySelector(".site-opacity-container").style.display = "block";
|
||||||
|
}
|
||||||
|
syncSiteRuleField(ruleEl, rule, "controllerOpacity", false);
|
||||||
|
|
||||||
|
if (
|
||||||
|
rule &&
|
||||||
|
(rule.enableSubtitleNudge !== undefined ||
|
||||||
|
rule.subtitleNudgeInterval !== undefined)
|
||||||
|
) {
|
||||||
|
ruleEl.querySelector(".override-subtitleNudge").checked = true;
|
||||||
|
ruleEl.querySelector(".site-subtitleNudge-container").style.display =
|
||||||
|
"block";
|
||||||
|
}
|
||||||
|
syncSiteRuleField(ruleEl, rule, "enableSubtitleNudge", true);
|
||||||
|
syncSiteRuleField(ruleEl, rule, "subtitleNudgeInterval", false);
|
||||||
|
|
||||||
if (rule && Array.isArray(rule.controllerButtons)) {
|
if (rule && Array.isArray(rule.controllerButtons)) {
|
||||||
ruleEl.querySelector(".override-controlbar").checked = true;
|
ruleEl.querySelector(".override-controlbar").checked = true;
|
||||||
@@ -943,16 +1041,35 @@ function createSiteRule(rule) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rule && Array.isArray(rule.popupControllerButtons)) {
|
if (
|
||||||
|
rule &&
|
||||||
|
(rule.showPopupControlBar !== undefined ||
|
||||||
|
Array.isArray(rule.popupControllerButtons))
|
||||||
|
) {
|
||||||
ruleEl.querySelector(".override-popup-controlbar").checked = true;
|
ruleEl.querySelector(".override-popup-controlbar").checked = true;
|
||||||
var popupCbContainer = ruleEl.querySelector(".site-popup-controlbar-container");
|
var popupCbContainer = ruleEl.querySelector(".site-popup-controlbar-container");
|
||||||
popupCbContainer.style.display = "block";
|
popupCbContainer.style.display = "block";
|
||||||
populateControlBarZones(
|
var sitePopupActive = ruleEl.querySelector(".site-popup-cb-active");
|
||||||
ruleEl.querySelector(".site-popup-cb-active"),
|
var sitePopupAvailable = ruleEl.querySelector(".site-popup-cb-available");
|
||||||
ruleEl.querySelector(".site-popup-cb-available"),
|
if (Array.isArray(rule.popupControllerButtons)) {
|
||||||
rule.popupControllerButtons
|
populateControlBarZones(
|
||||||
);
|
sitePopupActive,
|
||||||
|
sitePopupAvailable,
|
||||||
|
rule.popupControllerButtons
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
sitePopupActive &&
|
||||||
|
sitePopupAvailable &&
|
||||||
|
sitePopupActive.children.length === 0
|
||||||
|
) {
|
||||||
|
populateControlBarZones(
|
||||||
|
sitePopupActive,
|
||||||
|
sitePopupAvailable,
|
||||||
|
getPopupControlBarOrder()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
syncSiteRuleField(ruleEl, rule, "showPopupControlBar", true);
|
||||||
|
|
||||||
if (rule && Array.isArray(rule.shortcuts) && rule.shortcuts.length > 0) {
|
if (rule && Array.isArray(rule.shortcuts) && rule.shortcuts.length > 0) {
|
||||||
ruleEl.querySelector(".override-shortcuts").checked = true;
|
ruleEl.querySelector(".override-shortcuts").checked = true;
|
||||||
@@ -1170,6 +1287,10 @@ function restore_options() {
|
|||||||
normalizeControllerLocation(storage.controllerLocation);
|
normalizeControllerLocation(storage.controllerLocation);
|
||||||
document.getElementById("controllerOpacity").value =
|
document.getElementById("controllerOpacity").value =
|
||||||
storage.controllerOpacity;
|
storage.controllerOpacity;
|
||||||
|
document.getElementById("controllerMarginTop").value =
|
||||||
|
storage.controllerMarginTop ?? tcDefaults.controllerMarginTop;
|
||||||
|
document.getElementById("controllerMarginBottom").value =
|
||||||
|
storage.controllerMarginBottom ?? tcDefaults.controllerMarginBottom;
|
||||||
document.getElementById("showPopupControlBar").checked =
|
document.getElementById("showPopupControlBar").checked =
|
||||||
storage.showPopupControlBar !== false;
|
storage.showPopupControlBar !== false;
|
||||||
document.getElementById("enableSubtitleNudge").checked =
|
document.getElementById("enableSubtitleNudge").checked =
|
||||||
@@ -1353,6 +1474,28 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
};
|
||||||
|
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) {
|
||||||
|
targetBox.style.display = event.target.checked ? "block" : "none";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle site rule override checkboxes
|
// Handle site rule override checkboxes
|
||||||
if (event.target.classList.contains("override-shortcuts")) {
|
if (event.target.classList.contains("override-shortcuts")) {
|
||||||
var container = event.target
|
var container = event.target
|
||||||
|
|||||||
+12
-7
@@ -29,6 +29,11 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: top 160ms ease, left 160ms ease, transform 160ms ease,
|
transition: top 160ms ease, left 160ms ease, transform 160ms ease,
|
||||||
opacity 160ms ease;
|
opacity 160ms ease;
|
||||||
|
/* Insets are baked into left/top; bar must not wrap when narrow. Hover controls
|
||||||
|
extend into the inset area (past “logical” margin). */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: visible;
|
||||||
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controller:hover {
|
#controller:hover {
|
||||||
@@ -39,20 +44,20 @@
|
|||||||
margin-right: 0.8em;
|
margin-right: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For center positions, override transform to expand from left edge instead of center.
|
/* Center presets: midpoint between left- and right-preset inset lines; center bar on that X. */
|
||||||
Exclude manual mode so dragging can freely reposition the controller. */
|
#controller[data-location="top-center"]:not([data-position-mode="manual"]) {
|
||||||
#controller[data-location="top-center"]:not([data-position-mode="manual"]),
|
transform: translate(-50%, 0) !important;
|
||||||
#controller[data-location="bottom-center"]:not([data-position-mode="manual"]) {
|
|
||||||
transform: translate(0, 0) !important;
|
|
||||||
left: calc(50% - 30px) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#controller[data-location="bottom-center"]:not([data-position-mode="manual"]) {
|
#controller[data-location="bottom-center"]:not([data-position-mode="manual"]) {
|
||||||
transform: translate(0, -100%) !important;
|
transform: translate(-50%, -100%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
display: none;
|
display: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: visible;
|
||||||
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls > * + * {
|
#controls > * + * {
|
||||||
|
|||||||
Reference in New Issue
Block a user