diff options
author | Ajay Ramachandran <[email protected]> | 2021-01-17 14:46:27 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2021-01-17 14:46:27 -0500 |
commit | d2f377c8d745c5c8f4e2ede2ea7b930cb4d1c670 (patch) | |
tree | 4bfe3793a7d0fd2ac53859a53e2b8c4d1b14ea7f | |
parent | e32a251ef374c1dbffb9f36b0fa0349898089bec (diff) | |
parent | 551355d21a0e45552be7e9c2bfab2b918a298943 (diff) | |
download | SponsorBlock-d2f377c8d745c5c8f4e2ede2ea7b930cb4d1c670.tar.gz SponsorBlock-d2f377c8d745c5c8f4e2ede2ea7b930cb4d1c670.zip |
Merge pull request #622 from ajayyy/fix-unsubmitted-list
Improvements
-rw-r--r-- | manifest/manifest.json | 1 | ||||
-rw-r--r-- | public/_locales/en/messages.json | 12 | ||||
-rw-r--r-- | public/options/options.html | 38 | ||||
-rw-r--r-- | src/background.ts | 10 | ||||
-rw-r--r-- | src/components/NoticeComponent.tsx | 45 | ||||
-rw-r--r-- | src/components/SkipNoticeComponent.tsx | 52 | ||||
-rw-r--r-- | src/config.ts | 16 | ||||
-rw-r--r-- | src/content.ts | 88 | ||||
-rw-r--r-- | src/render/SkipNotice.tsx | 11 | ||||
-rw-r--r-- | src/types.ts | 48 |
10 files changed, 164 insertions, 157 deletions
diff --git a/manifest/manifest.json b/manifest/manifest.json index 7f60599f..c25f2275 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -46,7 +46,6 @@ ], "permissions": [ "storage", - "notifications", "https://sponsor.ajay.app/*" ], "optional_permissions": [ diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 5f92d260..d631d2cc 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -97,9 +97,6 @@ "wantToSubmit": { "message": "Do you want to submit for video id" }, - "leftTimes": { - "message": "You seem to have left some segments unsubmitted. Go back to that page to submit them (they are not deleted)." - }, "clearTimes": { "message": "Clear Segments" }, @@ -235,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" }, @@ -631,12 +631,6 @@ "categoryUpdate2": { "message": "Open the options to skip intros, outros, merch, etc." }, - "unsubmittedWarning": { - "message": "Unsubmitted Segments Notification" - }, - "unsubmittedWarningDescription": { - "message": "Send a notification when you leave a video with segments that are not uploaded" - }, "help": { "message": "Help" } diff --git a/public/options/options.html b/public/options/options.html index 04620a9e..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"> @@ -152,23 +173,6 @@ <br/> <br/> - <div option-type="toggle" sync-option="unsubmittedWarning"> - <label class="switch-container" label-name="__MSG_unsubmittedWarning__"> - <label class="switch"> - <input type="checkbox" checked> - <span class="slider round"></span> - </label> - </label> - - <br/> - <br/> - - <div class="small-description">__MSG_unsubmittedWarningDescription__</div> - </div> - - <br/> - <br/> - <div option-type="toggle" sync-option="forceChannelCheck"> <label class="switch-container" label-name="__MSG_forceChannelCheck__"> <label class="switch"> diff --git a/src/background.ts b/src/background.ts index e9643667..ea011576 100644 --- a/src/background.ts +++ b/src/background.ts @@ -52,16 +52,6 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) { //this allows the callback to be called later return true; - case "alertPrevious": - if (Config.config.unsubmittedWarning) { - chrome.notifications.create("stillThere" + Math.random(), { - type: "basic", - title: chrome.i18n.getMessage("wantToSubmit") + " " + request.previousVideoID + "?", - message: chrome.i18n.getMessage("leftTimes"), - iconUrl: "./icons/LogoSponsorBlocker256px.png" - }); - } - break; case "registerContentScript": registerFirefoxContentScript(request); return false; diff --git a/src/components/NoticeComponent.tsx b/src/components/NoticeComponent.tsx index 9fff3be4..3867fa0a 100644 --- a/src/components/NoticeComponent.tsx +++ b/src/components/NoticeComponent.tsx @@ -8,6 +8,8 @@ export interface NoticeProps { timed?: boolean, idSuffix?: string, + videoSpeed?: () => number, + fadeIn?: boolean, // Callback for when this is closed @@ -19,7 +21,7 @@ export interface NoticeProps { export interface NoticeState { noticeTitle: string, - maxCountdownTime?: () => number, + maxCountdownTime: () => number, countdownTime: number, countdownText: string, @@ -28,6 +30,8 @@ export interface NoticeState { class NoticeComponent extends React.Component<NoticeProps, NoticeState> { countdownInterval: NodeJS.Timeout; + intervalVideoSpeed: number; + idSuffix: string; amountOfPreviousNotices: number; @@ -71,7 +75,9 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> { return ( <table id={"sponsorSkipNotice" + this.idSuffix} - className={"sponsorSkipObject sponsorSkipNotice" + (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")} + className={"sponsorSkipObject sponsorSkipNotice" + + (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "") + + (this.amountOfPreviousNotices > 0 ? " secondSkipNotice" : "")} style={noticeStyle} onMouseEnter={() => this.timerMouseEnter()} onMouseLeave={() => this.timerMouseLeave()}> @@ -152,7 +158,11 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> { countdown(): void { if (!this.props.timed) return; - const countdownTime = this.state.countdownTime - 1; + const countdownTime = Math.min(this.state.countdownTime - 1, this.state.maxCountdownTime()); + + if (this.props.videoSpeed && this.intervalVideoSpeed != this.props.videoSpeed()) { + this.setupInterval(); + } if (countdownTime <= 0) { //remove this from setInterval @@ -175,12 +185,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 @@ -189,10 +206,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 { @@ -206,16 +220,29 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> { countdownText: null }); - this.countdownInterval = setInterval(this.countdown.bind(this), 1000); + this.setupInterval(); + } + + setupInterval(): void { + if (this.countdownInterval) clearInterval(this.countdownInterval); + + const intervalDuration = this.props.videoSpeed ? 1000 / this.props.videoSpeed() : 1000; + this.countdownInterval = setInterval(this.countdown.bind(this), intervalDuration); + + if (this.props.videoSpeed) this.intervalVideoSpeed = this.props.videoSpeed(); } 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 aa75a587..b4f31932 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> { @@ -91,13 +91,6 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta } this.idSuffix += this.amountOfPreviousNotices; - if (this.amountOfPreviousNotices > 0) { - //another notice exists - - const previousNotice = document.getElementsByClassName("sponsorSkipNotice")[0]; - previousNotice.classList.add("secondSkipNotice") - } - // Setup state this.state = { noticeTitle, @@ -148,6 +141,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta fadeIn={true} timed={true} maxCountdownTime={this.state.maxCountdownTime} + videoSpeed={() => this.contentContainer().v?.playbackRate} ref={this.noticeRef} closeListener={() => this.closeListener()}> @@ -203,7 +197,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> @@ -463,21 +457,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 2a40dbf6..5723db12 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,5 @@ import * as CompileConfig from "../config.json"; -import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject } from "./types"; +import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes } from "./types"; import Utils from "./utils"; const utils = new Utils(); @@ -10,6 +10,7 @@ interface SBConfig { defaultCategory: string, whitelistedChannels: string[], forceChannelCheck: boolean, + skipKeybind: string, startSponsorKeybind: string, submitKeybind: string, minutesSaved: number, @@ -17,7 +18,6 @@ interface SBConfig { sponsorTimesContributed: number, submissionCountSinceCategories: number, // New count used to show the "Read The Guidelines!!" message showTimeWithSkips: boolean, - unsubmittedWarning: boolean, disableSkipping: boolean, trackViewCount: boolean, dontShowNotice: boolean, @@ -65,7 +65,7 @@ export interface SBObject { config: SBConfig; // Functions - encodeStoredItem<T>(data: T): T | Array<any>; + encodeStoredItem<T>(data: T): T | UnencodedSegmentTimes; convertJSON(): void; } @@ -143,6 +143,7 @@ const Config: SBObject = { defaultCategory: "chooseACategory", whitelistedChannels: [], forceChannelCheck: false, + skipKeybind: "Enter", startSponsorKeybind: ";", submitKeybind: "'", minutesSaved: 0, @@ -150,7 +151,6 @@ const Config: SBObject = { sponsorTimesContributed: 0, submissionCountSinceCategories: 0, showTimeWithSkips: true, - unsubmittedWarning: true, disableSkipping: false, trackViewCount: true, dontShowNotice: false, @@ -247,10 +247,10 @@ const Config: SBObject = { * * @param data */ -function encodeStoredItem<T>(data: T): T | Array<any> { +function encodeStoredItem<T>(data: T): T | UnencodedSegmentTimes { // if data is SBMap convert to json for storing if(!(data instanceof SBMap)) return data; - return Array.from(data.entries()); + return Array.from(data.entries()).filter((element) => element[1] === []); // Remove empty entries } /** @@ -265,7 +265,7 @@ function decodeStoredItem<T>(id: string, data: T): T | SBMap<string, SponsorTime if (Config.defaults[id] instanceof SBMap) { try { if (!Array.isArray(data)) return data; - return new SBMap(id, data); + return new SBMap(id, data as UnencodedSegmentTimes); } catch(e) { console.error("Failed to parse SBMap: " + id); } @@ -395,7 +395,7 @@ function migrateOldFormats(config: SBConfig) { // Migrate old "sponsorTimes" if (config["sponsorTimes"]) { - let jsonData: any = config["sponsorTimes"]; + let jsonData: unknown = config["sponsorTimes"]; // Check if data is stored in the old format for SBMap (a JSON string) if (typeof jsonData === "string") { diff --git a/src/content.ts b/src/content.ts index aba53f24..9e9332e6 100644 --- a/src/content.ts +++ b/src/content.ts @@ -19,11 +19,12 @@ utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); //was sponsor data found when doing SponsorsLookup let sponsorDataFound = false; -let previousVideoID: VideoID = null; //the actual sponsorTimes if loaded and UUIDs associated with them 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; @@ -36,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; @@ -100,6 +103,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({ unskipSponsorTime, sponsorTimes, sponsorTimesSubmitting, + skipNotices, v: video, sponsorVideoID, reskipSponsorTime, @@ -198,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; @@ -310,25 +292,6 @@ async function videoIDChange(id) { } } - //warn them if they had unsubmitted times - if (previousVideoID != null) { - //get the sponsor times from storage - const sponsorTimes = Config.config.segmentTimes.get(previousVideoID); - if (sponsorTimes != undefined && sponsorTimes.length > 0 && new URL(document.URL).host !== "music.youtube.com") { - //warn them that they have unsubmitted sponsor times - chrome.runtime.sendMessage({ - message: "alertPrevious", - previousVideoID: previousVideoID - }); - } - - //set the previous video id to the currentID - previousVideoID = id; - } else { - //set the previous id now, don't wait for chrome.storage.get - previousVideoID = id; - } - //close popup closeInfoMenu(); @@ -532,6 +495,8 @@ async function sponsorsLookup(id: string) { return; } + addHotkeyListener(); + if (!durationListenerSetUp) { durationListenerSetUp = true; @@ -1016,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)); } } @@ -1562,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 64afe352..dd870f37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,13 +1,15 @@ import SubmissionNotice from "./render/SubmissionNotice"; import SkipNoticeComponent from "./components/SkipNoticeComponent"; +import SkipNotice from "./render/SkipNotice"; -interface ContentContainer { +export interface ContentContainer { (): { vote: (type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) => void, dontShowNoticeAgain: () => void, unskipSponsorTime: (segment: SponsorTime) => void, sponsorTimes: SponsorTime[], sponsorTimesSubmitting: SponsorTime[], + skipNotices: SkipNotice[], v: HTMLVideoElement, sponsorVideoID, reskipSponsorTime: (segment: SponsorTime) => void, @@ -22,34 +24,34 @@ interface ContentContainer { } } -interface FetchResponse { +export interface FetchResponse { responseText: string, status: number, ok: boolean } -interface VideoDurationResponse { +export interface VideoDurationResponse { duration: number; } -enum CategorySkipOption { +export enum CategorySkipOption { ShowOverlay, ManualSkip, AutoSkip } -interface CategorySelection { +export interface CategorySelection { name: string; option: CategorySkipOption } -enum SponsorHideType { +export enum SponsorHideType { Visible = undefined, Downvoted = 1, MinimumDuration } -interface SponsorTime { +export interface SponsorTime { segment: number[]; UUID: string; @@ -58,13 +60,13 @@ interface SponsorTime { hidden?: SponsorHideType; } -interface PreviewBarOption { +export interface PreviewBarOption { color: string, opacity: string } -interface Registration { +export interface Registration { message: string, id: string, allFrames: boolean, @@ -73,12 +75,12 @@ interface Registration { matches: string[] } -interface BackgroundScriptContainer { +export interface BackgroundScriptContainer { registerFirefoxContentScript: (opts: Registration) => void, unregisterFirefoxContentScript: (id: string) => void } -interface VideoInfo { +export interface VideoInfo { responseContext: { serviceTrackingParams: Array<{service: string, params: Array<{key: string, value: string}>}>, webResponseContextExtensionData: { @@ -154,22 +156,8 @@ interface VideoInfo { messages: unknown; } -type VideoID = string; - -type StorageChangesObject = { [key: string]: chrome.storage.StorageChange }; - -export { - FetchResponse, - VideoDurationResponse, - ContentContainer, - CategorySelection, - CategorySkipOption, - SponsorTime, - VideoID, - SponsorHideType, - PreviewBarOption, - Registration, - BackgroundScriptContainer, - VideoInfo, - StorageChangesObject, -}; +export type VideoID = string; + +export type StorageChangesObject = { [key: string]: chrome.storage.StorageChange }; + +export type UnEncodedSegmentTimes = [string, SponsorTime[]][];
\ No newline at end of file |