Add skip keybind

https://github.com/ajayyy/SponsorBlock/issues/299
This commit is contained in:
Ajay Ramachandran
2021-01-17 14:25:45 -05:00
parent ff0dc6e570
commit e269b1aec6
8 changed files with 123 additions and 51 deletions

View File

@@ -232,6 +232,9 @@
"message": "If you still don't like it, hit the never show button.",
"description": "The second line of the message displayed after the notice was upgraded."
},
"setSkipShortcut": {
"message": "Set key for skipping a segment"
},
"setStartSponsorShortcut": {
"message": "Set key for start segment keybind"
},

View File

@@ -84,6 +84,27 @@
<br/>
<br/>
<div option-type="keybind-change" sync-option="skipKeybind">
<div class="option-button trigger-button">
__MSG_setSkipShortcut__
</div>
<div class="option-hidden-section hidden">
<br/>
<div class="medium-description keybind-status">
__MSG_keybindDescription__
</div>
<span class="medium-description bold keybind-status-key">
</span>
</div>
</div>
<br/>
<br/>
<div option-type="keybind-change" sync-option="startSponsorKeybind">
<div class="option-button trigger-button">
__MSG_setStartSponsorShortcut__

View File

@@ -178,11 +178,18 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
})
}
removeFadeAnimation(): void {
//remove the fade out class if it exists
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice.classList.remove("sponsorSkipNoticeFadeOut");
notice.style.animation = "none";
}
pauseCountdown(): void {
if (!this.props.timed) return;
//remove setInterval
clearInterval(this.countdownInterval);
if (this.countdownInterval) clearInterval(this.countdownInterval);
this.countdownInterval = null;
//reset countdown and inform the user
@@ -191,10 +198,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
countdownText: this.state.countdownManuallyPaused ? chrome.i18n.getMessage("manualPaused") : chrome.i18n.getMessage("paused")
});
//remove the fade out class if it exists
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice.classList.remove("sponsorSkipNoticeFadeOut");
notice.style.animation = "none";
this.removeFadeAnimation();
}
startCountdown(): void {
@@ -208,16 +212,25 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
countdownText: null
});
this.setupInterval();
}
setupInterval(): void {
if (this.countdownInterval) clearInterval(this.countdownInterval);
this.countdownInterval = setInterval(this.countdown.bind(this), 1000);
}
resetCountdown(): void {
if (!this.props.timed) return;
this.setupInterval();
this.setState({
countdownTime: this.state.maxCountdownTime(),
countdownText: null
});
this.removeFadeAnimation();
}
/**

View File

@@ -5,7 +5,7 @@ import { ContentContainer, SponsorHideType, SponsorTime } from "../types";
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
enum SkipNoticeAction {
export enum SkipNoticeAction {
None,
Upvote,
Downvote,
@@ -24,23 +24,23 @@ export interface SkipNoticeProps {
}
export interface SkipNoticeState {
noticeTitle: string;
noticeTitle?: string;
messages: string[];
messageOnClick: (event: React.MouseEvent) => unknown;
messages?: string[];
messageOnClick?: (event: React.MouseEvent) => unknown;
countdownTime: number;
maxCountdownTime: () => number;
countdownText: string;
countdownTime?: number;
maxCountdownTime?: () => number;
countdownText?: string;
unskipText: string;
unskipCallback: (index: number) => void;
unskipText?: string;
unskipCallback?: (index: number) => void;
downvoting: boolean;
choosingCategory: boolean;
thanksForVotingText: string; //null until the voting buttons should be hidden
downvoting?: boolean;
choosingCategory?: boolean;
thanksForVotingText?: string; //null until the voting buttons should be hidden
actionState: SkipNoticeAction;
actionState?: SkipNoticeAction;
}
class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeState> {
@@ -196,7 +196,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
style={{marginLeft: "4px"}}
onClick={() => this.prepAction(SkipNoticeAction.Unskip)}>
{this.state.unskipText}
{this.state.unskipText + " (" + Config.config.skipKeybind + ")"}
</button>
</td>
@@ -456,21 +456,23 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
reskip(index: number): void {
this.contentContainer().reskipSponsorTime(this.segments[index]);
//reset countdown
this.setState({
const newState: SkipNoticeState = {
unskipText: chrome.i18n.getMessage("unskip"),
unskipCallback: this.unskip.bind(this),
maxCountdownTime: () => 4,
countdownTime: 4
});
};
// See if the title should be changed
if (!this.autoSkip) {
this.setState({
noticeTitle: chrome.i18n.getMessage("noticeTitle")
});
newState.noticeTitle = chrome.i18n.getMessage("noticeTitle");
}
//reset countdown
this.setState(newState, () => {
this.noticeRef.current.resetCountdown();
});
}
afterVote(segment: SponsorTime, type: number, category: string): void {

View File

@@ -10,6 +10,7 @@ interface SBConfig {
defaultCategory: string,
whitelistedChannels: string[],
forceChannelCheck: boolean,
skipKeybind: string,
startSponsorKeybind: string,
submitKeybind: string,
minutesSaved: number,
@@ -142,6 +143,7 @@ const Config: SBObject = {
defaultCategory: "chooseACategory",
whitelistedChannels: [],
forceChannelCheck: false,
skipKeybind: "Enter",
startSponsorKeybind: ";",
submitKeybind: "'",
minutesSaved: 0,

View File

@@ -23,6 +23,8 @@ let sponsorDataFound = false;
let sponsorTimes: SponsorTime[] = null;
//what video id are these sponsors for
let sponsorVideoID: VideoID = null;
// List of open skip notices
const skipNotices: SkipNotice[] = [];
// JSON video info
let videoInfo: VideoInfo = null;
@@ -35,11 +37,13 @@ let channelID: string;
let currentSkipSchedule: NodeJS.Timeout = null;
let seekListenerSetUp = false
/** @type {Array[boolean]} Has the sponsor been skipped */
/** Has the sponsor been skipped */
let sponsorSkipped: boolean[] = [];
//the video
let video: HTMLVideoElement;
// List of videos that have had event listeners added to them
const videoRootsWithEventListeners: HTMLDivElement[] = [];
let onInvidious;
let onMobileYouTube;
@@ -99,6 +103,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({
unskipSponsorTime,
sponsorTimes,
sponsorTimesSubmitting,
skipNotices,
v: video,
sponsorVideoID,
reskipSponsorTime,
@@ -197,28 +202,6 @@ if (!Config.configListeners.includes(contentConfigUpdateListener)) {
Config.configListeners.push(contentConfigUpdateListener);
}
//check for hotkey pressed
document.onkeydown = function(e: KeyboardEvent){
const key = e.key;
const video = document.getElementById("movie_player");
const startSponsorKey = Config.config.startSponsorKeybind;
const submitKey = Config.config.submitKeybind;
//is the video in focus, otherwise they could be typing a comment
if (document.activeElement === video) {
if(key == startSponsorKey){
//semicolon
startSponsorClicked();
} else if (key == submitKey) {
//single quote
submitSponsorTimes();
}
}
}
function resetValues() {
lastCheckTime = 0;
lastCheckVideoTime = -1;
@@ -512,6 +495,8 @@ async function sponsorsLookup(id: string) {
return;
}
addHotkeyListener();
if (!durationListenerSetUp) {
durationListenerSetUp = true;
@@ -996,7 +981,7 @@ function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: S
if (openNotice) {
//send out the message saying that a sponsor message was skipped
if (!Config.config.dontShowNotice || !autoSkip) {
new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer);
skipNotices.push(new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer));
}
}
@@ -1542,6 +1527,41 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
return sponsorTimesMessage;
}
function addHotkeyListener(): boolean {
const videoRoot = document.getElementById("movie_player") as HTMLDivElement;
if (!videoRootsWithEventListeners.includes(videoRoot)) {
videoRoot.addEventListener("keydown", hotkeyListener);
videoRootsWithEventListeners.push(videoRoot);
return true;
}
return false;
}
function hotkeyListener(e: KeyboardEvent): void {
const key = e.key;
const skipKey = Config.config.skipKeybind;
const startSponsorKey = Config.config.startSponsorKeybind;
const submitKey = Config.config.submitKeybind;
switch (key) {
case skipKey:
if (skipNotices.length > 0) {
const latestSkipNotice = skipNotices[skipNotices.length - 1];
latestSkipNotice.toggleSkip.call(latestSkipNotice);
}
break;
case startSponsorKey:
startSponsorClicked();
break;
case submitKey:
submitSponsorTimes();
break;
}
}
/**
* Is this an unlisted YouTube video.
* Assumes that the the privacy info is available.

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import SkipNoticeComponent from "../components/SkipNoticeComponent";
import SkipNoticeComponent, { SkipNoticeAction } from "../components/SkipNoticeComponent";
import { SponsorTime, ContentContainer } from "../types";
class SkipNotice {
@@ -15,6 +15,8 @@ class SkipNotice {
skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer) {
this.skipNoticeRef = React.createRef();
this.segments = segments;
this.autoSkip = autoSkip;
this.contentContainer = contentContainer;
@@ -67,6 +69,13 @@ class SkipNotice {
ReactDOM.unmountComponentAtNode(this.noticeElement);
this.noticeElement.remove();
const skipNotices = this.contentContainer().skipNotices;
skipNotices.splice(skipNotices.indexOf(this), 1);
}
toggleSkip(): void {
this.skipNoticeRef.current.prepAction(SkipNoticeAction.Unskip);
}
}

View File

@@ -1,5 +1,6 @@
import SubmissionNotice from "./render/SubmissionNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent";
import SkipNotice from "./render/SkipNotice";
export interface ContentContainer {
(): {
@@ -8,6 +9,7 @@ export interface ContentContainer {
unskipSponsorTime: (segment: SponsorTime) => void,
sponsorTimes: SponsorTime[],
sponsorTimesSubmitting: SponsorTime[],
skipNotices: SkipNotice[],
v: HTMLVideoElement,
sponsorVideoID,
reskipSponsorTime: (segment: SponsorTime) => void,