mirror of
https://github.com/SoPat712/videospeed.git
synced 2026-04-26 22:23:09 -04:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1536c13c3e
|
|||
|
6bd319c8cc
|
|||
|
3aee8c8f9a
|
|||
|
939ee08466
|
|||
|
5a175c3cf8
|
|||
|
805e5a82e5
|
|||
|
df34b1fee9
|
|||
|
0741c6e535
|
|||
|
fad0c49e65
|
|||
|
66075fb6f3
|
|||
|
bf4025dcb4
|
|||
|
76a7b933bb
|
|||
|
1cd533fc5c
|
|||
|
8c5bd68d39
|
|||
|
9c257af446
|
|||
|
64a9b85587
|
@@ -10,6 +10,8 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
WEB_EXT_IGNORE_FILES: scripts/**
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ var controllerButtonDefs = {
|
|||||||
faster: { label: "", className: "" },
|
faster: { label: "", className: "" },
|
||||||
advance: { label: "", className: "rw" },
|
advance: { label: "", className: "rw" },
|
||||||
display: { label: "", className: "hideButton" },
|
display: { label: "", className: "hideButton" },
|
||||||
reset: { label: "", className: "" },
|
reset: { label: "\u21BB", className: "" },
|
||||||
fast: { label: "", className: "" },
|
fast: { label: "", className: "" },
|
||||||
settings: { label: "", className: "" },
|
settings: { label: "", className: "" },
|
||||||
pause: { label: "", className: "" },
|
pause: { label: "", className: "" },
|
||||||
@@ -776,18 +776,40 @@ function setSubtitleNudgeEnabledForVideo(video, enabled) {
|
|||||||
return normalizedEnabled;
|
return normalizedEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
function subtitleNudgeIconMarkup(isEnabled) {
|
function renderSubtitleNudgeIndicatorContent(target, isEnabled) {
|
||||||
|
if (!target) return;
|
||||||
var action = isEnabled ? "subtitleNudgeOn" : "subtitleNudgeOff";
|
var action = isEnabled ? "subtitleNudgeOn" : "subtitleNudgeOff";
|
||||||
|
var custom =
|
||||||
|
tc.settings.customButtonIcons &&
|
||||||
|
tc.settings.customButtonIcons[action] &&
|
||||||
|
tc.settings.customButtonIcons[action].svg;
|
||||||
|
vscClearElement(target);
|
||||||
|
if (custom) {
|
||||||
|
var customWrap = vscCreateSvgWrap(
|
||||||
|
target.ownerDocument || document,
|
||||||
|
custom,
|
||||||
|
"vsc-btn-icon"
|
||||||
|
);
|
||||||
|
if (customWrap) {
|
||||||
|
target.appendChild(customWrap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (typeof vscIconSvgString !== "function") {
|
if (typeof vscIconSvgString !== "function") {
|
||||||
return isEnabled ? "✓" : "×";
|
target.textContent = isEnabled ? "✓" : "×";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var svg = vscIconSvgString(action, 14);
|
var svg = vscIconSvgString(action, 14);
|
||||||
if (!svg) {
|
if (!svg) {
|
||||||
return isEnabled ? "✓" : "×";
|
target.textContent = isEnabled ? "✓" : "×";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return (
|
var wrap = vscCreateSvgWrap(target.ownerDocument || document, svg, "vsc-btn-icon");
|
||||||
'<span class="vsc-btn-icon" aria-hidden="true">' + svg + "</span>"
|
if (wrap) {
|
||||||
);
|
target.appendChild(wrap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target.textContent = isEnabled ? "✓" : "×";
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSubtitleNudgeIndicator(video) {
|
function updateSubtitleNudgeIndicator(video) {
|
||||||
@@ -795,11 +817,10 @@ function updateSubtitleNudgeIndicator(video) {
|
|||||||
|
|
||||||
var isEnabled = isSubtitleNudgeEnabledForVideo(video);
|
var isEnabled = isSubtitleNudgeEnabledForVideo(video);
|
||||||
var title = isEnabled ? "Subtitle nudge enabled" : "Subtitle nudge disabled";
|
var title = isEnabled ? "Subtitle nudge enabled" : "Subtitle nudge disabled";
|
||||||
var mark = subtitleNudgeIconMarkup(isEnabled);
|
|
||||||
|
|
||||||
var indicator = video.vsc.subtitleNudgeIndicator;
|
var indicator = video.vsc.subtitleNudgeIndicator;
|
||||||
if (indicator) {
|
if (indicator) {
|
||||||
indicator.innerHTML = mark;
|
renderSubtitleNudgeIndicatorContent(indicator, isEnabled);
|
||||||
indicator.dataset.enabled = isEnabled ? "true" : "false";
|
indicator.dataset.enabled = isEnabled ? "true" : "false";
|
||||||
indicator.dataset.supported = "true";
|
indicator.dataset.supported = "true";
|
||||||
indicator.title = title;
|
indicator.title = title;
|
||||||
@@ -808,7 +829,7 @@ function updateSubtitleNudgeIndicator(video) {
|
|||||||
|
|
||||||
var flashEl = video.vsc.nudgeFlashIndicator;
|
var flashEl = video.vsc.nudgeFlashIndicator;
|
||||||
if (flashEl) {
|
if (flashEl) {
|
||||||
flashEl.innerHTML = mark;
|
renderSubtitleNudgeIndicatorContent(flashEl, isEnabled);
|
||||||
flashEl.dataset.enabled = isEnabled ? "true" : "false";
|
flashEl.dataset.enabled = isEnabled ? "true" : "false";
|
||||||
flashEl.dataset.supported = "true";
|
flashEl.dataset.supported = "true";
|
||||||
flashEl.setAttribute("aria-label", title);
|
flashEl.setAttribute("aria-label", title);
|
||||||
@@ -1348,12 +1369,15 @@ chrome.storage.sync.get(tc.settings, function (storage) {
|
|||||||
tc.settings.customButtonIcons &&
|
tc.settings.customButtonIcons &&
|
||||||
tc.settings.customButtonIcons[act] &&
|
tc.settings.customButtonIcons[act] &&
|
||||||
tc.settings.customButtonIcons[act].svg;
|
tc.settings.customButtonIcons[act].svg;
|
||||||
btn.innerHTML = "";
|
vscClearElement(btn);
|
||||||
if (svg) {
|
if (svg) {
|
||||||
var cw = doc.createElement("span");
|
var cw = vscCreateSvgWrap(doc, svg, "vsc-btn-icon");
|
||||||
cw.className = "vsc-btn-icon";
|
if (cw) {
|
||||||
cw.innerHTML = svg;
|
btn.appendChild(cw);
|
||||||
btn.appendChild(cw);
|
} else {
|
||||||
|
var cdf = controllerButtonDefs[act];
|
||||||
|
btn.textContent = (cdf && cdf.label) || "?";
|
||||||
|
}
|
||||||
} else if (typeof vscIconWrap === "function") {
|
} else if (typeof vscIconWrap === "function") {
|
||||||
var wrap = vscIconWrap(doc, act, 14);
|
var wrap = vscIconWrap(doc, act, 14);
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
@@ -1367,6 +1391,7 @@ chrome.storage.sync.get(tc.settings, function (storage) {
|
|||||||
btn.textContent = (cdf2 && cdf2.label) || "?";
|
btn.textContent = (cdf2 && cdf2.label) || "?";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
updateSubtitleNudgeIndicator(video);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1395,10 +1420,12 @@ function createControllerButton(doc, action, label, className) {
|
|||||||
tc.settings.customButtonIcons[action] &&
|
tc.settings.customButtonIcons[action] &&
|
||||||
tc.settings.customButtonIcons[action].svg;
|
tc.settings.customButtonIcons[action].svg;
|
||||||
if (custom) {
|
if (custom) {
|
||||||
var customWrap = doc.createElement("span");
|
var customWrap = vscCreateSvgWrap(doc, custom, "vsc-btn-icon");
|
||||||
customWrap.className = "vsc-btn-icon";
|
if (customWrap) {
|
||||||
customWrap.innerHTML = custom;
|
button.appendChild(customWrap);
|
||||||
button.appendChild(customWrap);
|
} else {
|
||||||
|
button.textContent = label || "?";
|
||||||
|
}
|
||||||
} else if (typeof vscIconWrap === "function") {
|
} else if (typeof vscIconWrap === "function") {
|
||||||
var wrap = vscIconWrap(doc, action, 14);
|
var wrap = vscIconWrap(doc, action, 14);
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
@@ -1939,14 +1966,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 +1988,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 +2005,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 +2699,7 @@ function runAction(action, value, e) {
|
|||||||
"mark",
|
"mark",
|
||||||
"jump",
|
"jump",
|
||||||
"drag",
|
"drag",
|
||||||
|
"nudge",
|
||||||
"toggleSubtitleNudge",
|
"toggleSubtitleNudge",
|
||||||
"display"
|
"display"
|
||||||
];
|
];
|
||||||
@@ -2782,6 +2815,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
-25
@@ -31,32 +31,9 @@ function sanitizeLucideSvg(svgText) {
|
|||||||
var t = String(svgText).replace(/\0/g, "").trim();
|
var t = String(svgText).replace(/\0/g, "").trim();
|
||||||
if (!/<svg[\s>]/i.test(t)) return null;
|
if (!/<svg[\s>]/i.test(t)) return null;
|
||||||
var doc = new DOMParser().parseFromString(t, "image/svg+xml");
|
var doc = new DOMParser().parseFromString(t, "image/svg+xml");
|
||||||
var svg = doc.querySelector("svg");
|
if (doc.querySelector("parsererror")) return null;
|
||||||
|
var svg = vscSanitizeSvgTree(doc.querySelector("svg"));
|
||||||
if (!svg) return null;
|
if (!svg) return null;
|
||||||
svg.querySelectorAll("script").forEach(function (n) {
|
|
||||||
n.remove();
|
|
||||||
});
|
|
||||||
svg.querySelectorAll("style").forEach(function (n) {
|
|
||||||
n.remove();
|
|
||||||
});
|
|
||||||
svg.querySelectorAll("*").forEach(function (el) {
|
|
||||||
for (var i = el.attributes.length - 1; i >= 0; i--) {
|
|
||||||
var attr = el.attributes[i];
|
|
||||||
var name = attr.name.toLowerCase();
|
|
||||||
var val = attr.value;
|
|
||||||
if (name.indexOf("on") === 0) {
|
|
||||||
el.removeAttribute(attr.name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(name === "href" || name === "xlink:href") &&
|
|
||||||
/^javascript:/i.test(val)
|
|
||||||
) {
|
|
||||||
el.removeAttribute(attr.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
||||||
svg.removeAttribute("width");
|
svg.removeAttribute("width");
|
||||||
svg.removeAttribute("height");
|
svg.removeAttribute("height");
|
||||||
svg.setAttribute("width", "100%");
|
svg.setAttribute("width", "100%");
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Speeder",
|
"name": "Speeder",
|
||||||
"short_name": "Speeder",
|
"short_name": "Speeder",
|
||||||
"version": "5.1.1",
|
"version": "5.1.6",
|
||||||
"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",
|
||||||
|
|||||||
+45
@@ -545,6 +545,51 @@ label em {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cb-icon.cb-icon-nudge-pair {
|
||||||
|
width: auto;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 4px;
|
||||||
|
gap: 4px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cb-nudge-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cb-nudge-chip[data-nudge-state="on"] {
|
||||||
|
background: #4b9135;
|
||||||
|
border: 1px solid #6ec754;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cb-nudge-chip[data-nudge-state="off"] {
|
||||||
|
background: #943e3e;
|
||||||
|
border: 1px solid #c06060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cb-nudge-chip .vsc-btn-icon svg,
|
||||||
|
.cb-nudge-chip svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cb-nudge-sep {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0.45;
|
||||||
|
color: var(--text);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.row-lucide-pair select {
|
.row-lucide-pair select {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-20
@@ -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>
|
||||||
@@ -383,9 +382,11 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>Lucide</a
|
>Lucide</a
|
||||||
>
|
>
|
||||||
set (fetched from jsDelivr). Chosen SVGs are cached in local
|
set (fetched from jsDelivr). Custom icons are cached in local
|
||||||
storage and included in settings export.
|
storage and included when you export settings. Subtitle nudge
|
||||||
<strong>Reset speed</strong> stays numeric text only.
|
icons use two menu entries (enabled and disabled), not the bar
|
||||||
|
block id
|
||||||
|
<code>nudge</code>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row row-lucide-pair">
|
<div class="row row-lucide-pair">
|
||||||
@@ -677,6 +678,17 @@
|
|||||||
<div id="status" role="status" aria-live="polite"></div>
|
<div id="status" role="status" aria-live="polite"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="faq" class="settings-card info-card">
|
||||||
|
<h4>Extension controls not appearing?</h4>
|
||||||
|
<p>
|
||||||
|
This extension only works with HTML5 audio and video. If the
|
||||||
|
controls never appear, you may be looking at Flash content instead.
|
||||||
|
Right-click the player to check: if the menu mentions Flash, that
|
||||||
|
is the issue. Most sites will fall back to HTML5 when Flash is not
|
||||||
|
available, so disabling Flash in the browser can help.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<footer class="support-footer settings-card">
|
<footer class="support-footer settings-card">
|
||||||
<p>
|
<p>
|
||||||
If Speeder has been useful, consider supporting its development via
|
If Speeder has been useful, consider supporting its development via
|
||||||
@@ -695,17 +707,6 @@
|
|||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<section id="faq" class="settings-card info-card">
|
|
||||||
<h4>Extension controls not appearing?</h4>
|
|
||||||
<p>
|
|
||||||
This extension only works with HTML5 audio and video. If the
|
|
||||||
controls never appear, you may be looking at Flash content instead.
|
|
||||||
Right-click the player to check: if the menu mentions Flash, that
|
|
||||||
is the issue. Most sites will fall back to HTML5 when Flash is not
|
|
||||||
available, so disabling Flash in the browser can help.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+109
-22
@@ -138,7 +138,7 @@ var controllerButtonDefs = {
|
|||||||
faster: { icon: "+", name: "Increase speed" },
|
faster: { icon: "+", name: "Increase speed" },
|
||||||
advance: { icon: "\u00BB", name: "Advance" },
|
advance: { icon: "\u00BB", name: "Advance" },
|
||||||
display: { icon: "\u00D7", name: "Close controller" },
|
display: { icon: "\u00D7", name: "Close controller" },
|
||||||
reset: { icon: "", name: "Reset speed" },
|
reset: { icon: "\u21BB", name: "Reset speed" },
|
||||||
fast: { icon: "\u2605", name: "Preferred speed" },
|
fast: { icon: "\u2605", name: "Preferred speed" },
|
||||||
nudge: { icon: "\u2713", name: "Subtitle nudge" },
|
nudge: { icon: "\u2713", name: "Subtitle nudge" },
|
||||||
settings: { icon: "\u2699", name: "Settings" },
|
settings: { icon: "\u2699", name: "Settings" },
|
||||||
@@ -147,24 +147,76 @@ 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"]);
|
||||||
|
|
||||||
|
/** Lucide picker only — not control-bar blocks (chip uses subtitleNudgeOn/Off). */
|
||||||
|
var lucideSubtitleNudgeActionLabels = {
|
||||||
|
subtitleNudgeOn: "Subtitle nudge — enabled",
|
||||||
|
subtitleNudgeOff: "Subtitle nudge — disabled"
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = {};
|
||||||
|
|
||||||
function fillControlBarIconElement(icon, buttonId) {
|
function fillControlBarIconElement(icon, buttonId) {
|
||||||
if (!icon || !buttonId) return;
|
if (!icon || !buttonId) return;
|
||||||
|
var doc = icon.ownerDocument || document;
|
||||||
|
if (buttonId === "nudge") {
|
||||||
|
vscClearElement(icon);
|
||||||
|
icon.className = "cb-icon cb-icon-nudge-pair";
|
||||||
|
function nudgeChipMarkup(action) {
|
||||||
|
var c = customButtonIconsLive[action];
|
||||||
|
if (c && c.svg) return c.svg;
|
||||||
|
if (typeof vscIconSvgString === "function") {
|
||||||
|
return vscIconSvgString(action, 14) || "";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
function appendChip(action, stateKey) {
|
||||||
|
var sp = document.createElement("span");
|
||||||
|
sp.className = "cb-nudge-chip";
|
||||||
|
sp.setAttribute("data-nudge-state", stateKey);
|
||||||
|
var inner = nudgeChipMarkup(action);
|
||||||
|
if (inner) {
|
||||||
|
var wrap = vscCreateSvgWrap(doc, inner, "vsc-btn-icon");
|
||||||
|
if (wrap) {
|
||||||
|
sp.appendChild(wrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
icon.appendChild(sp);
|
||||||
|
}
|
||||||
|
appendChip("subtitleNudgeOn", "on");
|
||||||
|
var sep = document.createElement("span");
|
||||||
|
sep.className = "cb-nudge-sep";
|
||||||
|
sep.textContent = "/";
|
||||||
|
icon.appendChild(sep);
|
||||||
|
appendChip("subtitleNudgeOff", "off");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
icon.className = "cb-icon";
|
||||||
var custom = customButtonIconsLive[buttonId];
|
var custom = customButtonIconsLive[buttonId];
|
||||||
if (custom && custom.svg) {
|
if (custom && custom.svg) {
|
||||||
icon.innerHTML = custom.svg;
|
if (vscSetSvgContent(icon, custom.svg)) return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (typeof vscIconSvgString === "function") {
|
if (typeof vscIconSvgString === "function") {
|
||||||
var svgHtml = vscIconSvgString(buttonId, 16);
|
var svgHtml = vscIconSvgString(buttonId, 16);
|
||||||
if (svgHtml) {
|
if (svgHtml) {
|
||||||
icon.innerHTML = svgHtml;
|
if (vscSetSvgContent(icon, svgHtml)) return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vscClearElement(icon);
|
||||||
var def = controllerButtonDefs[buttonId];
|
var def = controllerButtonDefs[buttonId];
|
||||||
icon.textContent = (def && def.icon) || "?";
|
icon.textContent = (def && def.icon) || "?";
|
||||||
}
|
}
|
||||||
@@ -713,7 +765,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 +854,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 +1125,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 +1138,10 @@ function createSiteRule(rule) {
|
|||||||
populateControlBarZones(
|
populateControlBarZones(
|
||||||
sitePopupActive,
|
sitePopupActive,
|
||||||
sitePopupAvailable,
|
sitePopupAvailable,
|
||||||
getPopupControlBarOrder()
|
getPopupControlBarOrder(),
|
||||||
|
function (id) {
|
||||||
|
return !popupExcludedButtonIds.has(id);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1139,16 +1199,23 @@ function createControlBarBlock(buttonId) {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateControlBarZones(activeZone, availableZone, activeIds) {
|
function populateControlBarZones(activeZone, availableZone, activeIds, allowButtonId) {
|
||||||
activeZone.innerHTML = "";
|
vscClearElement(activeZone);
|
||||||
availableZone.innerHTML = "";
|
vscClearElement(availableZone);
|
||||||
|
|
||||||
|
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 +1243,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() {
|
||||||
@@ -1321,9 +1394,18 @@ function initLucideButtonIconsUI() {
|
|||||||
|
|
||||||
if (!actionSel.dataset.lucideInit) {
|
if (!actionSel.dataset.lucideInit) {
|
||||||
actionSel.dataset.lucideInit = "1";
|
actionSel.dataset.lucideInit = "1";
|
||||||
actionSel.innerHTML = "";
|
vscClearElement(actionSel);
|
||||||
Object.keys(controllerButtonDefs).forEach(function (aid) {
|
Object.keys(controllerButtonDefs).forEach(function (aid) {
|
||||||
if (aid === "reset") return;
|
if (aid === "nudge") {
|
||||||
|
Object.keys(lucideSubtitleNudgeActionLabels).forEach(function (subId) {
|
||||||
|
var o2 = document.createElement("option");
|
||||||
|
o2.value = subId;
|
||||||
|
o2.textContent =
|
||||||
|
lucideSubtitleNudgeActionLabels[subId] + " (" + subId + ")";
|
||||||
|
actionSel.appendChild(o2);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
var o = document.createElement("option");
|
var o = document.createElement("option");
|
||||||
o.value = aid;
|
o.value = aid;
|
||||||
o.textContent =
|
o.textContent =
|
||||||
@@ -1333,7 +1415,7 @@ function initLucideButtonIconsUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderResults(slugs) {
|
function renderResults(slugs) {
|
||||||
resultsEl.innerHTML = "";
|
vscClearElement(resultsEl);
|
||||||
slugs.forEach(function (slug) {
|
slugs.forEach(function (slug) {
|
||||||
var b = document.createElement("button");
|
var b = document.createElement("button");
|
||||||
b.type = "button";
|
b.type = "button";
|
||||||
@@ -1368,11 +1450,13 @@ function initLucideButtonIconsUI() {
|
|||||||
.then(function (txt) {
|
.then(function (txt) {
|
||||||
var safe = sanitizeLucideSvg(txt);
|
var safe = sanitizeLucideSvg(txt);
|
||||||
if (!safe) throw new Error("Bad SVG");
|
if (!safe) throw new Error("Bad SVG");
|
||||||
previewEl.innerHTML = safe;
|
if (!vscSetSvgContent(previewEl, safe)) {
|
||||||
|
throw new Error("Preview render failed");
|
||||||
|
}
|
||||||
setLucideStatus("Preview: " + slug);
|
setLucideStatus("Preview: " + slug);
|
||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
previewEl.innerHTML = "";
|
vscClearElement(previewEl);
|
||||||
setLucideStatus(
|
setLucideStatus(
|
||||||
"Could not load: " + slug + " — " + e.message
|
"Could not load: " + slug + " — " + e.message
|
||||||
);
|
);
|
||||||
@@ -1391,7 +1475,7 @@ function initLucideButtonIconsUI() {
|
|||||||
.then(function (map) {
|
.then(function (map) {
|
||||||
var q = searchInput.value;
|
var q = searchInput.value;
|
||||||
if (!q.trim()) {
|
if (!q.trim()) {
|
||||||
resultsEl.innerHTML = "";
|
vscClearElement(resultsEl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderResults(searchLucideSlugs(map, q, 48));
|
renderResults(searchLucideSlugs(map, q, 48));
|
||||||
@@ -1556,7 +1640,7 @@ function restore_options() {
|
|||||||
? storage.siteRules
|
? storage.siteRules
|
||||||
: tcDefaults.siteRules || [];
|
: tcDefaults.siteRules || [];
|
||||||
|
|
||||||
document.getElementById("siteRulesContainer").innerHTML = "";
|
vscClearElement(document.getElementById("siteRulesContainer"));
|
||||||
if (siteRules.length > 0) {
|
if (siteRules.length > 0) {
|
||||||
siteRules.forEach((rule) => {
|
siteRules.forEach((rule) => {
|
||||||
if (rule && rule.pattern) {
|
if (rule && rule.pattern) {
|
||||||
@@ -1771,7 +1855,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
populateControlBarZones(
|
populateControlBarZones(
|
||||||
popupActiveZone,
|
popupActiveZone,
|
||||||
popupAvailableZone,
|
popupAvailableZone,
|
||||||
getPopupControlBarOrder()
|
getPopupControlBarOrder(),
|
||||||
|
function (id) {
|
||||||
|
return !popupExcludedButtonIds.has(id);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
faster: { label: "", className: "" },
|
faster: { label: "", className: "" },
|
||||||
advance: { label: "", className: "rw" },
|
advance: { label: "", className: "rw" },
|
||||||
display: { label: "", className: "hideButton" },
|
display: { label: "", className: "hideButton" },
|
||||||
reset: { label: "", className: "" },
|
reset: { label: "\u21BB", 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;
|
||||||
|
|
||||||
@@ -217,17 +230,21 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
btn.dataset.action = btnId;
|
btn.dataset.action = btnId;
|
||||||
var customEntry = customMap[btnId];
|
var customEntry = customMap[btnId];
|
||||||
if (customEntry && customEntry.svg) {
|
if (customEntry && customEntry.svg) {
|
||||||
var customSpan = document.createElement("span");
|
var customSpan = vscCreateSvgWrap(document, customEntry.svg, "vsc-btn-icon");
|
||||||
customSpan.className = "vsc-btn-icon";
|
if (customSpan) {
|
||||||
customSpan.innerHTML = customEntry.svg;
|
btn.appendChild(customSpan);
|
||||||
btn.appendChild(customSpan);
|
} else {
|
||||||
|
btn.textContent = def.label || "?";
|
||||||
|
}
|
||||||
} else if (typeof vscIconSvgString === "function") {
|
} else if (typeof vscIconSvgString === "function") {
|
||||||
var svgStr = vscIconSvgString(btnId, 16);
|
var svgStr = vscIconSvgString(btnId, 16);
|
||||||
if (svgStr) {
|
if (svgStr) {
|
||||||
var iconSpan = document.createElement("span");
|
var iconSpan = vscCreateSvgWrap(document, svgStr, "vsc-btn-icon");
|
||||||
iconSpan.className = "vsc-btn-icon";
|
if (iconSpan) {
|
||||||
iconSpan.innerHTML = svgStr;
|
btn.appendChild(iconSpan);
|
||||||
btn.appendChild(iconSpan);
|
} else {
|
||||||
|
btn.textContent = def.label || "?";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
btn.textContent = def.label || "?";
|
btn.textContent = def.label || "?";
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -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;
|
||||||
|
|||||||
+75
-4
@@ -3,6 +3,7 @@
|
|||||||
* Use stroke="currentColor" so buttons inherit foreground for monochrome UI.
|
* Use stroke="currentColor" so buttons inherit foreground for monochrome UI.
|
||||||
*/
|
*/
|
||||||
var VSC_ICON_SIZE_DEFAULT = 18;
|
var VSC_ICON_SIZE_DEFAULT = 18;
|
||||||
|
var VSC_SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
/** Inner SVG markup only (paths / shapes inside <svg>). */
|
/** Inner SVG markup only (paths / shapes inside <svg>). */
|
||||||
var vscUiIconPaths = {
|
var vscUiIconPaths = {
|
||||||
@@ -54,6 +55,79 @@ function vscIconSvgString(action, size) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vscClearElement(el) {
|
||||||
|
if (!el) return;
|
||||||
|
while (el.firstChild) {
|
||||||
|
el.removeChild(el.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function vscSanitizeSvgTree(svg) {
|
||||||
|
if (!svg || String(svg.tagName).toLowerCase() !== "svg") return null;
|
||||||
|
|
||||||
|
svg.querySelectorAll("script, style, foreignObject").forEach(function (n) {
|
||||||
|
n.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.querySelectorAll("*").forEach(function (el) {
|
||||||
|
for (var i = el.attributes.length - 1; i >= 0; i--) {
|
||||||
|
var attr = el.attributes[i];
|
||||||
|
var name = attr.name.toLowerCase();
|
||||||
|
var val = attr.value;
|
||||||
|
if (name.indexOf("on") === 0) {
|
||||||
|
el.removeAttribute(attr.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(name === "href" || name === "xlink:href") &&
|
||||||
|
/^\s*javascript:/i.test(val)
|
||||||
|
) {
|
||||||
|
el.removeAttribute(attr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.setAttribute("xmlns", VSC_SVG_NS);
|
||||||
|
svg.setAttribute("aria-hidden", "true");
|
||||||
|
return svg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vscCreateSvgNode(doc, svgText) {
|
||||||
|
if (!doc || !svgText || typeof svgText !== "string") return null;
|
||||||
|
var clean = String(svgText).replace(/\0/g, "").trim();
|
||||||
|
if (!clean || !/<svg[\s>]/i.test(clean)) return null;
|
||||||
|
|
||||||
|
var parsed = new DOMParser().parseFromString(clean, "image/svg+xml");
|
||||||
|
if (parsed.querySelector("parsererror")) return null;
|
||||||
|
|
||||||
|
var svg = vscSanitizeSvgTree(parsed.querySelector("svg"));
|
||||||
|
if (!svg) return null;
|
||||||
|
|
||||||
|
return doc.importNode(svg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function vscSetSvgContent(el, svgText) {
|
||||||
|
if (!el) return false;
|
||||||
|
vscClearElement(el);
|
||||||
|
|
||||||
|
var doc = el.ownerDocument || document;
|
||||||
|
var svg = vscCreateSvgNode(doc, svgText);
|
||||||
|
if (!svg) return false;
|
||||||
|
|
||||||
|
el.appendChild(svg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vscCreateSvgWrap(doc, svgText, className) {
|
||||||
|
if (!doc) return null;
|
||||||
|
var span = doc.createElement("span");
|
||||||
|
span.className = className || "vsc-btn-icon";
|
||||||
|
if (!vscSetSvgContent(span, svgText)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Document} doc
|
* @param {Document} doc
|
||||||
* @param {string} action
|
* @param {string} action
|
||||||
@@ -62,8 +136,5 @@ function vscIconSvgString(action, size) {
|
|||||||
function vscIconWrap(doc, action, size) {
|
function vscIconWrap(doc, action, size) {
|
||||||
var html = vscIconSvgString(action, size);
|
var html = vscIconSvgString(action, size);
|
||||||
if (!html) return null;
|
if (!html) return null;
|
||||||
var span = doc.createElement("span");
|
return vscCreateSvgWrap(doc, html, "vsc-btn-icon");
|
||||||
span.className = "vsc-btn-icon";
|
|
||||||
span.innerHTML = html;
|
|
||||||
return span;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user