feat(ui): configurable control bar, popup controls, and settings overhaul

This commit is contained in:
2026-03-31 00:47:31 -04:00
parent b4030f91dd
commit 6d993edf50
10 changed files with 1191 additions and 248 deletions
+5
View File
@@ -0,0 +1,5 @@
chrome.runtime.onMessage.addListener(function (request) {
if (request.action === "openOptions") {
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
}
});
+103 -49
View File
@@ -20,6 +20,7 @@ var tc = {
controllerOpacity: 0.3,
keyBindings: [],
siteRules: [],
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
defaultLogLevel: 3,
logLevel: 3,
enableSubtitleNudge: true, // Enabled by default, but only activates on YouTube
@@ -100,6 +101,21 @@ var controllerLocationStyles = {
}
};
var controllerButtonDefs = {
rewind: { label: "\u00AB", className: "rw" },
slower: { label: "\u2212", className: "" },
faster: { label: "+", className: "" },
advance: { label: "\u00BB", className: "rw" },
display: { label: "\u00D7", className: "hideButton" },
reset: { label: "\u21BA", className: "" },
fast: { label: "\u2605", className: "" },
settings: { label: "\u2699", className: "" },
pause: { label: "\u23EF", className: "" },
muted: { label: "M", className: "" },
mark: { label: "\u2691", className: "" },
jump: { label: "\u21E5", className: "" }
};
var keyCodeToEventKey = {
32: " ",
37: "ArrowLeft",
@@ -519,11 +535,7 @@ function tryYouTubeNativeSpeed(video, speed) {
}
function isSubtitleNudgeSupported(video) {
return Boolean(
video &&
((video.currentSrc && video.currentSrc.includes("googlevideo.com")) ||
isOnYouTube())
);
return Boolean(video);
}
function isSubtitleNudgeEnabledForVideo(video) {
@@ -566,31 +578,24 @@ function setSubtitleNudgeEnabledForVideo(video, enabled) {
function updateSubtitleNudgeIndicator(video) {
if (!video || !video.vsc) return;
var isSupported = isSubtitleNudgeSupported(video);
var isEnabled = isSupported && isSubtitleNudgeEnabledForVideo(video);
var isEnabled = isSubtitleNudgeEnabledForVideo(video);
var label = isEnabled ? "✓" : "×";
var title = isSupported
? isEnabled
? "Subtitle nudge enabled"
: "Subtitle nudge disabled"
: "Subtitle nudge unavailable on this site";
var title = isEnabled ? "Subtitle nudge enabled" : "Subtitle nudge disabled";
// Update the hover indicator (inside #controls)
var indicator = video.vsc.subtitleNudgeIndicator;
if (indicator) {
indicator.textContent = label;
indicator.dataset.enabled = isEnabled ? "true" : "false";
indicator.dataset.supported = isSupported ? "true" : "false";
indicator.dataset.supported = "true";
indicator.title = title;
indicator.setAttribute("aria-label", title);
}
// Sync the flash indicator (next to speed text)
var flashEl = video.vsc.nudgeFlashIndicator;
if (flashEl) {
flashEl.textContent = label;
flashEl.dataset.enabled = isEnabled ? "true" : "false";
flashEl.dataset.supported = isSupported ? "true" : "false";
flashEl.dataset.supported = "true";
}
}
@@ -969,6 +974,10 @@ chrome.storage.sync.get(tc.settings, function (storage) {
? storage.siteRules
: [];
tc.settings.controllerButtons = Array.isArray(storage.controllerButtons)
? storage.controllerButtons
: tc.settings.controllerButtons;
// Migrate legacy blacklist if present
if (storage.blacklist && typeof storage.blacklist === "string" && tc.settings.siteRules.length === 0) {
var lines = storage.blacklist.split("\n");
@@ -1021,15 +1030,37 @@ chrome.storage.sync.get(tc.settings, function (storage) {
if (!window.vscMessageListener) {
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
// Check if the message is a request to re-scan the page.
if (request.action === "rescan_page") {
log("Re-scan command received from popup.", 4);
initializeWhenReady(document, true);
sendResponse({ status: "complete" });
} else if (request.action === "get_speed") {
var speed = 1.0;
if (tc.mediaElements && tc.mediaElements.length > 0) {
for (var i = 0; i < tc.mediaElements.length; i++) {
if (tc.mediaElements[i] && !tc.mediaElements[i].paused) {
speed = tc.mediaElements[i].playbackRate;
break;
}
}
if (speed === 1.0 && tc.mediaElements[0]) {
speed = tc.mediaElements[0].playbackRate;
}
}
sendResponse({ speed: speed });
} else if (request.action === "run_action") {
var value = request.value;
if (value === undefined || value === null) {
value = getKeyBindings(request.actionName, "value");
}
runAction(request.actionName, value);
var newSpeed = 1.0;
if (tc.mediaElements && tc.mediaElements.length > 0) {
newSpeed = tc.mediaElements[0].playbackRate;
}
sendResponse({ speed: newSpeed });
}
// Required to allow for asynchronous responses.
return true;
}
);
@@ -1513,36 +1544,46 @@ function defineVideoController() {
var controls = doc.createElement("span");
controls.id = "controls";
controls.appendChild(createControllerButton(doc, "rewind", "«", "rw"));
controls.appendChild(createControllerButton(doc, "slower", ""));
controls.appendChild(createControllerButton(doc, "faster", "+"));
controls.appendChild(createControllerButton(doc, "advance", "»", "rw"));
controls.appendChild(
createControllerButton(doc, "display", "×", "hideButton")
);
var subtitleNudgeIndicator = doc.createElement("span");
subtitleNudgeIndicator.id = "nudge-indicator";
subtitleNudgeIndicator.setAttribute("role", "button");
subtitleNudgeIndicator.setAttribute("aria-live", "polite");
subtitleNudgeIndicator.setAttribute("tabindex", "0");
var buttonConfig = Array.isArray(tc.settings.controllerButtons)
? tc.settings.controllerButtons
: ["rewind", "slower", "faster", "advance", "display"];
var subtitleNudgeIndicator = null;
buttonConfig.forEach(function (btnId) {
if (btnId === "nudge") {
subtitleNudgeIndicator = doc.createElement("span");
subtitleNudgeIndicator.id = "nudge-indicator";
subtitleNudgeIndicator.setAttribute("role", "button");
subtitleNudgeIndicator.setAttribute("aria-live", "polite");
subtitleNudgeIndicator.setAttribute("tabindex", "0");
controls.appendChild(subtitleNudgeIndicator);
} else {
var def = controllerButtonDefs[btnId];
if (def) {
controls.appendChild(
createControllerButton(doc, btnId, def.label, def.className)
);
}
}
});
// A second indicator that lives outside #controls, next to the speed text.
// Hidden by default, briefly shown when N is pressed to flash the state.
var nudgeFlashIndicator = doc.createElement("span");
nudgeFlashIndicator.id = "nudge-flash-indicator";
nudgeFlashIndicator.setAttribute("aria-hidden", "true");
controller.appendChild(dragHandle);
controller.appendChild(nudgeFlashIndicator);
controls.appendChild(subtitleNudgeIndicator);
controller.appendChild(controls);
shadow.appendChild(controller);
this.speedIndicator = dragHandle;
this.subtitleNudgeIndicator = subtitleNudgeIndicator;
this.nudgeFlashIndicator = nudgeFlashIndicator;
updateSubtitleNudgeIndicator(this.video);
if (subtitleNudgeIndicator) {
updateSubtitleNudgeIndicator(this.video);
}
dragHandle.addEventListener(
"mousedown",
(e) => {
@@ -1569,21 +1610,23 @@ function defineVideoController() {
true
);
});
subtitleNudgeIndicator.addEventListener(
"click",
(e) => {
var video = this.video;
if (video) {
var newState = !isSubtitleNudgeEnabledForVideo(video);
setSubtitleNudgeEnabledForVideo(video, newState);
}
e.stopPropagation();
},
true
);
if (subtitleNudgeIndicator) {
subtitleNudgeIndicator.addEventListener(
"click",
(e) => {
var video = this.video;
if (video) {
var newState = !isSubtitleNudgeEnabledForVideo(video);
setSubtitleNudgeEnabledForVideo(video, newState);
}
e.stopPropagation();
},
true
);
}
controller.addEventListener("click", (e) => e.stopPropagation(), false);
controller.addEventListener("mousedown", (e) => e.stopPropagation(), false);
// Setup auto-hide observers if enabled
if (tc.settings.hideWithControls) {
if (isOnYouTube()) {
@@ -1721,7 +1764,9 @@ function applySiteRuleOverrides() {
"rememberSpeed",
"forceLastSavedSpeed",
"audioBoolean",
"controllerOpacity"
"controllerOpacity",
"enableSubtitleNudge",
"subtitleNudgeInterval"
];
siteSettings.forEach((key) => {
@@ -1731,6 +1776,11 @@ function applySiteRuleOverrides() {
}
});
if (Array.isArray(matchedRule.controllerButtons) && matchedRule.controllerButtons.length > 0) {
log(`Overriding controllerButtons for site`, 4);
tc.settings.controllerButtons = matchedRule.controllerButtons;
}
// Override key bindings with site-specific shortcuts
if (Array.isArray(matchedRule.shortcuts) && matchedRule.shortcuts.length > 0) {
var overriddenActions = new Set();
@@ -2183,6 +2233,10 @@ function runAction(action, value, e) {
} else {
mediaTagsToProcess = tc.mediaElements;
}
if (action === "settings") {
chrome.runtime.sendMessage({ action: "openOptions" });
return;
}
if (mediaTagsToProcess.length === 0 && action !== "display") return;
if (action === "toggleSubtitleNudge" && mediaTagsToProcess.length > 0) {
+3
View File
@@ -20,6 +20,9 @@
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"background": {
"scripts": ["background.js"]
},
"permissions": [
"storage"
],
+137
View File
@@ -108,6 +108,19 @@ h4 {
margin-bottom: 10px;
}
.defaults-divider {
height: 1px;
background: var(--border);
margin: 14px 0 10px;
}
.defaults-sub-heading {
font-size: 13px;
font-weight: 600;
color: var(--muted);
margin-bottom: 8px;
}
p {
margin: 0.75em 0;
}
@@ -291,6 +304,119 @@ label em {
border-top: 0;
}
.cb-editor {
display: flex;
flex-direction: column;
gap: 16px;
}
.cb-editor-disabled {
opacity: 0.4;
pointer-events: none;
user-select: none;
}
.cb-zone-label {
font-size: 11px;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
}
.cb-dropzone {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-height: 52px;
padding: 10px;
border: 2px dashed var(--border);
border-radius: 10px;
background: var(--panel-subtle);
transition: border-color 150ms ease, background 150ms ease;
}
.cb-dropzone.cb-over {
border-color: var(--accent);
background: rgba(17, 24, 39, 0.03);
}
.cb-active-zone:empty::after {
content: "Drag buttons here";
display: flex;
align-items: center;
width: 100%;
justify-content: center;
color: var(--muted);
font-size: 13px;
font-style: italic;
}
.cb-available-zone:empty::after {
content: "All buttons active";
display: flex;
align-items: center;
width: 100%;
justify-content: center;
color: var(--muted);
font-size: 13px;
font-style: italic;
}
.cb-block {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 12px 7px 8px;
border: 1px solid var(--border-strong);
border-radius: 8px;
background: var(--panel);
cursor: grab;
user-select: none;
transition: box-shadow 150ms ease, opacity 150ms ease;
}
.cb-block:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.cb-block:active {
cursor: grabbing;
}
.cb-block.cb-dragging {
opacity: 0.35;
}
.cb-grip {
width: 6px;
min-width: 6px;
height: 14px;
background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
background-size: 3px 3px;
opacity: 0.35;
}
.cb-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 4px;
background: var(--panel-subtle);
border: 1px solid var(--border);
font-size: 12px;
line-height: 1;
}
.cb-label {
font-size: 13px;
font-weight: 500;
white-space: nowrap;
}
#siteRulesContainer {
display: grid;
gap: 12px;
@@ -347,12 +473,14 @@ label em {
width: auto;
}
.site-rule-controlbar,
.site-rule-shortcuts {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.site-rule-controlbar > label,
.site-rule-shortcuts > label {
display: flex;
align-items: flex-start;
@@ -361,6 +489,11 @@ label em {
margin: 0;
}
.site-controlbar-container,
.site-popup-controlbar-container {
margin-top: 12px;
}
.site-shortcuts-container {
display: flex;
flex-direction: column;
@@ -505,6 +638,10 @@ label em {
border-color: #dfe3e8;
}
.cb-dropzone.cb-over {
background: rgba(255, 255, 255, 0.04);
}
input[type="text"]:focus,
select:focus,
textarea:focus {
+182 -68
View File
@@ -177,34 +177,43 @@
<h3>Defaults</h3>
<p class="section-intro">Used unless a site rule overrides it.</p>
</div>
<h4 class="defaults-sub-heading">General</h4>
<div class="row">
<label for="enabled">Enable</label>
<input id="enabled" type="checkbox" />
</div>
<div class="row">
<label for="audioBoolean">Work on audio</label>
<input id="audioBoolean" type="checkbox" />
</div>
<div class="defaults-divider"></div>
<h4 class="defaults-sub-heading">Playback</h4>
<div class="row">
<label for="rememberSpeed">Remember playback speed</label>
<input id="rememberSpeed" type="checkbox" />
</div>
<div class="row">
<label for="forceLastSavedSpeed"
>Force last saved speed<br />
<em
>Useful when a video player tries to override the speed you set
in Speeder.</em
>
</label>
<input id="forceLastSavedSpeed" type="checkbox" />
</div>
<div class="defaults-divider"></div>
<h4 class="defaults-sub-heading">Controller</h4>
<div class="row">
<label for="startHidden">Hide controller by default</label>
<input id="startHidden" type="checkbox" />
</div>
<div class="row">
<label for="hideWithControls"
>Hide with controls (all sites)<br />
<em
>Fade the controller in and out with the video interface:
perfect sync on YouTube, idle-based elsewhere.</em
>
</label>
<input id="hideWithControls" type="checkbox" />
</div>
<div class="row">
<label for="hideWithControlsTimer"
>Auto-hide timer (seconds)<br />
<em
>Seconds of inactivity before hiding: 0.1&ndash;15 for non-YouTube
sites.</em
>
</label>
<input id="hideWithControlsTimer" type="text" placeholder="2" />
</div>
<div class="row">
<label for="controllerLocation">Default controller location</label>
<select id="controllerLocation">
@@ -218,28 +227,115 @@
<option value="middle-left">Middle left</option>
</select>
</div>
<div class="row">
<label for="rememberSpeed">Remember playback speed</label>
<input id="rememberSpeed" type="checkbox" />
</div>
<div class="row">
<label for="forceLastSavedSpeed"
>Force last saved speed<br />
<em
>Useful when a video player tries to override the speed you set
in Speeder.</em
>
</label>
<input id="forceLastSavedSpeed" type="checkbox" />
</div>
<div class="row">
<label for="audioBoolean">Work on audio</label>
<input id="audioBoolean" type="checkbox" />
</div>
<div class="row">
<label for="controllerOpacity">Controller opacity</label>
<input id="controllerOpacity" type="text" value="" />
</div>
<div class="row">
<label for="hideWithControls"
>Hide with controls<br />
<em
>Fade the controller in and out with the video interface:
perfect sync on YouTube, idle-based elsewhere.</em
>
</label>
<input id="hideWithControls" type="checkbox" />
</div>
<div class="row">
<label for="hideWithControlsTimer"
>Auto-hide timer (seconds)<br />
<em
>Seconds of inactivity before hiding: 0.1&ndash;15 for
non-YouTube sites.</em
>
</label>
<input id="hideWithControlsTimer" type="text" placeholder="2" />
</div>
<div class="row">
<label for="showPopupControlBar">Show popup control bar</label>
<input id="showPopupControlBar" type="checkbox" />
</div>
<div class="defaults-divider"></div>
<h4 class="defaults-sub-heading">Subtitle sync</h4>
<div class="row">
<label for="enableSubtitleNudge"
>Enable subtitle nudge<br /><em
>Makes tiny playback changes to help keep subtitles aligned.</em
>
</label>
<input id="enableSubtitleNudge" type="checkbox" />
</div>
<div class="row">
<label for="subtitleNudgeInterval"
>Nudge interval (milliseconds)<br /><em
>How often to nudge: 10&ndash;1000. Smaller values are more
frequent. Default: 50.</em
>
</label>
<input
id="subtitleNudgeInterval"
type="text"
value=""
placeholder="50"
/>
</div>
</section>
<section id="controlBarSettings" class="settings-card">
<div class="section-heading">
<h3>Control bar</h3>
<p class="section-intro">
Drag blocks to reorder. Move between Active and Available to show
or hide buttons.
</p>
</div>
<div class="cb-editor">
<div class="cb-zone">
<div class="cb-zone-label">Active</div>
<div
id="controlBarActive"
class="cb-dropzone cb-active-zone"
></div>
</div>
<div class="cb-zone">
<div class="cb-zone-label">Available</div>
<div
id="controlBarAvailable"
class="cb-dropzone cb-available-zone"
></div>
</div>
</div>
</section>
<section id="popupControlBarSettings" class="settings-card">
<div class="section-heading">
<h3>Popup control bar</h3>
<p class="section-intro">
Configure which buttons appear in the browser popup control bar.
</p>
</div>
<div class="row">
<label for="popupMatchHoverControls">Match hover controls</label>
<input id="popupMatchHoverControls" type="checkbox" />
</div>
<div id="popupCbEditorWrap" class="cb-editor cb-editor-disabled">
<div class="cb-zone">
<div class="cb-zone-label">Active</div>
<div
id="popupControlBarActive"
class="cb-dropzone cb-active-zone"
></div>
</div>
<div class="cb-zone">
<div class="cb-zone-label">Available</div>
<div
id="popupControlBarAvailable"
class="cb-dropzone cb-available-zone"
></div>
</div>
</div>
</section>
<section id="siteRules" class="settings-card">
@@ -322,6 +418,54 @@
<label>Controller opacity:</label>
<input type="text" class="site-controllerOpacity" />
</div>
<div class="site-rule-option">
<label>Show popup control bar:</label>
<input type="checkbox" class="site-showPopupControlBar" />
</div>
<div class="site-rule-option">
<label>Enable subtitle nudge:</label>
<input type="checkbox" class="site-enableSubtitleNudge" />
</div>
<div class="site-rule-option">
<label>Nudge interval (10&ndash;1000ms):</label>
<input type="text" class="site-subtitleNudgeInterval" placeholder="50" />
</div>
<div class="site-rule-controlbar">
<label>
<input type="checkbox" class="override-controlbar" />
Custom control bar for this site
</label>
<div class="site-controlbar-container" style="display: none">
<div class="cb-editor">
<div class="cb-zone">
<div class="cb-zone-label">Active</div>
<div class="cb-dropzone cb-active-zone site-cb-active"></div>
</div>
<div class="cb-zone">
<div class="cb-zone-label">Available</div>
<div class="cb-dropzone cb-available-zone site-cb-available"></div>
</div>
</div>
</div>
</div>
<div class="site-rule-controlbar">
<label>
<input type="checkbox" class="override-popup-controlbar" />
Custom popup control bar for this site
</label>
<div class="site-popup-controlbar-container" style="display: none">
<div class="cb-editor">
<div class="cb-zone">
<div class="cb-zone-label">Active</div>
<div class="cb-dropzone cb-active-zone site-popup-cb-active"></div>
</div>
<div class="cb-zone">
<div class="cb-zone-label">Available</div>
<div class="cb-dropzone cb-available-zone site-popup-cb-available"></div>
</div>
</div>
</div>
</div>
<div class="site-rule-shortcuts">
<label>
<input type="checkbox" class="override-shortcuts" />
@@ -334,36 +478,6 @@
</div>
</template>
<section id="nudgeSettings" class="settings-card">
<div class="section-heading">
<h3>Subtitle sync</h3>
<p class="section-intro">Use small speed nudges if subtitles drift.</p>
</div>
<div class="row">
<label for="enableSubtitleNudge"
>Enable subtitle nudge<br /><em
>Makes tiny playback changes to help keep subtitles aligned on
some sites, especially YouTube.</em
>
</label>
<input id="enableSubtitleNudge" type="checkbox" />
</div>
<div class="row">
<label for="subtitleNudgeInterval"
>Nudge interval (milliseconds)<br /><em
>How often to nudge: 10&ndash;1000. Smaller values are more frequent.
Default: 50.</em
>
</label>
<input
id="subtitleNudgeInterval"
type="text"
value=""
placeholder="50"
/>
</div>
</section>
<section class="settings-card action-card">
<div class="section-heading">
<h3>Actions</h3>
+308 -6
View File
@@ -132,6 +132,22 @@ var controllerLocations = [
"middle-left"
];
var controllerButtonDefs = {
rewind: { icon: "\u00AB", name: "Rewind" },
slower: { icon: "\u2212", name: "Decrease speed" },
faster: { icon: "+", name: "Increase speed" },
advance: { icon: "\u00BB", name: "Advance" },
display: { icon: "\u00D7", name: "Close controller" },
reset: { icon: "\u21BA", name: "Reset speed" },
fast: { icon: "\u2605", name: "Preferred speed" },
nudge: { icon: "\u2713", name: "Subtitle nudge" },
settings: { icon: "\u2699", name: "Settings" },
pause: { icon: "\u23EF", name: "Pause / Play" },
muted: { icon: "M", name: "Mute / Unmute" },
mark: { icon: "\u2691", name: "Set marker" },
jump: { icon: "\u21E5", name: "Jump to marker" }
};
function createDefaultBinding(action, key, keyCode, value) {
return {
action: action,
@@ -169,11 +185,16 @@ var tcDefaults = {
createDefaultBinding("toggleSubtitleNudge", "N", 78, 0)
],
siteRules: [
{ pattern: "youtube.com", enabled: true, enableSubtitleNudge: true },
{ pattern: "example1.com", enabled: false },
{ pattern: "/example2\\.com/i", enabled: false },
{ pattern: "/(example3|sample3)\\.com/gi", enabled: false }
],
enableSubtitleNudge: true,
controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
showPopupControlBar: true,
popupMatchHoverControls: true,
popupControllerButtons: ["rewind", "slower", "faster", "advance", "display"],
enableSubtitleNudge: false,
subtitleNudgeInterval: 50,
subtitleNudgeAmount: 0.001
};
@@ -608,6 +629,13 @@ function save_options() {
settings.subtitleNudgeInterval = 1000;
}
settings.controllerButtons = getControlBarOrder();
settings.showPopupControlBar =
document.getElementById("showPopupControlBar").checked;
settings.popupMatchHoverControls =
document.getElementById("popupMatchHoverControls").checked;
settings.popupControllerButtons = getPopupControlBarOrder();
// Collect site rules
settings.siteRules = [];
document.querySelectorAll(".site-rule").forEach((ruleEl) => {
@@ -628,18 +656,46 @@ function save_options() {
{ key: "rememberSpeed", type: "checkbox" },
{ key: "forceLastSavedSpeed", type: "checkbox" },
{ key: "audioBoolean", type: "checkbox" },
{ key: "controllerOpacity", type: "text" }
{ key: "controllerOpacity", type: "text" },
{ key: "showPopupControlBar", type: "checkbox" },
{ key: "enableSubtitleNudge", type: "checkbox" },
{ key: "subtitleNudgeInterval", type: "text" }
];
siteSettings.forEach((s) => {
var input = ruleEl.querySelector(`.site-${s.key}`);
if (!input) return;
var siteValue;
if (s.type === "checkbox") {
rule[s.key] = input.checked;
siteValue = input.checked;
} else {
rule[s.key] = input.value;
siteValue = input.value;
}
var globalInput = document.getElementById(s.key);
if (globalInput) {
var globalValue = s.type === "checkbox" ? globalInput.checked : globalInput.value;
if (String(siteValue) !== String(globalValue)) {
rule[s.key] = siteValue;
}
} else {
rule[s.key] = siteValue;
}
});
if (ruleEl.querySelector(".override-controlbar").checked) {
var activeZone = ruleEl.querySelector(".site-cb-active");
if (activeZone) {
rule.controllerButtons = readControlBarOrder(activeZone);
}
}
if (ruleEl.querySelector(".override-popup-controlbar").checked) {
var popupActiveZone = ruleEl.querySelector(".site-popup-cb-active");
if (popupActiveZone) {
rule.popupControllerButtons = readControlBarOrder(popupActiveZone);
}
}
if (ruleEl.querySelector(".override-shortcuts").checked) {
var shortcuts = [];
ruleEl.querySelectorAll(".site-shortcuts-container .customs").forEach((shortcutRow) => {
@@ -844,7 +900,10 @@ function createSiteRule(rule) {
{ key: "rememberSpeed", type: "checkbox" },
{ key: "forceLastSavedSpeed", type: "checkbox" },
{ key: "audioBoolean", type: "checkbox" },
{ key: "controllerOpacity", type: "text" }
{ key: "controllerOpacity", type: "text" },
{ key: "showPopupControlBar", type: "checkbox" },
{ key: "enableSubtitleNudge", type: "checkbox" },
{ key: "subtitleNudgeInterval", type: "text" }
];
settings.forEach((s) => {
@@ -873,6 +932,28 @@ function createSiteRule(rule) {
}
});
if (rule && Array.isArray(rule.controllerButtons) && rule.controllerButtons.length > 0) {
ruleEl.querySelector(".override-controlbar").checked = true;
var cbContainer = ruleEl.querySelector(".site-controlbar-container");
cbContainer.style.display = "block";
populateControlBarZones(
ruleEl.querySelector(".site-cb-active"),
ruleEl.querySelector(".site-cb-available"),
rule.controllerButtons
);
}
if (rule && Array.isArray(rule.popupControllerButtons) && rule.popupControllerButtons.length > 0) {
ruleEl.querySelector(".override-popup-controlbar").checked = true;
var popupCbContainer = ruleEl.querySelector(".site-popup-controlbar-container");
popupCbContainer.style.display = "block";
populateControlBarZones(
ruleEl.querySelector(".site-popup-cb-active"),
ruleEl.querySelector(".site-popup-cb-available"),
rule.popupControllerButtons
);
}
if (rule && Array.isArray(rule.shortcuts) && rule.shortcuts.length > 0) {
ruleEl.querySelector(".override-shortcuts").checked = true;
var container = ruleEl.querySelector(".site-shortcuts-container");
@@ -898,6 +979,164 @@ function populateDefaultSiteShortcuts(container) {
});
}
function createControlBarBlock(buttonId) {
var def = controllerButtonDefs[buttonId];
if (!def) return null;
var block = document.createElement("div");
block.className = "cb-block";
block.dataset.buttonId = buttonId;
block.draggable = true;
var grip = document.createElement("span");
grip.className = "cb-grip";
var icon = document.createElement("span");
icon.className = "cb-icon";
icon.textContent = def.icon;
var label = document.createElement("span");
label.className = "cb-label";
label.textContent = def.name;
block.appendChild(grip);
block.appendChild(icon);
block.appendChild(label);
return block;
}
function populateControlBarZones(activeZone, availableZone, activeIds) {
activeZone.innerHTML = "";
availableZone.innerHTML = "";
activeIds.forEach(function (id) {
var block = createControlBarBlock(id);
if (block) activeZone.appendChild(block);
});
Object.keys(controllerButtonDefs).forEach(function (id) {
if (!activeIds.includes(id)) {
var block = createControlBarBlock(id);
if (block) availableZone.appendChild(block);
}
});
}
function readControlBarOrder(activeZone) {
var blocks = activeZone.querySelectorAll(".cb-block");
return Array.from(blocks).map(function (block) {
return block.dataset.buttonId;
});
}
function populateControlBarEditor(activeIds) {
populateControlBarZones(
document.getElementById("controlBarActive"),
document.getElementById("controlBarAvailable"),
activeIds
);
}
function getControlBarOrder() {
return readControlBarOrder(document.getElementById("controlBarActive"));
}
function populatePopupControlBarEditor(activeIds) {
populateControlBarZones(
document.getElementById("popupControlBarActive"),
document.getElementById("popupControlBarAvailable"),
activeIds
);
}
function getPopupControlBarOrder() {
return readControlBarOrder(document.getElementById("popupControlBarActive"));
}
function updatePopupEditorDisabledState() {
var checkbox = document.getElementById("popupMatchHoverControls");
var wrap = document.getElementById("popupCbEditorWrap");
if (!checkbox || !wrap) return;
if (checkbox.checked) {
wrap.classList.add("cb-editor-disabled");
} else {
wrap.classList.remove("cb-editor-disabled");
}
}
function getDragAfterElement(container, x, y) {
var elements = Array.from(
container.querySelectorAll(".cb-block:not(.cb-dragging)")
);
for (var i = 0; i < elements.length; i++) {
var box = elements[i].getBoundingClientRect();
var centerX = box.left + box.width / 2;
var centerY = box.top + box.height / 2;
var rowThresh = box.height * 0.5;
if (y - centerY > rowThresh) continue;
if (centerY - y > rowThresh) return elements[i];
if (x < centerX) return elements[i];
}
return undefined;
}
function initControlBarEditor() {
var zones = document.querySelectorAll(".cb-dropzone");
var draggedBlock = null;
document.addEventListener("dragstart", function (e) {
var block = e.target.closest(".cb-block");
if (!block) return;
draggedBlock = block;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", block.dataset.buttonId);
requestAnimationFrame(function () {
block.classList.add("cb-dragging");
});
});
document.addEventListener("dragend", function (e) {
var block = e.target.closest(".cb-block");
if (!block) return;
block.classList.remove("cb-dragging");
draggedBlock = null;
zones.forEach(function (zone) {
zone.classList.remove("cb-over");
});
});
zones.forEach(function (zone) {
zone.addEventListener("dragover", function (e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
zone.classList.add("cb-over");
if (!draggedBlock) return;
var afterEl = getDragAfterElement(zone, e.clientX, e.clientY);
if (afterEl) {
zone.insertBefore(draggedBlock, afterEl);
} else {
zone.appendChild(draggedBlock);
}
});
zone.addEventListener("dragleave", function (e) {
if (zone.contains(e.relatedTarget)) return;
zone.classList.remove("cb-over");
});
zone.addEventListener("drop", function (e) {
e.preventDefault();
zone.classList.remove("cb-over");
});
});
}
function restore_options() {
chrome.storage.sync.get(tcDefaults, function (storage) {
document.getElementById("rememberSpeed").checked = storage.rememberSpeed;
@@ -906,7 +1145,7 @@ function restore_options() {
document.getElementById("audioBoolean").checked = storage.audioBoolean;
document.getElementById("enabled").checked = storage.enabled;
document.getElementById("startHidden").checked = storage.startHidden;
// Migration/Normalization for hideWithControls
const hideWithControls = typeof storage.hideWithControls !== "undefined"
? storage.hideWithControls
@@ -920,6 +1159,8 @@ function restore_options() {
normalizeControllerLocation(storage.controllerLocation);
document.getElementById("controllerOpacity").value =
storage.controllerOpacity;
document.getElementById("showPopupControlBar").checked =
storage.showPopupControlBar !== false;
document.getElementById("enableSubtitleNudge").checked =
storage.enableSubtitleNudge;
document.getElementById("subtitleNudgeInterval").value =
@@ -981,6 +1222,24 @@ function restore_options() {
}
});
}
var controllerButtons =
Array.isArray(storage.controllerButtons) &&
storage.controllerButtons.length > 0
? storage.controllerButtons
: tcDefaults.controllerButtons;
populateControlBarEditor(controllerButtons);
document.getElementById("popupMatchHoverControls").checked =
storage.popupMatchHoverControls !== false;
var popupButtons =
Array.isArray(storage.popupControllerButtons) &&
storage.popupControllerButtons.length > 0
? storage.popupControllerButtons
: tcDefaults.popupControllerButtons;
populatePopupControlBarEditor(popupButtons);
updatePopupEditorDisabledState();
});
}
@@ -1005,6 +1264,11 @@ document.addEventListener("DOMContentLoaded", function () {
}
restore_options();
initControlBarEditor();
document.getElementById("popupMatchHoverControls")
.addEventListener("change", updatePopupEditorDisabledState);
document.getElementById("save").addEventListener("click", save_options);
const addSelector = document.getElementById("addShortcutSelector");
@@ -1096,5 +1360,43 @@ document.addEventListener("DOMContentLoaded", function () {
container.style.display = "none";
}
}
if (event.target.classList.contains("override-controlbar")) {
var cbContainer = event.target
.closest(".site-rule-controlbar")
.querySelector(".site-controlbar-container");
if (event.target.checked) {
cbContainer.style.display = "block";
var activeZone = cbContainer.querySelector(".site-cb-active");
if (activeZone && activeZone.children.length === 0) {
populateControlBarZones(
activeZone,
cbContainer.querySelector(".site-cb-available"),
getControlBarOrder()
);
}
} else {
cbContainer.style.display = "none";
}
}
if (event.target.classList.contains("override-popup-controlbar")) {
var popupCbContainer = event.target
.closest(".site-rule-controlbar")
.querySelector(".site-popup-controlbar-container");
if (event.target.checked) {
popupCbContainer.style.display = "block";
var popupActiveZone = popupCbContainer.querySelector(".site-popup-cb-active");
if (popupActiveZone && popupActiveZone.children.length === 0) {
populateControlBarZones(
popupActiveZone,
popupCbContainer.querySelector(".site-popup-cb-available"),
getPopupControlBarOrder()
);
}
} else {
popupCbContainer.style.display = "none";
}
}
});
});
+229 -55
View File
@@ -1,79 +1,253 @@
body {
min-width: 8em;
background-color: white;
color: #333;
}
.version {
margin-top: 0.7em;
font-size: 0.85em;
text-align: center;
color: #666;
}
:root {
--bg: #f4f5f7;
--panel: #ffffff;
--border: #e2e5e9;
--border-strong: #d4d9e0;
--text: #17191c;
--muted: #626b76;
--accent: #111827;
}
hr {
width: 100%;
border: 0;
height: 0;
border-top: 1px solid rgba(0, 0, 0, 0.3);
margin: 0.6em 0;
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-width: 220px;
background: var(--bg);
color: var(--text);
font: 13px/1.45 "Avenir Next", "SF Pro Text", "Segoe UI", sans-serif;
padding: 12px;
}
.popup-shell {
display: flex;
flex-direction: column;
gap: 10px;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.popup-title {
font-weight: 600;
font-size: 15px;
font-family: "Avenir Next", "SF Pro Display", "Segoe UI", sans-serif;
letter-spacing: -0.01em;
}
.popup-version {
font-size: 11px;
font-weight: 600;
color: var(--muted);
padding: 2px 7px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--panel);
}
.popup-actions {
display: flex;
flex-direction: column;
gap: 6px;
}
button {
appearance: none;
width: 100%;
background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
border: 1px solid rgba(0, 0, 0, 0.25);
border-radius: 2px;
outline: none;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
inset 0 1px 2px rgba(255, 255, 255, 0.75);
color: #444;
text-shadow: 0 1px 0 rgb(240, 240, 240);
min-height: 32px;
padding: 0 12px;
border: 1px solid var(--border-strong);
border-radius: 8px;
background: var(--panel);
color: var(--text);
font: inherit;
font-weight: 500;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease;
}
button:hover {
background: #f8f9fb;
border-color: #c5ccd5;
}
button:active {
background: #f1f3f5;
}
button:focus-visible {
outline: 2px solid rgba(17, 24, 39, 0.14);
outline-offset: 2px;
}
#refresh {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
#refresh:hover {
background: #1f2937;
border-color: #1f2937;
}
.popup-divider {
height: 1px;
background: var(--border);
margin: 2px 0;
}
.popup-control-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 6px 10px;
background: var(--panel);
border: 1px solid var(--border-strong);
border-radius: 8px;
}
.popup-speed {
font-family: "Lucida Console", Monaco, monospace;
font-size: 13px;
font-weight: bold;
color: var(--text);
margin-right: 4px;
line-height: 1;
user-select: none;
white-space: nowrap;
}
.popup-control-bar button {
width: auto;
min-height: 24px;
border: 1px solid var(--border-strong);
border-radius: 6px;
background: var(--bg);
color: var(--text);
font-family: "Lucida Console", Monaco, monospace;
font-size: 13px;
line-height: 13px;
font-weight: bold;
padding: 3px 7px;
cursor: pointer;
}
.popup-control-bar button:hover {
background: var(--panel);
border-color: var(--border-strong);
}
.popup-control-bar button:active {
background: var(--bg);
}
.popup-control-bar button.rw {
opacity: 0.55;
}
.popup-control-bar button.hideButton {
opacity: 0.55;
}
.popup-status {
font-size: 12px;
color: var(--muted);
font-weight: 500;
text-align: center;
padding: 2px 0;
}
.popup-links {
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 8px;
border-top: 1px solid var(--border);
}
.popup-secondary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.secondary {
font-size: 0.95em;
margin: 0.15em 0;
font-size: 12px;
min-height: 28px;
color: var(--muted);
}
.donate-wrap {
display: grid;
grid-template-columns: 1fr;
}
.donate-split {
display: grid;
grid-template-columns: 1fr 1fr;
}
.donate-split button {
width: auto;
border-radius: 0;
min-height: 28px;
}
.donate-split button:first-child {
border-radius: 8px 0 0 8px;
border-right: 0;
}
.donate-split button:last-child {
border-radius: 0 8px 8px 0;
}
.hide {
display: none;
display: none !important;
}
/* Dark mode styles */
@media (prefers-color-scheme: dark) {
:root {
--bg: #111315;
--panel: #171a1d;
--border: #2b3138;
--border-strong: #3a414a;
--text: #f2f4f6;
--muted: #a0a8b2;
--accent: #f2f4f6;
}
body {
background-color: #1a1a1a;
color: #e0e0e0;
}
hr {
border-top: 1px solid rgba(255, 255, 255, 0.3);
}
button {
background-image: linear-gradient(#404040, #404040 38%, #353535);
border: 1px solid rgba(255, 255, 255, 0.25);
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08),
inset 0 1px 2px rgba(0, 0, 0, 0.75);
color: #e0e0e0;
text-shadow: 0 1px 0 rgb(20, 20, 20);
color-scheme: dark;
}
button:hover {
background-image: linear-gradient(#4a4a4a, #4a4a4a 38%, #3f3f3f);
background: #1f2226;
border-color: #4a515a;
}
button:active {
background-image: linear-gradient(#353535, #353535 38%, #2a2a2a);
background: #252a2f;
}
#status {
color: #ccc;
}
.version {
color: #aaa;
}
}
#refresh {
background: #f2f4f6;
border-color: #f2f4f6;
color: #111315;
}
#refresh:hover {
background: #dfe3e8;
border-color: #dfe3e8;
}
}
+33 -12
View File
@@ -1,21 +1,42 @@
<!doctype html>
<html>
<head>
<title>Video Speed Controller: Popup</title>
<meta charset="utf-8" />
<title>Speeder</title>
<link rel="stylesheet" href="popup.css" />
<script src="popup.js"></script>
</head>
<body>
<button id="refresh">Re-scan Page for Videos</button>
<hr />
<button id="enable" class="hide">Enable</button>
<button id="disable">Disable</button>
<span id="status" class="hide"></span>
<hr />
<button id="config">Settings</button>
<hr />
<button id="feedback" class="secondary">Send feedback</button>
<button id="about" class="secondary">About</button>
<div class="version">Version <span id="app-version"></span></div>
<div class="popup-shell">
<div class="popup-header">
<span class="popup-title">Speeder</span>
<span class="popup-version">v<span id="app-version"></span></span>
</div>
<div class="popup-actions">
<button id="refresh">Rescan page for videos</button>
<div class="popup-divider"></div>
<div id="popupControlBar" class="popup-control-bar">
<span id="popupSpeed" class="popup-speed">1.00</span>
</div>
<div class="popup-divider"></div>
<button id="enable" class="hide">Enable</button>
<button id="disable">Disable</button>
</div>
<div id="status" class="popup-status hide"></div>
<div class="popup-links">
<button id="config">Settings</button>
<div class="popup-secondary">
<button id="feedback" class="secondary">Feedback</button>
<button id="about" class="secondary">About</button>
</div>
<div id="donateWrap" class="donate-wrap">
<button id="donate" class="secondary">Donate</button>
<div id="donateOptions" class="donate-split hide">
<button id="donateKofi" class="secondary">Ko-fi</button>
<button id="donateGithub" class="secondary">Sponsors</button>
</div>
</div>
</div>
</div>
</body>
</html>
+167 -29
View File
@@ -1,6 +1,23 @@
document.addEventListener("DOMContentLoaded", function () {
var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
var controllerButtonDefs = {
rewind: { label: "\u00AB", className: "rw" },
slower: { label: "\u2212", className: "" },
faster: { label: "+", className: "" },
advance: { label: "\u00BB", className: "rw" },
display: { label: "\u00D7", className: "hideButton" },
reset: { label: "\u21BA", className: "" },
fast: { label: "\u2605", className: "" },
settings: { label: "\u2699", className: "" },
pause: { label: "\u23EF", className: "" },
muted: { label: "M", className: "" },
mark: { label: "\u2691", className: "" },
jump: { label: "\u21E5", className: "" }
};
var defaultButtons = ["rewind", "slower", "faster", "advance", "display"];
function escapeStringRegExp(str) {
const m = /[|\\{}()[\]^$+*?.]/g;
return str.replace(m, "\\$&");
@@ -27,6 +44,102 @@ document.addEventListener("DOMContentLoaded", function () {
return b;
}
function matchSiteRule(url, siteRules) {
if (!url || !Array.isArray(siteRules)) return null;
for (var i = 0; i < siteRules.length; i++) {
var rule = siteRules[i];
if (!rule || !rule.pattern) continue;
var pattern = rule.pattern.replace(regStrip, "");
if (pattern.length === 0) continue;
var re;
if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
try {
var ls = pattern.lastIndexOf("/");
re = new RegExp(pattern.substring(1, ls), pattern.substring(ls + 1));
} catch (e) {
continue;
}
} else {
re = new RegExp(escapeStringRegExp(pattern));
}
if (re && re.test(url)) return rule;
}
return null;
}
function setControlBarVisible(visible) {
var bar = document.getElementById("popupControlBar");
var dividers = document.querySelectorAll(".popup-divider");
if (bar) bar.style.display = visible ? "" : "none";
dividers.forEach(function (d) { d.style.display = visible ? "" : "none"; });
}
function sendToActiveTab(message, callback) {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (tabs[0] && tabs[0].id) {
chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
if (chrome.runtime.lastError) {
if (callback) callback(null);
} else {
if (callback) callback(response);
}
});
} else {
if (callback) callback(null);
}
});
}
function updateSpeedDisplay(speed) {
var el = document.getElementById("popupSpeed");
if (el) el.textContent = (speed != null ? Number(speed) : 1).toFixed(2);
}
function querySpeed() {
sendToActiveTab({ action: "get_speed" }, function (response) {
if (response && response.speed != null) {
updateSpeedDisplay(response.speed);
}
});
}
function buildControlBar(buttons) {
var bar = document.getElementById("popupControlBar");
if (!bar) return;
var existing = bar.querySelectorAll("button");
existing.forEach(function (btn) { btn.remove(); });
buttons.forEach(function (btnId) {
if (btnId === "nudge") return;
var def = controllerButtonDefs[btnId];
if (!def) return;
var btn = document.createElement("button");
btn.dataset.action = btnId;
btn.textContent = def.label;
if (def.className) btn.className = def.className;
btn.title = btnId.charAt(0).toUpperCase() + btnId.slice(1);
btn.addEventListener("click", function () {
if (btnId === "settings") {
window.open(chrome.runtime.getURL("options.html"));
return;
}
sendToActiveTab(
{ action: "run_action", actionName: btnId },
function (response) {
if (response && response.speed != null) {
updateSpeedDisplay(response.speed);
}
}
);
});
bar.appendChild(btn);
});
}
var manifest = chrome.runtime.getManifest();
var versionElement = document.querySelector("#app-version");
if (versionElement) {
@@ -45,6 +158,19 @@ document.addEventListener("DOMContentLoaded", function () {
window.open("https://github.com/SoPat712/Speeder/issues");
});
document.querySelector("#donate").addEventListener("click", function () {
this.classList.add("hide");
document.querySelector("#donateOptions").classList.remove("hide");
});
document.querySelector("#donateKofi").addEventListener("click", function () {
window.open("https://ko-fi.com/joshpatra");
});
document.querySelector("#donateGithub").addEventListener("click", function () {
window.open("https://github.com/sponsors/SoPat712");
});
document.querySelector("#enable").addEventListener("click", function () {
toggleEnabled(true, settingsSavedReloadMessage);
});
@@ -53,27 +179,16 @@ document.addEventListener("DOMContentLoaded", function () {
toggleEnabled(false, settingsSavedReloadMessage);
});
// --- REVISED: "Re-scan" button functionality ---
document.querySelector("#refresh").addEventListener("click", function () {
setStatusMessage("Re-scanning page...");
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (tabs[0] && tabs[0].id) {
// Send a message to the content script, asking it to re-initialize.
chrome.tabs.sendMessage(
tabs[0].id,
{ action: "rescan_page" },
function (response) {
if (chrome.runtime.lastError) {
// This error is expected on pages where content scripts cannot run.
setStatusMessage("Cannot run on this page.");
} else if (response && response.status === "complete") {
setStatusMessage("Scan complete. Closing...");
setTimeout(() => window.close(), 500); // Close popup on success.
} else {
setStatusMessage("Scan failed. Please reload the page.");
}
}
);
setStatusMessage("Rescanning page...");
sendToActiveTab({ action: "rescan_page" }, function (response) {
if (!response) {
setStatusMessage("Cannot run on this page.");
} else if (response.status === "complete") {
setStatusMessage("Scan complete. Closing...");
setTimeout(function () { window.close(); }, 500);
} else {
setStatusMessage("Scan failed. Please reload the page.");
}
});
});
@@ -81,6 +196,11 @@ document.addEventListener("DOMContentLoaded", function () {
chrome.storage.sync.get(
{
enabled: true,
showPopupControlBar: true,
controllerButtons: defaultButtons,
popupMatchHoverControls: true,
popupControllerButtons: defaultButtons,
siteRules: [],
blacklist: `\
www.instagram.com
twitter.com
@@ -97,20 +217,38 @@ document.addEventListener("DOMContentLoaded", function () {
if (blacklisted) {
setStatusMessage("Site is blacklisted.");
}
var siteRule = matchSiteRule(url, storage.siteRules);
var buttons = storage.popupMatchHoverControls
? storage.controllerButtons
: storage.popupControllerButtons;
if (siteRule && Array.isArray(siteRule.popupControllerButtons) && siteRule.popupControllerButtons.length > 0) {
buttons = siteRule.popupControllerButtons;
}
if (!Array.isArray(buttons) || buttons.length === 0) {
buttons = defaultButtons;
}
buildControlBar(buttons);
querySpeed();
var showBar = storage.showPopupControlBar !== false;
if (siteRule && siteRule.showPopupControlBar !== undefined) {
showBar = siteRule.showPopupControlBar;
}
setControlBarVisible(showBar);
});
}
);
function toggleEnabled(enabled, callback) {
chrome.storage.sync.set(
{
enabled: enabled
},
function () {
toggleEnabledUI(enabled);
if (callback) callback(enabled);
}
);
chrome.storage.sync.set({ enabled: enabled }, function () {
toggleEnabledUI(enabled);
if (callback) callback(enabled);
});
}
function toggleEnabledUI(enabled) {
+24 -29
View File
@@ -61,15 +61,13 @@
vertical-align: middle;
align-items: center;
justify-content: center;
min-width: 1.35em;
height: 1.35em;
margin-left: 0.3em;
padding: 0 0.25em;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.28);
font-size: 12px;
padding: 3px 6px;
border-radius: 5px;
font-size: 14px;
line-height: 14px;
font-weight: bold;
line-height: 1;
font-family: "Lucida Console", Monaco, monospace;
box-sizing: border-box;
}
@@ -83,49 +81,46 @@
}
#nudge-flash-indicator[data-enabled="true"] {
color: #bff3a2;
background: rgba(75, 145, 53, 0.28);
border-color: rgba(126, 199, 104, 0.7);
color: #fff;
background: #4b9135;
border: 1px solid #6ec754;
}
#nudge-flash-indicator[data-enabled="false"] {
color: #ffb8b8;
background: rgba(164, 73, 73, 0.24);
border-color: rgba(214, 118, 118, 0.65);
color: #fff;
background: #943e3e;
border: 1px solid #c06060;
}
#nudge-indicator {
display: inline-flex;
vertical-align: middle;
align-items: center;
justify-content: center;
min-width: 1.35em;
height: 1.35em;
margin-left: 0.45em;
padding: 0 0.25em;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.28);
font-size: 12px;
padding: 3px 6px;
border-radius: 5px;
font-size: 14px;
line-height: 14px;
font-weight: bold;
line-height: 1;
font-family: "Lucida Console", Monaco, monospace;
box-sizing: border-box;
cursor: pointer;
margin-bottom: 2px;
}
#nudge-indicator[data-enabled="true"] {
color: #bff3a2;
background: rgba(75, 145, 53, 0.28);
border-color: rgba(126, 199, 104, 0.7);
color: #fff;
background: #4b9135;
border: 1px solid #6ec754;
}
#nudge-indicator[data-enabled="false"] {
color: #ffb8b8;
background: rgba(164, 73, 73, 0.24);
border-color: rgba(214, 118, 118, 0.65);
color: #fff;
background: #943e3e;
border: 1px solid #c06060;
}
#nudge-indicator[data-supported="false"] {
opacity: 0.75;
opacity: 0.6;
}
#controller.dragging {