aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--LICENSE-APPSTORE.txt10
-rw-r--r--README.md2
-rw-r--r--manifest/manifest.json2
m---------maze-utils0
m---------public/_locales0
-rw-r--r--public/content.css12
-rw-r--r--public/options/options.html5
-rw-r--r--src/background.ts54
-rw-r--r--src/components/CategoryPillComponent.tsx16
-rw-r--r--src/components/ChapterVoteComponent.tsx2
-rw-r--r--src/components/NoticeComponent.tsx2
-rw-r--r--src/components/SponsorTimeEditComponent.tsx17
-rw-r--r--src/components/SubmissionNoticeComponent.tsx33
-rw-r--r--src/components/options/CategorySkipOptionsComponent.tsx2
-rw-r--r--src/components/options/KeybindDialogComponent.tsx1
-rw-r--r--src/config.ts2
-rw-r--r--src/content.ts111
-rw-r--r--src/js-components/previewBar.ts91
-rw-r--r--src/js-components/skipButtonControlBar.ts9
-rw-r--r--src/popup.ts8
-rw-r--r--src/render/CategoryPill.tsx12
-rw-r--r--src/render/RectangleTooltip.tsx2
-rw-r--r--src/render/SubmissionNotice.tsx8
-rw-r--r--src/utils/animationUtils.ts78
-rw-r--r--src/utils/constants.ts4
-rw-r--r--src/utils/mobileUtils.ts9
-rw-r--r--webpack/webpack.common.js2
27 files changed, 254 insertions, 240 deletions
diff --git a/LICENSE-APPSTORE.txt b/LICENSE-APPSTORE.txt
index 5df878ab..414a3de7 100644
--- a/LICENSE-APPSTORE.txt
+++ b/LICENSE-APPSTORE.txt
@@ -1,12 +1,12 @@
The developers are aware that the terms of service that
apply to apps distributed via Apple's App Store services and similar app stores may conflict
with rights granted under the SponsorBlock license, the GNU General
-Public License, version 3 or (at your option) any later version. The
-copyright holders of the SponsorBlock project do not wish this conflict
-to prevent the otherwise-compliant distribution of derived apps via
-the App Store and similar app stores. Therefore, we have committed not to pursue any license
+Public License, version 3. The copyright holders of the SponsorBlock
+project do not wish this conflict to prevent the otherwise-compliant
+distribution of derived apps via the App Store and similar app stores.
+Therefore, we have committed not to pursue any license
violation that results solely from the conflict between the GNU GPLv3
-or any later version and the Apple App Store terms of service or similar app stores. In
+and the Apple App Store terms of service or similar app stores. In
other words, as long as you comply with the GPL in all other respects,
including its requirements to provide users with source code and the
text of the license, we will not object to your distribution of the
diff --git a/README.md b/README.md
index d4589bdd..63e17e13 100644
--- a/README.md
+++ b/README.md
@@ -75,4 +75,4 @@ Icons made by:
### License
-This project is licensed under GNU LGPL v3 or any later version
+This project is licensed under GNU GPL v3 or any later version
diff --git a/manifest/manifest.json b/manifest/manifest.json
index ed81f4c3..9ad1f384 100644
--- a/manifest/manifest.json
+++ b/manifest/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
- "version": "5.4.28",
+ "version": "5.5.7",
"default_locale": "en",
"description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app",
diff --git a/maze-utils b/maze-utils
-Subproject 27010ba86e475f6225ef3ae8b3b8fb72f2ef2bb
+Subproject 8c0385deb54414bf5436e4a1a59e1a87f3a5f41
diff --git a/public/_locales b/public/_locales
-Subproject 7f2d4e63dc53facfeed96aae1086c2bc3329b51
+Subproject 3f17e350861638b1c58da3cf2381ec681dd9ed7
diff --git a/public/content.css b/public/content.css
index 285ba11d..7dd3c33f 100644
--- a/public/content.css
+++ b/public/content.css
@@ -780,6 +780,18 @@ input::-webkit-inner-spin-button {
line-height: 1.5em;
}
+/* Description on right layout */
+#title > #categoryPillParent {
+ font-size: 2rem;
+ font-weight: bold;
+ display: flex;
+ justify-content: center;
+ line-height: 2.8rem;
+}
+#title > #categoryPillParent > #categoryPill.cbPillOpen {
+ margin-bottom: 5px;
+}
+
#categoryPillParent {
height: fit-content;
margin-top: auto;
diff --git a/public/options/options.html b/public/options/options.html
index 01a3b345..146b7956 100644
--- a/public/options/options.html
+++ b/public/options/options.html
@@ -448,6 +448,11 @@
<div class="inline"></div>
</div>
+ <div data-type="keybind-change" data-sync="previewKeybind">
+ <label class="optionLabel">__MSG_setPreviewKeybind__:</label>
+ <div class="inline"></div>
+ </div>
+
<div data-type="keybind-change" data-sync="actuallySubmitKeybind">
<label class="optionLabel">__MSG_setSubmitKeybind__:</label>
<div class="inline"></div>
diff --git a/src/background.ts b/src/background.ts
index 99df62db..23914f46 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -123,7 +123,7 @@ chrome.runtime.onInstalled.addListener(function () {
// If there is no userID, then it is the first install.
if (!userID && !Config.local.alreadyInstalled){
//open up the install page
- chrome.tabs.create({url: chrome.extension.getURL("/help/index.html")});
+ chrome.tabs.create({url: chrome.runtime.getURL("/help/index.html")});
//generate a userID
const newUserID = generateUserID();
@@ -137,7 +137,7 @@ chrome.runtime.onInstalled.addListener(function () {
if (Config.config.supportInvidious) {
if (!(await utils.containsInvidiousPermission())) {
- chrome.tabs.create({url: chrome.extension.getURL("/permissions/index.html")});
+ chrome.tabs.create({url: chrome.runtime.getURL("/permissions/index.html")});
}
}
}, 1500);
@@ -160,8 +160,8 @@ async function registerFirefoxContentScript(options: Registration) {
ids: [options.id]
}).catch(() => []);
- if (existingRegistrations.length > 0
- && existingRegistrations[0].matches.every((match) => options.matches.includes(match))) {
+ if (existingRegistrations && existingRegistrations.length > 0
+ && options.matches.every((match) => existingRegistrations[0].matches.includes(match))) {
// No need to register another script, already registered
return;
}
@@ -222,27 +222,35 @@ async function submitVote(type: number, UUID: string, category: string) {
const typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category;
- //publish this vote
- const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection);
-
- if (response.ok) {
- return {
- successType: 1,
- responseText: await response.text()
- };
- } else if (response.status == 405) {
- //duplicate vote
- return {
- successType: 0,
- statusCode: response.status,
- responseText: await response.text()
- };
- } else {
- //error while connect
+ try {
+ const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection);
+
+ if (response.ok) {
+ return {
+ successType: 1,
+ responseText: await response.text()
+ };
+ } else if (response.status == 405) {
+ //duplicate vote
+ return {
+ successType: 0,
+ statusCode: response.status,
+ responseText: await response.text()
+ };
+ } else {
+ //error while connect
+ return {
+ successType: -1,
+ statusCode: response.status,
+ responseText: await response.text()
+ };
+ }
+ } catch (e) {
+ console.error(e);
return {
successType: -1,
- statusCode: response.status,
- responseText: await response.text()
+ statusCode: -1,
+ responseText: ""
};
}
}
diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx
index 1d39a400..e4c5a3ed 100644
--- a/src/components/CategoryPillComponent.tsx
+++ b/src/components/CategoryPillComponent.tsx
@@ -6,7 +6,7 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
-import { AnimationUtils } from "../utils/animationUtils";
+import { AnimationUtils } from "../../maze-utils/src/animationUtils";
import { Tooltip } from "../render/Tooltip";
import { getErrorMessage } from "../../maze-utils/src/formating";
@@ -23,12 +23,14 @@ export interface CategoryPillState {
}
class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryPillState> {
-
+ mainRef: React.MutableRefObject<HTMLSpanElement>;
tooltip?: Tooltip;
constructor(props: CategoryPillProps) {
super(props);
+ this.mainRef = React.createRef();
+
this.state = {
segment: null,
show: false,
@@ -43,17 +45,21 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
color: this.getTextColor(),
}
+ // To be able to remove the margin from the parent
+ this.mainRef?.current?.parentElement?.classList?.toggle("cbPillOpen", this.state.show);
+
return (
<span style={style}
className={"sponsorBlockCategoryPill" + (!this.props.showTextByDefault ? " sbPillNoText" : "")}
aria-label={this.getTitleText()}
onClick={(e) => this.toggleOpen(e)}
onMouseEnter={() => this.openTooltip()}
- onMouseLeave={() => this.closeTooltip()}>
+ onMouseLeave={() => this.closeTooltip()}
+ ref={this.mainRef}>
<span className="sponsorBlockCategoryPillTitleSection">
<img className="sponsorSkipLogo sponsorSkipObject"
- src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
+ src={chrome.runtime.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
{
@@ -86,7 +92,7 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
)}
{/* Close Button */}
- <img src={chrome.extension.getURL("icons/close.png")}
+ <img src={chrome.runtime.getURL("icons/close.png")}
className="categoryPillClose"
onClick={() => {
this.setState({ show: false });
diff --git a/src/components/ChapterVoteComponent.tsx b/src/components/ChapterVoteComponent.tsx
index d50878a6..677a966d 100644
--- a/src/components/ChapterVoteComponent.tsx
+++ b/src/components/ChapterVoteComponent.tsx
@@ -6,7 +6,7 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
-import { AnimationUtils } from "../utils/animationUtils";
+import { AnimationUtils } from "../../maze-utils/src/animationUtils";
import { Tooltip } from "../render/Tooltip";
import { getErrorMessage } from "../../maze-utils/src/formating";
diff --git a/src/components/NoticeComponent.tsx b/src/components/NoticeComponent.tsx
index e41c3fa7..56a96378 100644
--- a/src/components/NoticeComponent.tsx
+++ b/src/components/NoticeComponent.tsx
@@ -207,7 +207,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
{/* Close button */}
- <img src={chrome.extension.getURL("icons/close.png")}
+ <img src={chrome.runtime.getURL("icons/close.png")}
className={"sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
+ (this.props.biggerCloseButton ? " biggerCloseButton" : "")}
onClick={() => this.close()}>
diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx
index 81f43753..0564293a 100644
--- a/src/components/SponsorTimeEditComponent.tsx
+++ b/src/components/SponsorTimeEditComponent.tsx
@@ -8,6 +8,7 @@ import SelectorComponent, { SelectorOption } from "./SelectorComponent";
import { DEFAULT_CATEGORY } from "../utils/categoryUtils";
import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import { asyncRequestToServer } from "../utils/requests";
+import { defaultPreviewTime } from "../utils/constants";
export interface SponsorTimeEditProps {
index: number;
@@ -33,7 +34,7 @@ export interface SponsorTimeEditState {
chapterNameSelectorHovering: boolean;
}
-const categoryNamesGrams: string[] = [].concat(...CompileConfig.categoryList.filter((name) => name !== "chapter")
+const categoryNamesGrams: string[] = [].concat(...CompileConfig.categoryList.filter((name) => !["chapter", "intro"].includes(name))
.map((name) => chrome.i18n.getMessage("category_" + name).split(/\/|\s|-/)));
class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, SponsorTimeEditState> {
@@ -80,13 +81,15 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
componentDidMount(): void {
// Prevent inputs from triggering key events
- document.getElementById("sponsorTimeEditContainer" + this.idSuffix).addEventListener('keydown', function (event) {
- event.stopPropagation();
+ document.getElementById("sponsorTimeEditContainer" + this.idSuffix).addEventListener('keydown', (e) => {
+ e.stopPropagation();
});
// Prevent scrolling while changing times
- document.getElementById("sponsorTimesContainer" + this.idSuffix).addEventListener('wheel', function (event) {
- event.preventDefault();
+ document.getElementById("sponsorTimesContainer" + this.idSuffix).addEventListener('wheel', (e) => {
+ if (this.state.editing) {
+ e.preventDefault();
+ }
}, {passive: false});
// Add as a config listener
@@ -220,7 +223,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
target="_blank" rel="noreferrer">
<img id={"sponsorTimeCategoriesHelpButton" + this.idSuffix}
className="helpButton"
- src={chrome.extension.getURL("icons/help.svg")}
+ src={chrome.runtime.getURL("icons/help.svg")}
title={chrome.i18n.getMessage("categoryGuidelines")} />
</a>
</div>
@@ -671,7 +674,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
previewTime(ctrlPressed = false, shiftPressed = false, skipToEndTime = false): void {
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
const index = this.props.index;
- let seekTime = 2;
+ let seekTime = defaultPreviewTime;
if (ctrlPressed) seekTime = 0.5;
if (shiftPressed) seekTime = 0.25;
diff --git a/src/components/SubmissionNoticeComponent.tsx b/src/components/SubmissionNoticeComponent.tsx
index 9aa52670..2cf394f1 100644
--- a/src/components/SubmissionNoticeComponent.tsx
+++ b/src/components/SubmissionNoticeComponent.tsx
@@ -14,7 +14,7 @@ export interface SubmissionNoticeProps {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
- callback: () => unknown;
+ callback: () => Promise<boolean>;
closeListener: () => void;
}
@@ -69,6 +69,13 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
this.videoObserver.observe(this.contentContainer().v, {
attributes: true
});
+
+ // Prevent zooming while changing times
+ document.getElementById("sponsorSkipNoticeMiddleRow" + this.state.idSuffix).addEventListener('wheel', function (event) {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ }, {passive: false});
}
componentWillUnmount(): void {
@@ -82,13 +89,17 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
if (currentSegmentCount > this.lastSegmentCount) {
this.lastSegmentCount = currentSegmentCount;
- const scrollElement = this.noticeRef.current.getElement().current.querySelector("#sponsorSkipNoticeMiddleRowSubmissionNotice");
- scrollElement.scrollTo({
- top: scrollElement.scrollHeight + 1000
- });
+ this.scrollToBottom();
}
}
+ scrollToBottom() {
+ const scrollElement = this.noticeRef.current.getElement().current.querySelector("#sponsorSkipNoticeMiddleRowSubmissionNotice");
+ scrollElement.scrollTo({
+ top: scrollElement.scrollHeight + 1000
+ });
+ }
+
render(): React.ReactElement {
const sortButton =
<img id={"sponsorSkipSortButton" + this.state.idSuffix}
@@ -96,7 +107,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
onClick={() => this.sortSegments()}
title={chrome.i18n.getMessage("sortSegments")}
key="sortButton"
- src={chrome.extension.getURL("icons/sort.svg")}>
+ src={chrome.runtime.getURL("icons/sort.svg")}>
</img>;
const exportButton =
<img id={"sponsorSkipExportButton" + this.state.idSuffix}
@@ -104,7 +115,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
onClick={() => this.exportSegments()}
title={chrome.i18n.getMessage("exportSegments")}
key="exportButton"
- src={chrome.extension.getURL("icons/export.svg")}>
+ src={chrome.runtime.getURL("icons/export.svg")}>
</img>;
return (
<NoticeComponent noticeTitle={this.state.noticeTitle}
@@ -228,9 +239,11 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
}
}
- this.props.callback();
-
- this.cancel();
+ this.props.callback().then((success) => {
+ if (success) {
+ this.cancel();
+ }
+ });
}
sortSegments(): void {
diff --git a/src/components/options/CategorySkipOptionsComponent.tsx b/src/components/options/CategorySkipOptionsComponent.tsx
index b82b0a52..d9d89cf0 100644
--- a/src/components/options/CategorySkipOptionsComponent.tsx
+++ b/src/components/options/CategorySkipOptionsComponent.tsx
@@ -158,7 +158,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
});
}
- Config.forceLocalUpdate("categorySelections");
+ Config.forceSyncUpdate("categorySelections");
}
getCategorySkipOptions(): JSX.Element[] {
diff --git a/src/components/options/KeybindDialogComponent.tsx b/src/components/options/KeybindDialogComponent.tsx
index 2fdd728d..b034d7eb 100644
--- a/src/components/options/KeybindDialogComponent.tsx
+++ b/src/components/options/KeybindDialogComponent.tsx
@@ -144,6 +144,7 @@ class KeybindDialogComponent extends React.Component<KeybindDialogProps, Keybind
if (this.props.option !== "skipKeybind" && this.equals(Config.config['skipKeybind']) ||
this.props.option !== "submitKeybind" && this.equals(Config.config['submitKeybind']) ||
this.props.option !== "actuallySubmitKeybind" && this.equals(Config.config['actuallySubmitKeybind']) ||
+ this.props.option !== "previewKeybind" && this.equals(Config.config['previewKeybind']) ||
this.props.option !== "closeSkipNoticeKeybind" && this.equals(Config.config['closeSkipNoticeKeybind']) ||
this.props.option !== "startSponsorKeybind" && this.equals(Config.config['startSponsorKeybind']))
return {message: chrome.i18n.getMessage("keyAlreadyUsed"), blocking: true};
diff --git a/src/config.ts b/src/config.ts
index a8a8a682..81f41997 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -91,6 +91,7 @@ interface SBConfig {
startSponsorKeybind: Keybind;
submitKeybind: Keybind;
actuallySubmitKeybind: Keybind;
+ previewKeybind: Keybind;
nextChapterKeybind: Keybind;
previousChapterKeybind: Keybind;
closeSkipNoticeKeybind: Keybind;
@@ -347,6 +348,7 @@ const syncDefaults = {
startSponsorKeybind: { key: ";" },
submitKeybind: { key: "'" },
actuallySubmitKeybind: { key: "'", ctrl: true },
+ previewKeybind: { key: ";", ctrl: true },
nextChapterKeybind: { key: "ArrowRight", ctrl: true },
previousChapterKeybind: { key: "ArrowLeft", ctrl: true },
closeSkipNoticeKeybind: { key: "Backspace" },
diff --git a/src/content.ts b/src/content.ts
index 0ea9da11..f89fb369 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -26,7 +26,7 @@ import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
import { getStartTimeFromUrl } from "./utils/urlParser";
import { getControls, getExistingChapters, getHashParams, isPlayingPlaylist, isVisible } from "./utils/pageUtils";
import { CategoryPill } from "./render/CategoryPill";
-import { AnimationUtils } from "./utils/animationUtils";
+import { AnimationUtils } from "../maze-utils/src/animationUtils";
import { GenericUtils } from "./utils/genericUtils";
import { logDebug, logWarn } from "./utils/logger";
import { importTimes } from "./utils/exporter";
@@ -35,7 +35,7 @@ import { openWarningDialog } from "./utils/warnings";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video";
-import { Keybind, StorageChangesObject, isSafari, keybindEquals } from "../maze-utils/src/config";
+import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
import { findValidElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash";
import { generateUserID } from "../maze-utils/src/setup";
@@ -47,6 +47,8 @@ import { cleanPage } from "./utils/pageCleaner";
import { addCleanupListener } from "../maze-utils/src/cleanup";
import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromotion";
import { asyncRequestToServer } from "./utils/requests";
+import { isMobileControlsOpen } from "./utils/mobileUtils";
+import { defaultPreviewTime } from "./utils/constants";
cleanPage();
@@ -76,6 +78,7 @@ let activeSkipKeybindElement: ToggleSkippable = null;
let retryFetchTimeout: NodeJS.Timeout = null;
let shownSegmentFailedToFetchWarning = false;
let selectedSegment: SegmentUUID | null = null;
+let previewedSegment = false;
// JSON video info
let videoInfo: VideoInfo = null;
@@ -255,7 +258,12 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
break;
case "refreshSegments":
// update video on refresh if videoID invalid
- if (!getVideoID()) checkVideoIDChange();
+ if (!getVideoID()) {
+ checkVideoIDChange().then(() => {
+ // if still no video ID found, return an empty info to the popup
+ if (!getVideoID()) chrome.runtime.sendMessage({ message: "infoUpdated" });
+ });
+ }
// fetch segments
sponsorsLookup(false);
@@ -370,6 +378,7 @@ function resetValues() {
lastCheckTime = 0;
lastCheckVideoTime = -1;
retryCount = 0;
+ previewedSegment = false;
sponsorTimes = [];
existingChaptersImported = false;
@@ -450,23 +459,22 @@ function videoIDChange(): void {
}
function handleMobileControlsMutations(): void {
- if (!chrome.runtime?.id) return;
+ // Don't update while scrubbing
+ if (!chrome.runtime?.id
+ || document.querySelector(".YtProgressBarProgressBarPlayheadDotInDragging")) return;
updateVisibilityOfPlayerControlsButton();
skipButtonControlBar?.updateMobileControls();
if (previewBar !== null) {
- if (document.body.contains(previewBar.container)) {
- const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background");
-
- if (progressBarBackground !== null) {
- updatePreviewBarPositionMobile(progressBarBackground);
- }
+ if (!previewBar.parent.contains(previewBar.container) && isMobileControlsOpen()) {
+ previewBar.createElement();
+ updatePreviewBar();
return;
- } else {
- // The container does not exist anymore, remove that old preview bar
+ } else if (!previewBar.parent.isConnected) {
+ // The parent does not exist anymore, remove that old preview bar
previewBar.remove();
previewBar = null;
}
@@ -483,14 +491,14 @@ function createPreviewBar(): void {
if (previewBar !== null) return;
const progressElementOptions = [{
- // For mobile YouTube
- selector: ".progress-bar-background",
- isVisibleCheck: true
- }, {
// For new mobile YouTube (#1287)
selector: ".progress-bar-line",
isVisibleCheck: true
}, {
+ // For newer mobile YouTube (Jan 2024)
+ selector: ".YtProgressBarProgressBarLine",
+ isVisibleCheck: true
+ }, {
// For Desktop YouTube
selector: ".ytp-progress-bar",
isVisibleCheck: true
@@ -769,6 +777,7 @@ function getVirtualTime(): number {
function inMuteSegment(currentTime: number, includeOverlap: boolean): boolean {
const checkFunction = (segment) => segment.actionType === ActionType.Mute
+ && segment.hidden === SponsorHideType.Visible
&& segment.segment[0] <= currentTime
&& (segment.segment[1] > currentTime || (includeOverlap && segment.segment[1] + 0.02 > currentTime));
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
@@ -1314,15 +1323,6 @@ function startSkipScheduleCheckingForStartSponsors() {
}
}
-/**
- * This function is required on mobile YouTube and will keep getting called whenever the preview bar disapears
- */
-function updatePreviewBarPositionMobile(parent: HTMLElement) {
- if (document.getElementById("previewbar") === null) {
- previewBar.createElement(parent);
- }
-}
-
function selectSegment(UUID: SegmentUUID): void {
selectedSegment = UUID;
updatePreviewBar();
@@ -1589,6 +1589,7 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
* @param time
*/
function previewTime(time: number, unpause = true) {
+ previewedSegment = true;
getVideo().currentTime = time;
// Unpause the video if needed
@@ -1615,6 +1616,9 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
}
if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
+ } else if (!previewedSegment && sponsorTimesSubmitting.some((s) => s.segment === segment.segment)) {
+ // Count that as a previewed segment
+ previewedSegment = true;
}
}
}
@@ -1775,7 +1779,7 @@ function createButton(baseID: string, title: string, callback: () => void, image
newButton.draggable = isDraggable;
newButtonImage.id = baseID + "Image";
newButtonImage.className = "playerButtonImage";
- newButtonImage.src = chrome.extension.getURL("icons/" + imageName);
+ newButtonImage.src = chrome.runtime.getURL("icons/" + imageName);
// Append image to button
newButton.appendChild(newButtonImage);
@@ -1874,10 +1878,10 @@ function updateEditButtonsOnPlayer(): void {
if (buttonsEnabled) {
if (creatingSegment) {
- playerButtons.startSegment.image.src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker.svg");
+ playerButtons.startSegment.image.src = chrome.runtime.getURL("icons/PlayerStopIconSponsorBlocker.svg");
playerButtons.startSegment.button.setAttribute("title", chrome.i18n.getMessage("sponsorEnd"));
} else {
- playerButtons.startSegment.image.src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker.svg");
+ playerButtons.startSegment.image.src = chrome.runtime.getURL("icons/PlayerStartIconSponsorBlocker.svg");
playerButtons.startSegment.button.setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
}
}
@@ -1990,6 +1994,9 @@ function updateSponsorTimesSubmitting(getFromConfig = true) {
}
if (sponsorTimesSubmitting.length > 0) {
+ // Assume they already previewed a segment
+ previewedSegment = true;
+
importExistingChapters(true);
}
}
@@ -2055,7 +2062,7 @@ function openInfoMenu() {
}
}
});
- frame.src = chrome.extension.getURL("popup.html");
+ frame.src = chrome.runtime.getURL("popup.html");
popup.appendChild(frame);
const elemHasChild = (elements: NodeListOf<HTMLElement>): Element => {
@@ -2241,7 +2248,16 @@ function openSubmissionMenu() {
if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
}
+}
+function previewRecentSegment() {
+ if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
+ previewTime(sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[0] - defaultPreviewTime);
+
+ if (submissionNotice) {
+ submissionNotice.scrollToBottom();
+ }
+ }
}
function submitSegments() {
@@ -2255,17 +2271,26 @@ function submitSegments() {
//send the message to the background js
//called after all the checks have been made that it's okay to do so
-async function sendSubmitMessage() {
+async function sendSubmitMessage(): Promise<boolean> {
// check if all segments are full video
const onlyFullVideo = sponsorTimesSubmitting.every((segment) => segment.actionType === ActionType.Full);
// Block if submitting on a running livestream or premiere
if (!onlyFullVideo && (getIsLivePremiere() || isVisible(document.querySelector(".ytp-live-badge")))) {
alert(chrome.i18n.getMessage("liveOrPremiere"));
- return;
+ return false;
+ }
+
+ if (!previewedSegment
+ && !sponsorTimesSubmitting.every((segment) =>
+ [ActionType.Full, ActionType.Chapter, ActionType.Poi].includes(segment.actionType)
+ || segment.segment[1] >= getVideo()?.duration
+ || segment.segment[0] === 0)) {
+ alert(`${chrome.i18n.getMessage("previewSegmentRequired")} ${keybindToString(Config.config.previewKeybind)}`);
+ return false;
}
// Add loading animation
- playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker.svg");
+ playerButtons.submit.image.src = chrome.runtime.getURL("icons/PlayerUploadIconSponsorBlocker.svg");
const stopAnimation = AnimationUtils.applyLoadingAnimation(playerButtons.submit.button, 1, () => updateEditButtonsOnPlayer());
//check if a sponsor exceeds the duration of the video
@@ -2287,7 +2312,7 @@ async function sendSubmitMessage() {
const confirmShort = chrome.i18n.getMessage("shortCheck") + "\n\n" +
getSegmentsMessage(sponsorTimesSubmitting);
- if(!confirm(confirmShort)) return;
+ if(!confirm(confirmShort)) return false;
}
}
}
@@ -2337,10 +2362,12 @@ async function sendSubmitMessage() {
if (fullVideoSegment) {
categoryPill?.setSegment(fullVideoSegment);
}
+
+ return true;
} else {
// Show that the upload failed
playerButtons.submit.button.style.animation = "unset";
- playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg");
+ playerButtons.submit.image.src = chrome.runtime.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg");
if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a tip from a moderator.")) {
openWarningDialog(skipNoticeContentContainer);
@@ -2348,6 +2375,8 @@ async function sendSubmitMessage() {
alert(getErrorMessage(response.status, response.responseText));
}
}
+
+ return false;
}
//get the message that visually displays the video times
@@ -2373,16 +2402,12 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
}
function updateActiveSegment(currentTime: number): void {
- const activeSegments = previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime);
+ previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime);
+
chrome.runtime.sendMessage({
message: "time",
time: currentTime
});
-
- const chapterSegments = activeSegments?.filter((segment) => segment.actionType === ActionType.Chapter);
- if (chapterSegments?.length > 0) {
- sendTelemetryAndCount(chapterSegments, 0, true);
- }
}
function nextChapter(): void {
@@ -2461,6 +2486,7 @@ function hotkeyListener(e: KeyboardEvent): void {
const closeSkipNoticeKey = Config.config.closeSkipNoticeKeybind;
const startSponsorKey = Config.config.startSponsorKeybind;
const submitKey = Config.config.actuallySubmitKeybind;
+ const previewKey = Config.config.previewKeybind;
const openSubmissionMenuKey = Config.config.submitKeybind;
const nextChapterKey = Config.config.nextChapterKeybind;
const previousChapterKey = Config.config.previousChapterKeybind;
@@ -2492,6 +2518,9 @@ function hotkeyListener(e: KeyboardEvent): void {
} else if (keybindEquals(key, openSubmissionMenuKey)) {
openSubmissionMenu();
return;
+ } else if (keybindEquals(key, previewKey)) {
+ previewRecentSegment();
+ return;
} else if (keybindEquals(key, nextChapterKey)) {
if (sponsorTimes.length > 0) e.stopPropagation();
nextChapter();
@@ -2526,7 +2555,7 @@ function addCSS() {
fileref.rel = "stylesheet";
fileref.type = "text/css";
- fileref.href = chrome.extension.getURL(file);
+ fileref.href = chrome.runtime.getURL(file);
head.appendChild(fileref);
}
diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts
index 1406f691..5e002fb9 100644
--- a/src/js-components/previewBar.ts
+++ b/src/js-components/previewBar.ts
@@ -11,7 +11,6 @@ import { ActionType, Category, SegmentContainer, SponsorHideType, SponsorSourceT
import { partition } from "../utils/arrayUtils";
import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
import { normalizeChapterName } from "../utils/exporter";
-import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup";
@@ -125,34 +124,11 @@ class PreviewBar {
mouseOnSeekBar = false;
});
- const observer = new MutationObserver((mutations) => {
- if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer) return;
+ seekBar.addEventListener("mousemove", (e: MouseEvent) => {
+ if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer || !chrome.runtime?.id) return;
- // Only care about mutations to time tooltip
- if (!mutations.some((mutation) => (mutation.target as HTMLElement).classList.contains("ytp-tooltip-text"))) {
- return;
- }
-
- const tooltipTextElements = tooltipTextWrapper.querySelectorAll(".ytp-tooltip-text");
- let timeInSeconds: number | null = null;
- let noYoutubeChapters = false;
-
- for (const tooltipTextElement of tooltipTextElements) {
- if (tooltipTextElement.classList.contains('ytp-tooltip-text-no-title')) noYoutubeChapters = true;
-
- const tooltipText = tooltipTextElement.textContent;
- if (tooltipText === null || tooltipText.length === 0) continue;
-
- timeInSeconds = getFormattedTimeToSeconds(tooltipText);
-
- if (timeInSeconds !== null) break;
- }
-
- if (timeInSeconds === null) {
- originalTooltip.style.removeProperty("display");
-
- return;
- }
+ let noYoutubeChapters = !!tooltipTextWrapper.querySelector(".ytp-tooltip-text.ytp-tooltip-text-no-title");
+ const timeInSeconds = this.decimalToTime((e.clientX - seekBar.getBoundingClientRect().x) / seekBar.clientWidth);
// Find the segment at that location, using the shortest if multiple found
const [normalSegments, chapterSegments] =
@@ -198,15 +174,6 @@ class PreviewBar {
this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign;
}
});
-
- observer.observe(tooltipTextWrapper, {
- childList: true,
- subtree: true,
- });
-
- addCleanupListener(() => {
- observer.disconnect();
- });
}
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
@@ -224,16 +191,12 @@ class PreviewBar {
}
}
- createElement(parent: HTMLElement): void {
- this.parent = parent;
+ createElement(parent?: HTMLElement): void {
+ if (parent) this.parent = parent;
if (this.onMobileYouTube) {
- if (parent.classList.contains("progress-bar-background")) {
- parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
- parent.style.opacity = "1";
- }
-
this.container.style.transform = "none";
+ this.container.style.height = "var(--yt-progress-bar-height)";
} else if (!this.onInvidious) {
this.container.classList.add("sbNotInvidious");
}
@@ -311,6 +274,7 @@ class PreviewBar {
return (b[1] - b[0]) - (a[1] - a[0]);
});
for (const segment of sortedSegments) {
+ if (segment.actionType === ActionType.Chapter) continue;
const bar = this.createBar(segment);
this.container.appendChild(bar);
@@ -350,7 +314,7 @@ class PreviewBar {
bar.style.left = this.timeToPercentage(startTime);
if (duration > 0) {
- bar.style.right = this.timeToPercentage(this.videoDuration - endTime);
+ bar.style.right = this.timeToRightPercentage(endTime);
}
if (this.chapterFilter(barSegment) && segment[1] < this.videoDuration) {
bar.style.marginRight = `${this.chapterMargin}px`;
@@ -923,7 +887,22 @@ class PreviewBar {
return `${this.timeToDecimal(time) * 100}%`
}
+ timeToRightPercentage(time: number): string {
+ return `${(1 - this.timeToDecimal(time)) * 100}%`
+ }
+
timeToDecimal(time: number): number {
+ return this.decimalTimeConverter(time, true);
+ }
+
+ decimalToTime(decimal: number): number {
+ return this.decimalTimeConverter(decimal, false);
+ }
+
+ /**
+ * Decimal to time or time to decimal
+ */
+ decimalTimeConverter(value: number, isTime: boolean): number {
if (this.originalChapterBarBlocks?.length > 1 && this.existingChapters.length === this.originalChapterBarBlocks?.length) {
// Parent element to still work when display: none
const totalPixels = this.originalChapterBar.parentElement.clientWidth;
@@ -933,8 +912,9 @@ class PreviewBar {
const chapterElement = this.originalChapterBarBlocks[i];
const widthPixels = parseFloat(chapterElement.style.width.replace("px", ""));
- if (time >= this.existingChapters[i].segment[1]) {
- const marginPixels = chapterElement.style.marginRight ? parseFloat(chapterElement.style.marginRight.replace("px", "")) : 0;
+ const marginPixels = chapterElement.style.marginRight ? parseFloat(chapterElement.style.marginRight.replace("px", "")) : 0;
+ if ((isTime && value >= this.existingChapters[i].segment[1])
+ || (!isTime && value >= (pixelOffset + widthPixels + marginPixels) / totalPixels)) {
pixelOffset += widthPixels + marginPixels;
lastCheckedChapter = i;
} else {
@@ -948,13 +928,22 @@ class PreviewBar {
const latestWidth = parseFloat(this.originalChapterBarBlocks[lastCheckedChapter + 1].style.width.replace("px", ""));
const latestChapterDuration = latestChapter.segment[1] - latestChapter.segment[0];
- const percentageInCurrentChapter = (time - latestChapter.segment[0]) / latestChapterDuration;
- const sizeOfCurrentChapter = latestWidth / totalPixels;
- return Math.min(1, ((pixelOffset / totalPixels) + (percentageInCurrentChapter * sizeOfCurrentChapter)));
+ if (isTime) {
+ const percentageInCurrentChapter = (value - latestChapter.segment[0]) / latestChapterDuration;
+ const sizeOfCurrentChapter = latestWidth / totalPixels;
+ return Math.min(1, ((pixelOffset / totalPixels) + (percentageInCurrentChapter * sizeOfCurrentChapter)));
+ } else {
+ const percentageInCurrentChapter = (value * totalPixels - pixelOffset) / latestWidth;
+ return Math.max(0, latestChapter.segment[0] + (percentageInCurrentChapter * latestChapterDuration));
+ }
}
}
- return Math.min(1, time / this.videoDuration);
+ if (isTime) {
+ return Math.min(1, value / this.videoDuration);
+ } else {
+ return Math.max(0, value * this.videoDuration);
+ }
}
/*
diff --git a/src/js-components/skipButtonControlBar.ts b/src/js-components/skipButtonControlBar.ts
index b14eed18..b5c18386 100644
--- a/src/js-components/skipButtonControlBar.ts
+++ b/src/js-components/skipButtonControlBar.ts
@@ -1,8 +1,9 @@
import Config from "../config";
import { SegmentUUID, SponsorTime } from "../types";
import { getSkippingText } from "../utils/categoryUtils";
-import { AnimationUtils } from "../utils/animationUtils";
+import { AnimationUtils } from "../../maze-utils/src/animationUtils";
import { keybindToString } from "../../maze-utils/src/config";
+import { isMobileControlsOpen } from "../utils/mobileUtils";
export interface SkipButtonControlBarProps {
skip: (segment: SponsorTime) => void;
@@ -183,10 +184,8 @@ export class SkipButtonControlBar {
}
updateMobileControls(): void {
- const overlay = document.getElementById("player-control-overlay");
-
- if (overlay && this.enabled) {
- if (overlay?.classList?.contains("fadein")) {
+ if (this.enabled) {
+ if (isMobileControlsOpen()) {
this.showButton();
} else {
this.hideButton();
diff --git a/src/popup.ts b/src/popup.ts
index e1e5757f..f954f168 100644
--- a/src/popup.ts
+++ b/src/popup.ts
@@ -19,7 +19,7 @@ import {
VoteResponse,
} from "./messageTypes";
import { showDonationLink } from "./utils/configUtils";
-import { AnimationUtils } from "./utils/animationUtils";
+import { AnimationUtils } from "../maze-utils/src/animationUtils";
import { shortCategoryName } from "./utils/categoryUtils";
import { localizeHtmlPage } from "../maze-utils/src/setup";
import { exportTimes } from "./utils/exporter";
@@ -465,8 +465,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
return;
}
- //if request is undefined, then the page currently being browsed is not YouTube
- if (request != undefined) {
+ // if request has no field other than message, then the page currently being browsed is not YouTube
+ if (request.found != undefined) {
//remove loading text
PageElements.mainControls.style.display = "block";
if (request.onMobileYouTube) PageElements.mainControls.classList.add("hidden");
@@ -490,6 +490,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.issueReporterImportExport.classList.remove("hidden");
}
+ } else {
+ displayNoVideo();
}
//see if whitelist button should be swapped
diff --git a/src/render/CategoryPill.tsx b/src/render/CategoryPill.tsx
index 57730af9..20923fba 100644
--- a/src/render/CategoryPill.tsx
+++ b/src/render/CategoryPill.tsx
@@ -43,9 +43,15 @@ export class CategoryPill {
}
private async attachToPageInternal(): Promise<void> {
- const referenceNode =
+ let referenceNode =
await waitFor(() => getYouTubeTitleNode());
+ // Experimental YouTube layout with description on right
+ const isOnDescriptionOnRightLayout = document.querySelector("#title #description");
+ if (isOnDescriptionOnRightLayout) {
+ referenceNode = referenceNode.parentElement;
+ }
+
if (referenceNode && !referenceNode.contains(this.container)) {
if (!this.container) {
this.container = document.createElement('span');
@@ -91,7 +97,9 @@ export class CategoryPill {
parent.appendChild(this.container);
referenceNode.prepend(parent);
- referenceNode.style.display = "flex";
+ if (!isOnDescriptionOnRightLayout) {
+ referenceNode.style.display = "flex";
+ }
}
}
diff --git a/src/render/RectangleTooltip.tsx b/src/render/RectangleTooltip.tsx
index 1b357fa8..d325688a 100644
--- a/src/render/RectangleTooltip.tsx
+++ b/src/render/RectangleTooltip.tsx
@@ -59,7 +59,7 @@ export class RectangleTooltip {
className="sponsorBlockRectangleTooltip" >
<div>
<img className="sponsorSkipLogo sponsorSkipObject"
- src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
+ src={chrome.runtime.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
<span className="sponsorSkipObject">
{this.text + (props.link ? ". " : "")}
diff --git a/src/render/SubmissionNotice.tsx b/src/render/SubmissionNotice.tsx
index 671dde6b..c0159cc0 100644
--- a/src/render/SubmissionNotice.tsx
+++ b/src/render/SubmissionNotice.tsx
@@ -11,7 +11,7 @@ class SubmissionNotice {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => unknown;
- callback: () => unknown;
+ callback: () => Promise<boolean>;
noticeRef: React.MutableRefObject<SubmissionNoticeComponent>;
@@ -19,7 +19,7 @@ class SubmissionNotice {
root: Root;
- constructor(contentContainer: ContentContainer, callback: () => unknown) {
+ constructor(contentContainer: ContentContainer, callback: () => Promise<boolean>) {
this.noticeRef = React.createRef();
this.contentContainer = contentContainer;
@@ -56,6 +56,10 @@ class SubmissionNotice {
submit(): void {
this.noticeRef.current?.submit?.();
}
+
+ scrollToBottom(): void {
+ this.noticeRef.current?.scrollToBottom?.();
+ }
}
export default SubmissionNotice; \ No newline at end of file
diff --git a/src/utils/animationUtils.ts b/src/utils/animationUtils.ts
deleted file mode 100644
index 08a59ce0..00000000
--- a/src/utils/animationUtils.ts
+++ /dev/null
@@ -1,78 +0,0 @@
- /**
- * Starts a spinning animation and returns a function to be called when it should be stopped
- * The callback will be called when the animation is finished
- * It waits until a full rotation is complete
- */
-function applyLoadingAnimation(element: HTMLElement, time: number, callback?: () => void): () => Promise<void> {
- element.style.animation = `rotate ${time}s 0s infinite`;
-
- return async () => new Promise((resolve) => {
- // Make the animation finite
- element.style.animation = `rotate ${time}s`;
-
- // When the animation is over, hide the button
- const animationEndListener = () => {
- if (callback) callback();
-
- element.style.animation = "none";
-
- element.removeEventListener("animationend", animationEndListener);
-
- resolve();
- };
-
- element.addEventListener("animationend", animationEndListener);
- });
-}
-
-function setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void; show: () => void } {
- if (enabled) element.classList.add("autoHiding");
- element.classList.add("sbhidden");
- element.classList.add("animationDone");
- if (!rightSlide) element.classList.add("autoHideLeft");
-
- let mouseEntered = false;
-
- return {
- hide: () => {
- mouseEntered = false;
- if (element.classList.contains("autoHiding")) {
- element.classList.add("sbhidden");
- }
- },
- show: () => {
- mouseEntered = true;
- element.classList.remove("animationDone");
-
- // Wait for next event loop
- setTimeout(() => {
- if (mouseEntered) element.classList.remove("sbhidden")
- }, 10);
- }
- };
-}
-
-function setupAutoHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): void {
- const { hide, show } = this.setupCustomHideAnimation(element, container, enabled, rightSlide);
-
- container.addEventListener("mouseleave", () => hide());
- container.addEventListener("mouseenter", () => show());
-}
-
-function enableAutoHideAnimation(element: Element): void {
- element.classList.add("autoHiding");
- element.classList.add("sbhidden");
-}
-
-function disableAutoHideAnimation(element: Element): void {
- element.classList.remove("autoHiding");
- element.classList.remove("sbhidden");
-}
-
-export const AnimationUtils = {
- applyLoadingAnimation,
- setupAutoHideAnimation,
- setupCustomHideAnimation,
- enableAutoHideAnimation,
- disableAutoHideAnimation
-}; \ No newline at end of file
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index dd44676a..afceb710 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -157,4 +157,6 @@ export function getGuidelineInfo(category: Category): TextBox[] {
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
}
-} \ No newline at end of file
+}
+
+export const defaultPreviewTime = 2; \ No newline at end of file
diff --git a/src/utils/mobileUtils.ts b/src/utils/mobileUtils.ts
new file mode 100644
index 00000000..3cff18f7
--- /dev/null
+++ b/src/utils/mobileUtils.ts
@@ -0,0 +1,9 @@
+export function isMobileControlsOpen(): boolean {
+ const overlay = document.getElementById("player-control-overlay");
+
+ if (overlay) {
+ return !!overlay?.classList?.contains("fadein");
+ }
+
+ return false;
+} \ No newline at end of file
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index e1a3e66c..189681c6 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -160,7 +160,7 @@ module.exports = env => {
if (path.match(/(\/|\\)_locales(\/|\\).+/)) {
const parsed = JSON.parse(content.toString());
if (env.browser.toLowerCase() === "safari") {
- parsed.fullName.message = parsed.fullName.message.match(/^.+(?= -)/)?.[0] || parsed.fullName.message;
+ parsed.fullName.message = parsed.fullName.message.match(/^.+(?= [-–])/)?.[0] || parsed.fullName.message;
if (parsed.fullName.message.length > 50) {
parsed.fullName.message = parsed.fullName.message.slice(0, 47) + "...";
}