Compare commits

...

12 Commits

Author SHA1 Message Date
joshpatra fad0c49e65 v5.1.3-beta.1 2026-04-02 13:56:22 -04:00
joshpatra 66075fb6f3 Bump version to 5.1.3 2026-04-02 13:56:21 -04:00
joshpatra bf4025dcb4 fix: settings update 2026-04-02 13:54:01 -04:00
joshpatra 76a7b933bb v5.1.2-beta.1 2026-04-02 13:52:04 -04:00
joshpatra 1cd533fc5c Bump version to 5.1.2 2026-04-02 13:52:02 -04:00
joshpatra 8c5bd68d39 fix: popup control bar section layout in options 2026-04-02 13:44:03 -04:00
joshpatra 9c257af446 feat: omit settings from popup control bar 2026-04-02 13:43:56 -04:00
joshpatra 64a9b85587 fix: control bar icon clicks, hover/focus-within, nudge action 2026-04-02 13:43:43 -04:00
joshpatra edd997037a v5.1.1-beta.1 2026-04-02 13:11:47 -04:00
joshpatra f85a1f9f29 Bump version to 5.1.1 2026-04-02 13:11:46 -04:00
joshpatra 97366b76b6 chore: open options in tab 2026-04-02 13:09:09 -04:00
joshpatra 8269875bb1 fix: removed divider 2026-04-02 13:01:14 -04:00
7 changed files with 97 additions and 42 deletions
+22 -10
View File
@@ -1939,14 +1939,20 @@ function defineVideoController() {
if (subtitleNudgeIndicator) { if (subtitleNudgeIndicator) {
updateSubtitleNudgeIndicator(this.video); updateSubtitleNudgeIndicator(this.video);
} }
function blurAfterPointerTap(target, e) {
if (!target || typeof target.blur !== "function") return;
var pt = e.pointerType;
if (pt === "mouse" || pt === "touch" || (!pt && e.detail > 0)) {
requestAnimationFrame(function () {
target.blur();
});
}
}
dragHandle.addEventListener( dragHandle.addEventListener(
"mousedown", "mousedown",
(e) => { (e) => {
runAction( var dragAction = dragHandle.dataset.action;
e.target.dataset["action"], runAction(dragAction, getKeyBindings(dragAction, "value"), e);
getKeyBindings(e.target.dataset["action"], "value"),
e
);
e.stopPropagation(); e.stopPropagation();
}, },
true true
@@ -1955,11 +1961,9 @@ function defineVideoController() {
button.addEventListener( button.addEventListener(
"click", "click",
(e) => { (e) => {
runAction( var action = button.dataset.action;
e.target.dataset["action"], runAction(action, getKeyBindings(action), e);
getKeyBindings(e.target.dataset["action"]), blurAfterPointerTap(button, e);
e
);
e.stopPropagation(); e.stopPropagation();
}, },
true true
@@ -1974,6 +1978,7 @@ function defineVideoController() {
var newState = !isSubtitleNudgeEnabledForVideo(video); var newState = !isSubtitleNudgeEnabledForVideo(video);
setSubtitleNudgeEnabledForVideo(video, newState); setSubtitleNudgeEnabledForVideo(video, newState);
} }
blurAfterPointerTap(subtitleNudgeIndicator, e);
e.stopPropagation(); e.stopPropagation();
}, },
true true
@@ -2667,6 +2672,7 @@ function runAction(action, value, e) {
"mark", "mark",
"jump", "jump",
"drag", "drag",
"nudge",
"toggleSubtitleNudge", "toggleSubtitleNudge",
"display" "display"
]; ];
@@ -2782,6 +2788,12 @@ function runAction(action, value, e) {
case "toggleSubtitleNudge": case "toggleSubtitleNudge":
setSubtitleNudgeEnabledForVideo(v, subtitleNudgeToggleValue); setSubtitleNudgeEnabledForVideo(v, subtitleNudgeToggleValue);
break; break;
case "nudge":
setSubtitleNudgeEnabledForVideo(
v,
!isSubtitleNudgeEnabledForVideo(v)
);
break;
} }
}); });
log("runAction End", 5); log("runAction End", 5);
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "Speeder", "name": "Speeder",
"short_name": "Speeder", "short_name": "Speeder",
"version": "5.1.0", "version": "5.1.3",
"manifest_version": 2, "manifest_version": 2,
"description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts (New and improved version of \"Video Speed Controller\")", "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts (New and improved version of \"Video Speed Controller\")",
"homepage_url": "https://github.com/SoPat712/speeder", "homepage_url": "https://github.com/SoPat712/speeder",
@@ -31,7 +31,7 @@
], ],
"options_ui": { "options_ui": {
"page": "options.html", "page": "options.html",
"open_in_tab": false "open_in_tab": true
}, },
"browser_action": { "browser_action": {
"default_icon": { "default_icon": {
-7
View File
@@ -873,13 +873,6 @@ button.lucide-result-tile.lucide-picked {
display: none; display: none;
} }
#faq hr {
height: 1px;
margin: 0 0 14px;
border: 0;
background: var(--border);
}
.support-footer { .support-footer {
padding: 16px 20px; padding: 16px 20px;
color: var(--muted); color: var(--muted);
+5 -8
View File
@@ -272,11 +272,6 @@
</label> </label>
<input id="hideWithControlsTimer" type="text" placeholder="2" /> <input id="hideWithControlsTimer" type="text" placeholder="2" />
</div> </div>
<div class="row row-checkbox">
<label for="showPopupControlBar">Show popup control bar</label>
<input id="showPopupControlBar" type="checkbox" />
</div>
<div class="defaults-divider"></div> <div class="defaults-divider"></div>
<h4 class="defaults-sub-heading">Subtitle sync</h4> <h4 class="defaults-sub-heading">Subtitle sync</h4>
@@ -350,7 +345,11 @@
Configure which buttons appear in the browser popup control bar. Configure which buttons appear in the browser popup control bar.
</p> </p>
</div> </div>
<div class="row"> <div class="row row-checkbox">
<label for="showPopupControlBar">Show popup control bar</label>
<input id="showPopupControlBar" type="checkbox" />
</div>
<div class="row row-checkbox">
<label for="popupMatchHoverControls">Match hover controls</label> <label for="popupMatchHoverControls">Match hover controls</label>
<input id="popupMatchHoverControls" type="checkbox" /> <input id="popupMatchHoverControls" type="checkbox" />
</div> </div>
@@ -678,8 +677,6 @@
</section> </section>
<section id="faq" class="settings-card info-card"> <section id="faq" class="settings-card info-card">
<hr />
<h4>Extension controls not appearing?</h4> <h4>Extension controls not appearing?</h4>
<p> <p>
This extension only works with HTML5 audio and video. If the This extension only works with HTML5 audio and video. If the
+45 -8
View File
@@ -147,6 +147,19 @@ var controllerButtonDefs = {
mark: { icon: "\u2691", name: "Set marker" }, mark: { icon: "\u2691", name: "Set marker" },
jump: { icon: "\u21E5", name: "Jump to marker" } jump: { icon: "\u21E5", name: "Jump to marker" }
}; };
var popupExcludedButtonIds = new Set(["settings"]);
function sanitizePopupButtonOrder(buttonIds) {
if (!Array.isArray(buttonIds)) return [];
var seen = new Set();
return buttonIds.filter(function (id) {
if (!controllerButtonDefs[id] || popupExcludedButtonIds.has(id) || seen.has(id)) {
return false;
}
seen.add(id);
return true;
});
}
/** Cached custom Lucide SVGs (mirrors chrome.storage.local customButtonIcons). */ /** Cached custom Lucide SVGs (mirrors chrome.storage.local customButtonIcons). */
var customButtonIconsLive = {}; var customButtonIconsLive = {};
@@ -713,7 +726,7 @@ function save_options() {
document.getElementById("showPopupControlBar").checked; document.getElementById("showPopupControlBar").checked;
settings.popupMatchHoverControls = settings.popupMatchHoverControls =
document.getElementById("popupMatchHoverControls").checked; document.getElementById("popupMatchHoverControls").checked;
settings.popupControllerButtons = getPopupControlBarOrder(); settings.popupControllerButtons = sanitizePopupButtonOrder(getPopupControlBarOrder());
// Collect site rules // Collect site rules
settings.siteRules = []; settings.siteRules = [];
@@ -802,7 +815,9 @@ function save_options() {
ruleEl.querySelector(".site-showPopupControlBar").checked; 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 = sanitizePopupButtonOrder(
readControlBarOrder(popupActiveZone)
);
} }
} }
@@ -1071,7 +1086,10 @@ function createSiteRule(rule) {
populateControlBarZones( populateControlBarZones(
sitePopupActive, sitePopupActive,
sitePopupAvailable, sitePopupAvailable,
rule.popupControllerButtons sanitizePopupButtonOrder(rule.popupControllerButtons),
function (id) {
return !popupExcludedButtonIds.has(id);
}
); );
} else if ( } else if (
sitePopupActive && sitePopupActive &&
@@ -1081,7 +1099,10 @@ function createSiteRule(rule) {
populateControlBarZones( populateControlBarZones(
sitePopupActive, sitePopupActive,
sitePopupAvailable, sitePopupAvailable,
getPopupControlBarOrder() getPopupControlBarOrder(),
function (id) {
return !popupExcludedButtonIds.has(id);
}
); );
} }
} }
@@ -1139,16 +1160,23 @@ function createControlBarBlock(buttonId) {
return block; return block;
} }
function populateControlBarZones(activeZone, availableZone, activeIds) { function populateControlBarZones(activeZone, availableZone, activeIds, allowButtonId) {
activeZone.innerHTML = ""; activeZone.innerHTML = "";
availableZone.innerHTML = ""; availableZone.innerHTML = "";
var allowed = function (id) {
if (!controllerButtonDefs[id]) return false;
return typeof allowButtonId === "function" ? Boolean(allowButtonId(id)) : true;
};
activeIds.forEach(function (id) { activeIds.forEach(function (id) {
if (!allowed(id)) return;
var block = createControlBarBlock(id); var block = createControlBarBlock(id);
if (block) activeZone.appendChild(block); if (block) activeZone.appendChild(block);
}); });
Object.keys(controllerButtonDefs).forEach(function (id) { Object.keys(controllerButtonDefs).forEach(function (id) {
if (!allowed(id)) return;
if (!activeIds.includes(id)) { if (!activeIds.includes(id)) {
var block = createControlBarBlock(id); var block = createControlBarBlock(id);
if (block) availableZone.appendChild(block); if (block) availableZone.appendChild(block);
@@ -1176,15 +1204,21 @@ function getControlBarOrder() {
} }
function populatePopupControlBarEditor(activeIds) { function populatePopupControlBarEditor(activeIds) {
var popupActiveIds = sanitizePopupButtonOrder(activeIds);
populateControlBarZones( populateControlBarZones(
document.getElementById("popupControlBarActive"), document.getElementById("popupControlBarActive"),
document.getElementById("popupControlBarAvailable"), document.getElementById("popupControlBarAvailable"),
activeIds popupActiveIds,
function (id) {
return !popupExcludedButtonIds.has(id);
}
); );
} }
function getPopupControlBarOrder() { function getPopupControlBarOrder() {
return readControlBarOrder(document.getElementById("popupControlBarActive")); return sanitizePopupButtonOrder(
readControlBarOrder(document.getElementById("popupControlBarActive"))
);
} }
function updatePopupEditorDisabledState() { function updatePopupEditorDisabledState() {
@@ -1771,7 +1805,10 @@ document.addEventListener("DOMContentLoaded", function () {
populateControlBarZones( populateControlBarZones(
popupActiveZone, popupActiveZone,
popupAvailableZone, popupAvailableZone,
getPopupControlBarOrder() getPopupControlBarOrder(),
function (id) {
return !popupExcludedButtonIds.has(id);
}
); );
} }
} else { } else {
+19 -6
View File
@@ -10,6 +10,7 @@ document.addEventListener("DOMContentLoaded", function () {
display: { label: "", className: "hideButton" }, display: { label: "", className: "hideButton" },
reset: { label: "", className: "" }, reset: { label: "", className: "" },
fast: { label: "", className: "" }, fast: { label: "", className: "" },
nudge: { label: "", className: "" },
settings: { label: "", className: "" }, settings: { label: "", className: "" },
pause: { label: "", className: "" }, pause: { label: "", className: "" },
muted: { label: "", className: "" }, muted: { label: "", className: "" },
@@ -18,6 +19,7 @@ document.addEventListener("DOMContentLoaded", function () {
}; };
var defaultButtons = ["rewind", "slower", "faster", "advance", "display"]; var defaultButtons = ["rewind", "slower", "faster", "advance", "display"];
var popupExcludedButtonIds = new Set(["settings"]);
var storageDefaults = { var storageDefaults = {
enabled: true, enabled: true,
showPopupControlBar: true, showPopupControlBar: true,
@@ -64,25 +66,37 @@ document.addEventListener("DOMContentLoaded", function () {
} }
function resolvePopupButtons(storage, siteRule) { function resolvePopupButtons(storage, siteRule) {
function sanitize(buttons) {
if (!Array.isArray(buttons)) return [];
var seen = new Set();
return buttons.filter(function (id) {
if (!controllerButtonDefs[id] || popupExcludedButtonIds.has(id) || seen.has(id)) {
return false;
}
seen.add(id);
return true;
});
}
if (siteRule && Array.isArray(siteRule.popupControllerButtons)) { if (siteRule && Array.isArray(siteRule.popupControllerButtons)) {
return siteRule.popupControllerButtons; return sanitize(siteRule.popupControllerButtons);
} }
if (storage.popupMatchHoverControls) { if (storage.popupMatchHoverControls) {
if (siteRule && Array.isArray(siteRule.controllerButtons)) { if (siteRule && Array.isArray(siteRule.controllerButtons)) {
return siteRule.controllerButtons; return sanitize(siteRule.controllerButtons);
} }
if (Array.isArray(storage.controllerButtons)) { if (Array.isArray(storage.controllerButtons)) {
return storage.controllerButtons; return sanitize(storage.controllerButtons);
} }
} }
if (Array.isArray(storage.popupControllerButtons)) { if (Array.isArray(storage.popupControllerButtons)) {
return storage.popupControllerButtons; return sanitize(storage.popupControllerButtons);
} }
return defaultButtons; return sanitize(defaultButtons);
} }
function setControlBarVisible(visible) { function setControlBarVisible(visible) {
@@ -209,7 +223,6 @@ document.addEventListener("DOMContentLoaded", function () {
var customMap = customIconsMap || {}; var customMap = customIconsMap || {};
buttons.forEach(function (btnId) { buttons.forEach(function (btnId) {
if (btnId === "nudge") return;
var def = controllerButtonDefs[btnId]; var def = controllerButtonDefs[btnId];
if (!def) return; if (!def) return;
+4 -1
View File
@@ -10,8 +10,11 @@
line-height: 1; line-height: 1;
} }
/* Show extra buttons on hover or keyboard :focus-visible only. Plain :focus-within
after a mouse click kept #controls visible while hover-only rules (e.g. draggable
margin) turned off when the pointer left the bar. */
#controller:hover #controls, #controller:hover #controls,
#controller:focus-within #controls, #controller:focus-within:has(:focus-visible) #controls,
:host(:hover) #controls { :host(:hover) #controls {
display: inline-flex; display: inline-flex;
vertical-align: middle; vertical-align: middle;