diff options
author | Ajay Ramachandran <[email protected]> | 2021-01-17 14:25:45 -0500 |
---|---|---|
committer | Ajay Ramachandran <[email protected]> | 2021-01-17 14:25:45 -0500 |
commit | e269b1aec605ae5afb55572cb9a0177529862209 (patch) | |
tree | e96855685ea6c0b433d7045e0ce2d1d49fa71f4f | |
parent | ff0dc6e5706ffae19502b192061189c6e0c2199f (diff) | |
download | SponsorBlock-e269b1aec605ae5afb55572cb9a0177529862209.tar.gz SponsorBlock-e269b1aec605ae5afb55572cb9a0177529862209.zip |
Add skip keybind
https://github.com/ajayyy/SponsorBlock/issues/299
-rw-r--r-- | public/_locales/en/messages.json | 3 | ||||
-rw-r--r-- | public/options/options.html | 21 | ||||
-rw-r--r-- | src/components/NoticeComponent.tsx | 23 | ||||
-rw-r--r-- | src/components/SkipNoticeComponent.tsx | 44 | ||||
-rw-r--r-- | src/config.ts | 2 | ||||
-rw-r--r-- | src/content.ts | 68 | ||||
-rw-r--r-- | src/render/SkipNotice.tsx | 11 | ||||
-rw-r--r-- | src/types.ts | 2 |
8 files changed, 123 insertions, 51 deletions
diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 0a761cf5..d631d2cc 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -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" }, diff --git a/public/options/options.html b/public/options/options.html index 68c28cbd..5907c07b 100644 --- a/public/options/options.html +++ b/public/options/options.html @@ -83,6 +83,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"> diff --git a/src/components/NoticeComponent.tsx b/src/components/NoticeComponent.tsx index d699c204..da25aa65 100644 --- a/src/components/NoticeComponent.tsx +++ b/src/components/NoticeComponent.tsx @@ -177,12 +177,19 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> { countdownTime }) } + + 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(); } /** diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 069472b2..f6b318e6 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -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 { diff --git a/src/config.ts b/src/config.ts index 5290fb61..5723db12 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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, diff --git a/src/content.ts b/src/content.ts index ab3ece52..9e9332e6 100644 --- a/src/content.ts +++ b/src/content.ts @@ -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. diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index ac66cae4..526d2d1c 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -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); } } diff --git a/src/types.ts b/src/types.ts index 1d79e015..dd870f37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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, |