mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2026-01-30 06:10:55 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
import Config from "../config"
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import { Category, CategorySkipOption } from "../types";
|
||||
|
||||
import { getCategoryActionType } from "../utils/categoryUtils";
|
||||
@@ -93,6 +94,10 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
<td
|
||||
colSpan={2}>
|
||||
{chrome.i18n.getMessage("category_" + this.props.category + "_description")}
|
||||
{' '}
|
||||
<a href={CompileConfig.wikiLinks[this.props.category]} target="_blank" rel="noreferrer">
|
||||
{`${chrome.i18n.getMessage("LearnMore")}`}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface NoticeProps {
|
||||
|
||||
zIndex?: number,
|
||||
style?: React.CSSProperties
|
||||
biggerCloseButton?: boolean;
|
||||
}
|
||||
|
||||
export interface NoticeState {
|
||||
@@ -151,7 +152,8 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
||||
|
||||
{/* Close button */}
|
||||
<img src={chrome.extension.getURL("icons/close.png")}
|
||||
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
|
||||
className={"sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
|
||||
+ (this.props.biggerCloseButton ? " biggerCloseButton" : "")}
|
||||
onClick={() => this.close()}>
|
||||
</img>
|
||||
</td>
|
||||
|
||||
@@ -188,6 +188,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
maxCountdownTime={this.state.maxCountdownTime}
|
||||
videoSpeed={() => this.contentContainer().v?.playbackRate}
|
||||
style={noticeStyle}
|
||||
biggerCloseButton={this.contentContainer().onMobileYouTube}
|
||||
ref={this.noticeRef}
|
||||
closeListener={() => this.closeListener()}
|
||||
smaller={this.state.smaller}
|
||||
@@ -350,13 +351,21 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||
if (this.state.showSkipButton && (this.segments.length > 1
|
||||
|| getCategoryActionType(this.segments[0].category) !== CategoryActionType.POI
|
||||
|| this.props.unskipTime)) {
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
marginLeft: "4px",
|
||||
color: (this.state.actionState === SkipNoticeAction.Unskip) ? this.selectedColor : this.unselectedColor
|
||||
};
|
||||
if (this.contentContainer().onMobileYouTube) {
|
||||
style.padding = "20px";
|
||||
style.minWidth = "100px";
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="sponsorSkipNoticeUnskipSection">
|
||||
<button id={"sponsorSkipUnskipButton" + this.idSuffix}
|
||||
className="sponsorSkipObject sponsorSkipNoticeButton"
|
||||
style={{marginLeft: "4px",
|
||||
color: (this.state.actionState === SkipNoticeAction.Unskip) ? this.selectedColor : this.unselectedColor
|
||||
}}
|
||||
style={style}
|
||||
onClick={() => this.prepAction(SkipNoticeAction.Unskip)}>
|
||||
{this.state.skipButtonText + (this.state.showKeybindHint ? " (" + Config.config.skipKeybind + ")" : "")}
|
||||
</button>
|
||||
|
||||
@@ -39,6 +39,9 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
configUpdateListener: () => void;
|
||||
|
||||
previousSkipType: CategoryActionType;
|
||||
timeBeforeChangingToPOI: number; // Initialized when first selecting POI
|
||||
|
||||
constructor(props: SponsorTimeEditProps) {
|
||||
super(props);
|
||||
|
||||
@@ -47,8 +50,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
this.descriptionOptionRef = React.createRef();
|
||||
|
||||
this.idSuffix = this.props.idSuffix;
|
||||
|
||||
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
|
||||
this.previousSkipType = CategoryActionType.Skippable;
|
||||
|
||||
this.state = {
|
||||
editing: false,
|
||||
sponsorTimeEdits: [null, null],
|
||||
@@ -155,8 +158,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
<div id={"sponsorTimesContainer" + this.idSuffix}
|
||||
className="sponsorTimeDisplay"
|
||||
onClick={this.toggleEditTime.bind(this)}
|
||||
onWheel={this.toggleEditTime.bind(this)}>
|
||||
onClick={this.toggleEditTime.bind(this)}>
|
||||
{utils.getFormattedTime(segment[0], true) +
|
||||
((!isNaN(segment[1]) && getCategoryActionType(sponsorTime.category) === CategoryActionType.Skippable)
|
||||
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segment[1], true) : "")}
|
||||
@@ -228,7 +230,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
{(!isNaN(segment[1]) && getCategoryActionType(sponsorTime.category) === CategoryActionType.Skippable) ? (
|
||||
<span id={"sponsorTimePreviewButton" + this.idSuffix}
|
||||
className="sponsorTimeEditButton"
|
||||
onClick={this.previewTime.bind(this)}>
|
||||
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey)}>
|
||||
{chrome.i18n.getMessage("preview")}
|
||||
</span>
|
||||
): ""}
|
||||
@@ -292,6 +294,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
sponsorTimeEdits[index] = utils.getFormattedTime(timeAsNumber, true);
|
||||
if (getCategoryActionType(sponsorTime.category) === CategoryActionType.POI) sponsorTimeEdits[1] = sponsorTimeEdits[0];
|
||||
|
||||
this.setState({sponsorTimeEdits});
|
||||
this.saveEditTimes();
|
||||
}
|
||||
@@ -305,11 +308,13 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
referenceNode: element.parentElement,
|
||||
prependElement: element,
|
||||
timeout: 15,
|
||||
bottomOffset: 75 + "px",
|
||||
bottomOffset: 0 + "px",
|
||||
leftOffset: -318 + "px",
|
||||
backgroundColor: "rgba(28, 28, 28, 1.0)",
|
||||
htmlId: "sponsorTimesContainer" + this.idSuffix,
|
||||
buttonFunction: () => {Config.config.scrollToEditTimeUpdate = true}
|
||||
buttonFunction: () => { Config.config.scrollToEditTimeUpdate = true },
|
||||
fontSize: "14px",
|
||||
maxHeight: "200px"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -343,7 +348,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
const chosenCategory = event.target.value as Category;
|
||||
|
||||
// See if show more categories was pressed
|
||||
if (event.target.value !== DEFAULT_CATEGORY && !Config.config.categorySelections.some((category) => category.name === event.target.value)) {
|
||||
if (chosenCategory !== DEFAULT_CATEGORY && !Config.config.categorySelections.some((category) => category.name === chosenCategory)) {
|
||||
event.target.value = DEFAULT_CATEGORY;
|
||||
|
||||
// Alert that they have to enable this category first
|
||||
@@ -357,6 +362,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
}
|
||||
|
||||
if (getCategoryActionType(chosenCategory) === CategoryActionType.POI) {
|
||||
if (this.previousSkipType === CategoryActionType.Skippable) this.timeBeforeChangingToPOI = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[1]);
|
||||
this.setTimeTo(1, null);
|
||||
this.props.contentContainer().updateEditButtonsOnPlayer();
|
||||
|
||||
@@ -364,8 +370,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
.some((segment, i) => segment.category === chosenCategory && i !== this.props.index)) {
|
||||
alert(chrome.i18n.getMessage("poiOnlyOneSegment"));
|
||||
}
|
||||
} else if (getCategoryActionType(chosenCategory) === CategoryActionType.Skippable && this.previousSkipType === CategoryActionType.POI) {
|
||||
this.setTimeTo(1, this.timeBeforeChangingToPOI);
|
||||
}
|
||||
|
||||
this.previousSkipType = getCategoryActionType(chosenCategory);
|
||||
this.saveEditTimes();
|
||||
}
|
||||
|
||||
@@ -462,13 +471,17 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
this.props.contentContainer().updatePreviewBar();
|
||||
}
|
||||
|
||||
previewTime(): void {
|
||||
previewTime(ctrlPressed = false, shiftPressed = false): void {
|
||||
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
|
||||
const index = this.props.index;
|
||||
|
||||
const skipTime = sponsorTimes[index].segment[0];
|
||||
|
||||
this.props.contentContainer().previewTime(skipTime - (2 * this.props.contentContainer().v.playbackRate));
|
||||
let seekTime = 2;
|
||||
if (ctrlPressed) seekTime = 0.5;
|
||||
if (shiftPressed) seekTime = 0.25;
|
||||
|
||||
this.props.contentContainer().previewTime(skipTime - (seekTime * this.props.contentContainer().v.playbackRate));
|
||||
}
|
||||
|
||||
inspectTime(): void {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { getCategoryActionType } from "./utils/categoryUtils";
|
||||
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
||||
import { Tooltip } from "./render/Tooltip";
|
||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||
import { getControls } from "./utils/pageUtils";
|
||||
|
||||
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
||||
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
||||
@@ -30,7 +31,6 @@ let sponsorVideoID: VideoID = null;
|
||||
// List of open skip notices
|
||||
const skipNotices: SkipNotice[] = [];
|
||||
let activeSkipKeybindElement: ToggleSkippable = null;
|
||||
let lastPOISkip = 0;
|
||||
|
||||
// JSON video info
|
||||
let videoInfo: VideoInfo = null;
|
||||
@@ -151,7 +151,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
//send the sponsor times along with if it's found
|
||||
sendResponse({
|
||||
found: sponsorDataFound,
|
||||
sponsorTimes: sponsorTimes
|
||||
sponsorTimes: sponsorTimes,
|
||||
onMobileYouTube
|
||||
});
|
||||
|
||||
if (!request.updating && popupInitialised && document.getElementById("sponsorBlockPopupContainer") != null) {
|
||||
@@ -191,7 +192,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
case "refreshSegments":
|
||||
sponsorsLookup(sponsorVideoID, false).then(() => sendResponse({
|
||||
found: sponsorDataFound,
|
||||
sponsorTimes: sponsorTimes
|
||||
sponsorTimes: sponsorTimes,
|
||||
onMobileYouTube
|
||||
}));
|
||||
|
||||
return true;
|
||||
@@ -336,6 +338,8 @@ async function videoIDChange(id) {
|
||||
function handleMobileControlsMutations(): void {
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
|
||||
skipButtonControlBar?.updateMobileControls();
|
||||
|
||||
if (previewBar !== null) {
|
||||
if (document.body.contains(previewBar.container)) {
|
||||
const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background");
|
||||
@@ -392,13 +396,6 @@ function createPreviewBar(): void {
|
||||
function durationChangeListener(): void {
|
||||
updateAdFlag();
|
||||
updatePreviewBar();
|
||||
|
||||
if (sponsorTimes) sponsorTimes = sponsorTimes.filter(segmentDurationFilter);
|
||||
}
|
||||
|
||||
function segmentDurationFilter(segment: SponsorTime): boolean {
|
||||
return segment.videoDuration === 0 || !video?.duration
|
||||
|| switchingVideos || Math.abs(video.duration - segment.videoDuration) < 2;
|
||||
}
|
||||
|
||||
function cancelSponsorSchedule(): void {
|
||||
@@ -612,24 +609,6 @@ function setupVideoListeners() {
|
||||
} else {
|
||||
previewBar.updateChapterText(sponsorTimes, video.currentTime);
|
||||
}
|
||||
|
||||
if (!Config.config.dontShowNotice) {
|
||||
const currentPoiSegment = sponsorTimes?.find((segment) =>
|
||||
getCategoryActionType(segment.category) === CategoryActionType.POI &&
|
||||
video.currentTime - segment.segment[0] > 0 &&
|
||||
video.currentTime - segment.segment[0] < previewBar.getMinimumSize(true));
|
||||
if (currentPoiSegment && lastPOISkip < Date.now() - 3000
|
||||
&& !skipNotices.some((notice) => notice.segments.some((s) => s.UUID === currentPoiSegment.UUID))) {
|
||||
lastPOISkip = Date.now();
|
||||
skipToTime({
|
||||
v: video,
|
||||
skipTime: currentPoiSegment.segment,
|
||||
skippingSegments: [currentPoiSegment],
|
||||
openNotice: true,
|
||||
forceAutoSkip: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
video.addEventListener('ratechange', () => startSponsorSchedule());
|
||||
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
|
||||
@@ -655,7 +634,8 @@ function setupSkipButtonControlBar() {
|
||||
skippingSegments: [segment],
|
||||
openNotice: true,
|
||||
forceAutoSkip: true
|
||||
})
|
||||
}),
|
||||
onMobileYouTube
|
||||
});
|
||||
}
|
||||
|
||||
@@ -681,19 +661,31 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
||||
categories.push(categorySelection.name);
|
||||
}
|
||||
|
||||
const extraRequestData: Record<string, unknown> = {};
|
||||
const windowHash = window.location.hash.substr(1);
|
||||
if (windowHash) {
|
||||
const params: Record<string, unknown> = windowHash.split('&').reduce((acc, param) => {
|
||||
const [key, value] = param.split('=');
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (params.requiredSegment) extraRequestData.requiredSegment = params.requiredSegment;
|
||||
}
|
||||
|
||||
// Check for hashPrefix setting
|
||||
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
||||
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories,
|
||||
actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute] : [ActionType.Skip],
|
||||
userAgent: `${chrome.runtime.id}`
|
||||
userAgent: `${chrome.runtime.id}`,
|
||||
...extraRequestData
|
||||
});
|
||||
|
||||
if (response?.ok) {
|
||||
const recievedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||
?.filter((video) => video.videoID === id)
|
||||
?.map((video) => video.segments)[0]
|
||||
?.filter(segmentDurationFilter);
|
||||
?.map((video) => video.segments)[0];
|
||||
if (!recievedSegments || !recievedSegments.length) {
|
||||
// return if no video found
|
||||
retryFetch();
|
||||
@@ -1239,6 +1231,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||
&& skippingSegments.length === 1
|
||||
&& getCategoryActionType(skippingSegments[0].category) === CategoryActionType.POI) {
|
||||
skipButtonControlBar.enable(skippingSegments[0], !Config.config.highlightCategoryUpdate ? 15 : 0);
|
||||
if (onMobileYouTube) skipButtonControlBar.setShowKeybindHint(false);
|
||||
|
||||
if (!Config.config.highlightCategoryUpdate) {
|
||||
new Tooltip({
|
||||
@@ -1259,6 +1252,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||
//send out the message saying that a sponsor message was skipped
|
||||
if (!Config.config.dontShowNotice || !autoSkip) {
|
||||
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime);
|
||||
if (onMobileYouTube) newSkipNotice.setShowKeybindHint(false);
|
||||
skipNotices.push(newSkipNotice);
|
||||
|
||||
activeSkipKeybindElement?.setShowKeybindHint(false);
|
||||
@@ -1346,27 +1340,6 @@ function shouldSkip(segment: SponsorTime): boolean {
|
||||
(Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic"));
|
||||
}
|
||||
|
||||
function getControls(): HTMLElement | false {
|
||||
const controlsSelectors = [
|
||||
// YouTube
|
||||
".ytp-right-controls",
|
||||
// Mobile YouTube
|
||||
".player-controls-top",
|
||||
// Invidious/videojs video element's controls element
|
||||
".vjs-control-bar",
|
||||
];
|
||||
|
||||
for (const controlsSelector of controlsSelectors) {
|
||||
const controls = document.querySelectorAll(controlsSelector);
|
||||
|
||||
if (controls && controls.length > 0) {
|
||||
return <HTMLElement> controls[controls.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Creates any missing buttons on the YouTube player if possible. */
|
||||
async function createButtons(): Promise<void> {
|
||||
if (onMobileYouTube) return;
|
||||
@@ -1465,9 +1438,10 @@ function getRealCurrentTime(): number {
|
||||
}
|
||||
|
||||
function startOrEndTimingNewSegment() {
|
||||
const roundedTime = Math.round((getRealCurrentTime() + Number.EPSILON) * 1000) / 1000;
|
||||
if (!isSegmentCreationInProgress()) {
|
||||
sponsorTimesSubmitting.push({
|
||||
segment: [getRealCurrentTime()],
|
||||
segment: [roundedTime],
|
||||
UUID: null,
|
||||
category: Config.config.defaultCategory,
|
||||
actionType: ActionType.Skip,
|
||||
@@ -1477,7 +1451,7 @@ function startOrEndTimingNewSegment() {
|
||||
// Finish creating the new segment
|
||||
const existingSegment = getIncompleteSegment();
|
||||
const existingTime = existingSegment.segment[0];
|
||||
const currentTime = getRealCurrentTime();
|
||||
const currentTime = roundedTime;
|
||||
|
||||
// Swap timestamps if the user put the segment end before the start
|
||||
existingSegment.segment = [Math.min(existingTime, currentTime), Math.max(existingTime, currentTime)];
|
||||
|
||||
@@ -210,7 +210,9 @@ class PreviewBar {
|
||||
bar.style.position = "absolute";
|
||||
const duration = segment[1] - segment[0];
|
||||
if (segment[1] - segment[0] > 0) bar.style.width = `calc(${this.timeToPercentage(segment[1] - segment[0])}${this.chapterFilter(barSegment) ? ' - 2px' : ''})`;
|
||||
bar.style.left = this.timeToPercentage(Math.min(this.videoDuration - Math.max(0, duration), segment[0]));
|
||||
|
||||
const time = segment[1] ? Math.min(this.videoDuration - Math.max(0, duration), segment[0]) : segment[0];
|
||||
bar.style.left = this.timeToPercentage(time);
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ const utils = new Utils();
|
||||
|
||||
export interface SkipButtonControlBarProps {
|
||||
skip: (segment: SponsorTime) => void;
|
||||
onMobileYouTube: boolean;
|
||||
}
|
||||
|
||||
export class SkipButtonControlBar {
|
||||
@@ -18,18 +19,31 @@ export class SkipButtonControlBar {
|
||||
segment: SponsorTime;
|
||||
|
||||
showKeybindHint = true;
|
||||
onMobileYouTube: boolean;
|
||||
|
||||
enabled = false;
|
||||
|
||||
timeout: NodeJS.Timeout;
|
||||
duration = 0;
|
||||
|
||||
skip: (segment: SponsorTime) => void;
|
||||
|
||||
// Used if on mobile page
|
||||
hideButton: () => void;
|
||||
showButton: () => void;
|
||||
|
||||
// Used by mobile only for swiping away
|
||||
left = 0;
|
||||
swipeStart = 0;
|
||||
|
||||
constructor(props: SkipButtonControlBarProps) {
|
||||
this.skip = props.skip;
|
||||
this.onMobileYouTube = props.onMobileYouTube;
|
||||
|
||||
this.container = document.createElement("div");
|
||||
this.container.classList.add("skipButtonControlBarContainer");
|
||||
this.container.classList.add("hidden");
|
||||
if (this.onMobileYouTube) this.container.classList.add("mobile");
|
||||
|
||||
this.skipIcon = document.createElement("img");
|
||||
this.skipIcon.src = chrome.runtime.getURL("icons/skipIcon.svg");
|
||||
@@ -43,6 +57,11 @@ export class SkipButtonControlBar {
|
||||
this.container.addEventListener("click", () => this.toggleSkip());
|
||||
this.container.addEventListener("mouseenter", () => this.stopTimer());
|
||||
this.container.addEventListener("mouseleave", () => this.startTimer());
|
||||
if (this.onMobileYouTube) {
|
||||
this.container.addEventListener("touchstart", (e) => this.handleTouchStart(e));
|
||||
this.container.addEventListener("touchmove", (e) => this.handleTouchMove(e));
|
||||
this.container.addEventListener("touchend", () => this.handleTouchEnd());
|
||||
}
|
||||
}
|
||||
|
||||
getElement(): HTMLElement {
|
||||
@@ -50,21 +69,38 @@ export class SkipButtonControlBar {
|
||||
}
|
||||
|
||||
attachToPage(): void {
|
||||
const leftControlsContainer = document.querySelector(".ytp-left-controls");
|
||||
const mountingContainer = this.getMountingContainer();
|
||||
this.chapterText = document.querySelector(".ytp-chapter-container");
|
||||
|
||||
if (leftControlsContainer && !leftControlsContainer.contains(this.container)) {
|
||||
leftControlsContainer.insertBefore(this.container, this.chapterText);
|
||||
|
||||
if (Config.config.autoHideInfoButton) {
|
||||
utils.setupAutoHideAnimation(this.skipIcon, leftControlsContainer, false, false);
|
||||
if (mountingContainer && !mountingContainer.contains(this.container)) {
|
||||
if (this.onMobileYouTube) {
|
||||
mountingContainer.appendChild(this.container);
|
||||
} else {
|
||||
mountingContainer.insertBefore(this.container, this.chapterText);
|
||||
}
|
||||
|
||||
if (Config.config.autoHideInfoButton && !this.onMobileYouTube) {
|
||||
utils.setupAutoHideAnimation(this.skipIcon, mountingContainer, false, false);
|
||||
} else {
|
||||
const { hide, show } = utils.setupCustomHideAnimation(this.skipIcon, mountingContainer, false, false);
|
||||
this.hideButton = hide;
|
||||
this.showButton = show;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getMountingContainer(): HTMLElement {
|
||||
if (!this.onMobileYouTube) {
|
||||
return document.querySelector(".ytp-left-controls");
|
||||
} else {
|
||||
return document.getElementById("player-container-id");
|
||||
}
|
||||
}
|
||||
|
||||
enable(segment: SponsorTime, duration?: number): void {
|
||||
if (duration) this.duration = duration;
|
||||
this.segment = segment;
|
||||
this.enabled = true;
|
||||
|
||||
this.refreshText();
|
||||
this.textContainer?.classList?.remove("hidden");
|
||||
@@ -103,6 +139,8 @@ export class SkipButtonControlBar {
|
||||
|
||||
this.chapterText?.classList?.remove("hidden");
|
||||
this.getChapterPrefix()?.classList?.remove("hidden");
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
toggleSkip(): void {
|
||||
@@ -116,12 +154,28 @@ export class SkipButtonControlBar {
|
||||
return;
|
||||
}
|
||||
|
||||
this.container.classList.add("textDisabled");
|
||||
this.textContainer?.classList?.add("hidden");
|
||||
this.chapterText?.classList?.remove("hidden");
|
||||
|
||||
this.getChapterPrefix()?.classList?.add("hidden");
|
||||
|
||||
utils.enableAutoHideAnimation(this.skipIcon);
|
||||
if (this.onMobileYouTube) {
|
||||
this.hideButton();
|
||||
}
|
||||
}
|
||||
|
||||
updateMobileControls(): void {
|
||||
const overlay = document.getElementById("player-control-overlay");
|
||||
|
||||
if (overlay && this.enabled) {
|
||||
if (overlay?.classList?.contains("pointer-events-off")) {
|
||||
this.hideButton();
|
||||
} else {
|
||||
this.showButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getTitle(): string {
|
||||
@@ -131,5 +185,37 @@ export class SkipButtonControlBar {
|
||||
private getChapterPrefix(): HTMLElement {
|
||||
return document.querySelector(".ytp-chapter-title-prefix");
|
||||
}
|
||||
|
||||
// Swiping away on mobile
|
||||
private handleTouchStart(event: TouchEvent): void {
|
||||
this.swipeStart = event.touches[0].clientX;
|
||||
}
|
||||
|
||||
// Swiping away on mobile
|
||||
private handleTouchMove(event: TouchEvent): void {
|
||||
const distanceMoved = this.swipeStart - event.touches[0].clientX;
|
||||
this.left = Math.min(-distanceMoved, 0);
|
||||
|
||||
this.updateLeftStyle();
|
||||
}
|
||||
|
||||
// Swiping away on mobile
|
||||
private handleTouchEnd(): void {
|
||||
if (this.left < -this.container.offsetWidth / 2) {
|
||||
this.disableText();
|
||||
|
||||
// Don't let animation play
|
||||
this.skipIcon.style.display = "none";
|
||||
setTimeout(() => this.skipIcon.style.removeProperty("display"), 200);
|
||||
}
|
||||
|
||||
this.left = 0;
|
||||
this.updateLeftStyle();
|
||||
}
|
||||
|
||||
// Swiping away on mobile
|
||||
private updateLeftStyle() {
|
||||
this.container.style.left = this.left + "px";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ interface IsInfoFoundMessage {
|
||||
|
||||
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage);
|
||||
|
||||
interface IsInfoFoundMessageResponse {
|
||||
export interface IsInfoFoundMessageResponse {
|
||||
found: boolean;
|
||||
sponsorTimes: SponsorTime[];
|
||||
onMobileYouTube: boolean;
|
||||
}
|
||||
|
||||
interface GetVideoIdResponse {
|
||||
|
||||
@@ -2,7 +2,7 @@ import Config from "./config";
|
||||
|
||||
import Utils from "./utils";
|
||||
import { SponsorTime, SponsorHideType, CategoryActionType } from "./types";
|
||||
import { Message, MessageResponse } from "./messageTypes";
|
||||
import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes";
|
||||
import { showDonationLink } from "./utils/configUtils";
|
||||
import { getCategoryActionType } from "./utils/categoryUtils";
|
||||
const utils = new Utils();
|
||||
@@ -278,7 +278,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
}, (tabs) => onTabs(tabs, updating));
|
||||
}
|
||||
|
||||
function infoFound(request: { found: boolean, sponsorTimes: SponsorTime[] }) {
|
||||
function infoFound(request: IsInfoFoundMessageResponse) {
|
||||
if (chrome.runtime.lastError) {
|
||||
//This page doesn't have the injected content script, or at least not yet
|
||||
displayNoVideo();
|
||||
@@ -289,6 +289,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
if (request != undefined) {
|
||||
//remove loading text
|
||||
PageElements.mainControls.style.display = "flex";
|
||||
if (request.onMobileYouTube) PageElements.mainControls.classList.add("hidden");
|
||||
PageElements.whitelistButton.classList.remove("hidden");
|
||||
PageElements.loadingIndicator.style.display = "none";
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface RectangleTooltipProps {
|
||||
maxHeight?: string,
|
||||
maxWidth?: string,
|
||||
backgroundColor?: string,
|
||||
fontSize?: string,
|
||||
buttonFunction?: () => void;
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ export class RectangleTooltip {
|
||||
props.maxWidth ??= "300px";
|
||||
props.backgroundColor ??= "rgba(28, 28, 28, 0.7)";
|
||||
this.text = props.text;
|
||||
props.fontSize ??= "10px";
|
||||
|
||||
this.container = document.createElement('div');
|
||||
props.htmlId ??= props.text;
|
||||
@@ -51,7 +53,8 @@ export class RectangleTooltip {
|
||||
left: props.leftOffset,
|
||||
maxHeight: props.maxHeight,
|
||||
maxWidth: props.maxWidth,
|
||||
backgroundColor: props.backgroundColor}}
|
||||
backgroundColor: props.backgroundColor,
|
||||
fontSize: props.fontSize}}
|
||||
className="sponsorBlockRectangleTooltip" >
|
||||
<div>
|
||||
<img className="sponsorSkipLogo sponsorSkipObject"
|
||||
|
||||
40
src/utils.ts
40
src/utils.ts
@@ -183,7 +183,7 @@ export default class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
setupAutoHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): void {
|
||||
setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void, show: () => void } {
|
||||
if (enabled) element.classList.add("autoHiding");
|
||||
element.classList.add("hidden");
|
||||
element.classList.add("animationDone");
|
||||
@@ -191,22 +191,30 @@ export default class Utils {
|
||||
|
||||
let mouseEntered = false;
|
||||
|
||||
container.addEventListener("mouseenter", () => {
|
||||
mouseEntered = true;
|
||||
element.classList.remove("animationDone");
|
||||
|
||||
// Wait for next event loop
|
||||
setTimeout(() => {
|
||||
if (mouseEntered) element.classList.remove("hidden")
|
||||
}, 10);
|
||||
});
|
||||
|
||||
container.addEventListener("mouseleave", () => {
|
||||
mouseEntered = false;
|
||||
if (element.classList.contains("autoHiding")) {
|
||||
element.classList.add("hidden");
|
||||
return {
|
||||
hide: () => {
|
||||
mouseEntered = false;
|
||||
if (element.classList.contains("autoHiding")) {
|
||||
element.classList.add("hidden");
|
||||
}
|
||||
},
|
||||
show: () => {
|
||||
mouseEntered = true;
|
||||
element.classList.remove("animationDone");
|
||||
|
||||
// Wait for next event loop
|
||||
setTimeout(() => {
|
||||
if (mouseEntered) element.classList.remove("hidden")
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
enableAutoHideAnimation(element: Element): void {
|
||||
|
||||
20
src/utils/pageUtils.ts
Normal file
20
src/utils/pageUtils.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export function getControls(): HTMLElement | false {
|
||||
const controlsSelectors = [
|
||||
// YouTube
|
||||
".ytp-right-controls",
|
||||
// Mobile YouTube
|
||||
".player-controls-top",
|
||||
// Invidious/videojs video element's controls element
|
||||
".vjs-control-bar",
|
||||
];
|
||||
|
||||
for (const controlsSelector of controlsSelectors) {
|
||||
const controls = document.querySelectorAll(controlsSelector);
|
||||
|
||||
if (controls && controls.length > 0) {
|
||||
return <HTMLElement> controls[controls.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user