From d36b4a54f3154fdcfbd2e5f059c401bdd6ceeee3 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 2 Jan 2022 23:35:24 -0500 Subject: [PATCH 01/13] Allow submitting as full video --- config.json.example | 4 ++-- public/_locales/en/messages.json | 4 ++++ src/components/SponsorTimeEditComponent.tsx | 12 +++++++++++- src/types.ts | 3 ++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index 7ff22bb7..0a587e3c 100644 --- a/config.json.example +++ b/config.json.example @@ -4,8 +4,8 @@ "serverAddressComment": "This specifies the default SponsorBlock server to connect to", "categoryList": ["sponsor", "selfpromo", "interaction", "poi_highlight", "intro", "outro", "preview", "filler", "music_offtopic"], "categorySupport": { - "sponsor": ["skip", "mute"], - "selfpromo": ["skip", "mute"], + "sponsor": ["skip", "mute", "full"], + "selfpromo": ["skip", "mute", "full"], "interaction": ["skip", "mute"], "intro": ["skip", "mute"], "outro": ["skip", "mute"], diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index c56b3275..c862ba00 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -302,6 +302,10 @@ "mute": { "message": "Mute" }, + "full": { + "message": "Full Video", + "description": "Used for the name of the option to label an entire video as sponsor or self promotion." + }, "skip_category": { "message": "Skip {0}?" }, diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 144f3c82..1a734cb5 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config"; -import { ActionType, ActionTypes, Category, CategoryActionType, ContentContainer, SponsorTime } from "../types"; +import { ActionType, Category, CategoryActionType, ContentContainer, SponsorTime } from "../types"; import Utils from "../utils"; import { getCategoryActionType } from "../utils/categoryUtils"; import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; @@ -100,11 +100,14 @@ class SponsorTimeEditComponent extends React.Component {utils.getFormattedTime(segment[0], true) + @@ -444,6 +448,12 @@ class SponsorTimeEditComponent extends React.Component Date: Wed, 5 Jan 2022 02:35:58 -0500 Subject: [PATCH 02/13] Add pill beside title for full video reports --- public/content.css | 12 ++++ src/components/CategoryPillComponent.tsx | 47 ++++++++++++++++ src/content.ts | 22 +++++++- src/render/CategoryPill.tsx | 71 ++++++++++++++++++++++++ src/utils.ts | 22 +------- src/utils/genericUtils.ts | 26 +++++++++ 6 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 src/components/CategoryPillComponent.tsx create mode 100644 src/render/CategoryPill.tsx create mode 100644 src/utils/genericUtils.ts diff --git a/public/content.css b/public/content.css index b9f19080..98a6b4f2 100644 --- a/public/content.css +++ b/public/content.css @@ -613,3 +613,15 @@ input::-webkit-inner-spin-button { line-height: 1.5em; } +.sponsorBlockCategoryPill { + border-radius: 25px; + padding-left: 8px; + padding-right: 8px; + margin-right: 3px; + color: white; +} + +.sponsorBlockCategoryPillTitleSection { + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx new file mode 100644 index 00000000..0f20c82f --- /dev/null +++ b/src/components/CategoryPillComponent.tsx @@ -0,0 +1,47 @@ +import * as React from "react"; +import Config from "../config"; +import { SponsorTime } from "../types"; + +export interface CategoryPillProps { + +} + +export interface CategoryPillState { + segment?: SponsorTime; + show: boolean; +} + +class CategoryPillComponent extends React.Component { + + constructor(props: CategoryPillProps) { + super(props); + + this.state = { + segment: null, + show: false + }; + } + + render(): React.ReactElement { + const style: React.CSSProperties = { + backgroundColor: Config.config.barTypes["preview-" + this.state.segment?.category]?.color, + display: this.state.show ? "flex" : "none" + } + + return ( + + + + + + {chrome.i18n.getMessage("category_" + this.state.segment?.category)} + + + + ); + } +} + +export default CategoryPillComponent; diff --git a/src/content.ts b/src/content.ts index 681ef10c..f3d589cc 100644 --- a/src/content.ts +++ b/src/content.ts @@ -18,6 +18,7 @@ import { SkipButtonControlBar } from "./js-components/skipButtonControlBar"; import { Tooltip } from "./render/Tooltip"; import { getStartTimeFromUrl } from "./utils/urlParser"; import { getControls } from "./utils/pageUtils"; +import { CategoryPill } from "./render/CategoryPill"; // Hack to get the CSS loaded on permission-based sites (Invidious) utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); @@ -75,9 +76,11 @@ let lastCheckVideoTime = -1; //is this channel whitelised from getting sponsors skipped let channelWhitelisted = false; -// create preview bar let previewBar: PreviewBar = null; +// Skip to highlight button let skipButtonControlBar: SkipButtonControlBar = null; +// For full video sponsors/selfpromo +let categoryPill: CategoryPill = null; /** Element containing the player controls on the YouTube player. */ let controls: HTMLElement | null = null; @@ -263,6 +266,7 @@ function resetValues() { } skipButtonControlBar?.disable(); + categoryPill?.setVisibility(false); } async function videoIDChange(id) { @@ -549,6 +553,7 @@ function refreshVideoAttachments() { setupVideoListeners(); setupSkipButtonControlBar(); + setupCategoryPill(); } } } @@ -637,6 +642,14 @@ function setupSkipButtonControlBar() { skipButtonControlBar.attachToPage(); } +function setupCategoryPill() { + if (!categoryPill) { + categoryPill = new CategoryPill(); + } + + categoryPill.attachToPage(); +} + async function sponsorsLookup(id: string, keepOldSubmissions = true) { if (!video) refreshVideoAttachments(); //there is still no video here @@ -672,7 +685,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) { 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], + actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute, ActionType.Full] : [ActionType.Skip, ActionType.Full], userAgent: `${chrome.runtime.id}`, ...extraRequestData }); @@ -864,6 +877,11 @@ function startSkipScheduleCheckingForStartSponsors() { } } + const fullVideoSegment = sponsorTimes.filter((time) => time.actionType === ActionType.Full)[0]; + if (fullVideoSegment) { + categoryPill?.setSegment(fullVideoSegment); + } + if (startingSegmentTime !== -1) { startSponsorSchedule(undefined, startingSegmentTime); } else { diff --git a/src/render/CategoryPill.tsx b/src/render/CategoryPill.tsx new file mode 100644 index 00000000..6c6695e2 --- /dev/null +++ b/src/render/CategoryPill.tsx @@ -0,0 +1,71 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import CategoryPillComponent, { CategoryPillState } from "../components/CategoryPillComponent"; +import { SponsorTime } from "../types"; +import { GenericUtils } from "../utils/genericUtils"; + +export class CategoryPill { + container: HTMLElement; + ref: React.RefObject; + + unsavedState: CategoryPillState; + + constructor() { + this.ref = React.createRef(); + } + + async attachToPage(): Promise { + // TODO: Mobile and invidious + const referenceNode = await GenericUtils.wait(() => document.querySelector(".ytd-video-primary-info-renderer.title") as HTMLElement); + + if (referenceNode && !referenceNode.contains(this.container)) { + this.container = document.createElement('span'); + this.container.id = "categoryPill"; + this.container.style.display = "relative"; + + referenceNode.prepend(this.container); + referenceNode.style.display = "flex"; + + ReactDOM.render( + , + this.container + ); + + if (this.unsavedState) { + this.ref.current?.setState(this.unsavedState); + this.unsavedState = null; + } + } + } + + close(): void { + ReactDOM.unmountComponentAtNode(this.container); + this.container.remove(); + } + + setVisibility(show: boolean): void { + const newState = { + show + }; + + if (this.ref.current) { + this.ref.current?.setState(newState); + } else { + this.unsavedState = newState; + } + } + + setSegment(segment: SponsorTime): void { + const newState = { + segment, + show: true + }; + + if (this.ref.current) { + this.ref.current?.setState(newState); + } else { + this.unsavedState = newState; + } + + } +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 661ca0bb..7cffa45a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import Config from "./config"; import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration } from "./types"; import * as CompileConfig from "../config.json"; +import { GenericUtils } from "./utils/genericUtils"; export default class Utils { @@ -23,27 +24,8 @@ export default class Utils { this.backgroundScriptContainer = backgroundScriptContainer; } - /** Function that can be used to wait for a condition before returning. */ async wait(condition: () => T | false, timeout = 5000, check = 100): Promise { - return await new Promise((resolve, reject) => { - setTimeout(() => { - clearInterval(interval); - reject("TIMEOUT"); - }, timeout); - - const intervalCheck = () => { - const result = condition(); - if (result !== false) { - resolve(result); - clearInterval(interval); - } - }; - - const interval = setInterval(intervalCheck, check); - - //run the check once first, this speeds it up a lot - intervalCheck(); - }); + return GenericUtils.wait(condition, timeout, check); } containsPermission(permissions: chrome.permissions.Permissions): Promise { diff --git a/src/utils/genericUtils.ts b/src/utils/genericUtils.ts new file mode 100644 index 00000000..32cf83f5 --- /dev/null +++ b/src/utils/genericUtils.ts @@ -0,0 +1,26 @@ +/** Function that can be used to wait for a condition before returning. */ +async function wait(condition: () => T | false, timeout = 5000, check = 100): Promise { + return await new Promise((resolve, reject) => { + setTimeout(() => { + clearInterval(interval); + reject("TIMEOUT"); + }, timeout); + + const intervalCheck = () => { + const result = condition(); + if (result) { + resolve(result); + clearInterval(interval); + } + }; + + const interval = setInterval(intervalCheck, check); + + //run the check once first, this speeds it up a lot + intervalCheck(); + }); +} + +export const GenericUtils = { + wait +} \ No newline at end of file From 388b9179ac788812546c5021f367ede55d4917fa Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 5 Jan 2022 02:39:13 -0500 Subject: [PATCH 03/13] Don't show full segments on preview bar --- src/content.ts | 4 +++- src/js-components/previewBar.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/content.ts b/src/content.ts index f3d589cc..93e3fb8c 100644 --- a/src/content.ts +++ b/src/content.ts @@ -981,6 +981,7 @@ function updatePreviewBar(): void { segment: segment.segment as [number, number], category: segment.category, unsubmitted: false, + actionType: segment.actionType, showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI }); }); @@ -991,11 +992,12 @@ function updatePreviewBar(): void { segment: segment.segment as [number, number], category: segment.category, unsubmitted: true, + actionType: segment.actionType, showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI }); }); - previewBar.set(previewBarSegments, video?.duration) + previewBar.set(previewBarSegments.filter((segment) => segment.actionType !== ActionType.Full), video?.duration) if (Config.config.showTimeWithSkips) { const skippedDuration = utils.getTimestampsDuration(previewBarSegments.map(({segment}) => segment)); diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 4e840646..d4d041a9 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -6,6 +6,7 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd 'use strict'; import Config from "../config"; +import { ActionType } from "../types"; import Utils from "../utils"; const utils = new Utils(); @@ -15,6 +16,7 @@ export interface PreviewBarSegment { segment: [number, number]; category: string; unsubmitted: boolean; + actionType: ActionType; showLarger: boolean; } From 040bce263855a1ff68cecb694f8da903e3df55ec Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 5 Jan 2022 15:13:42 -0500 Subject: [PATCH 04/13] Make category pill work on invidious and mobile youtube --- src/content.ts | 2 +- src/render/CategoryPill.tsx | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/content.ts b/src/content.ts index 93e3fb8c..23d3b9d3 100644 --- a/src/content.ts +++ b/src/content.ts @@ -647,7 +647,7 @@ function setupCategoryPill() { categoryPill = new CategoryPill(); } - categoryPill.attachToPage(); + categoryPill.attachToPage(onMobileYouTube); } async function sponsorsLookup(id: string, keepOldSubmissions = true) { diff --git a/src/render/CategoryPill.tsx b/src/render/CategoryPill.tsx index 6c6695e2..4fe846d0 100644 --- a/src/render/CategoryPill.tsx +++ b/src/render/CategoryPill.tsx @@ -9,15 +9,19 @@ export class CategoryPill { ref: React.RefObject; unsavedState: CategoryPillState; + + mutationObserver?: MutationObserver; constructor() { this.ref = React.createRef(); } - async attachToPage(): Promise { - // TODO: Mobile and invidious - const referenceNode = await GenericUtils.wait(() => document.querySelector(".ytd-video-primary-info-renderer.title") as HTMLElement); - + async attachToPage(onMobileYouTube: boolean): Promise { + const referenceNode = + await GenericUtils.wait(() => + // YouTube, Mobile YouTube, Invidious + document.querySelector(".ytd-video-primary-info-renderer.title, .slim-video-information-title, #player-container + .h-box > h1") as HTMLElement); + if (referenceNode && !referenceNode.contains(this.container)) { this.container = document.createElement('span'); this.container.id = "categoryPill"; @@ -26,6 +30,10 @@ export class CategoryPill { referenceNode.prepend(this.container); referenceNode.style.display = "flex"; + if (this.ref.current) { + this.unsavedState = this.ref.current.state; + } + ReactDOM.render( , this.container @@ -35,6 +43,19 @@ export class CategoryPill { this.ref.current?.setState(this.unsavedState); this.unsavedState = null; } + + if (onMobileYouTube) { + if (this.mutationObserver) { + this.mutationObserver.disconnect(); + } + + this.mutationObserver = new MutationObserver(() => this.attachToPage(onMobileYouTube)); + + this.mutationObserver.observe(referenceNode, { + childList: true, + subtree: true + }); + } } } From d23e434209faf6c3ff502422b679c7a0fc98aa53 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 5 Jan 2022 15:16:29 -0500 Subject: [PATCH 05/13] Show full video on popup --- src/popup.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/popup.ts b/src/popup.ts index 14d22dd8..8b5366f3 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -1,7 +1,7 @@ import Config from "./config"; import Utils from "./utils"; -import { SponsorTime, SponsorHideType, CategoryActionType } from "./types"; +import { SponsorTime, SponsorHideType, CategoryActionType, ActionType } from "./types"; import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes"; import { showDonationLink } from "./utils/configUtils"; import { getCategoryActionType } from "./utils/categoryUtils"; @@ -405,10 +405,15 @@ async function runThePopup(messageListener?: MessageListener): Promise { const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo); const segmentTimeFromToNode = document.createElement("div"); - segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) + + if (segmentTimes[i].actionType === ActionType.Full) { + segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full"); + } else { + segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) + (getCategoryActionType(segmentTimes[i].category) !== CategoryActionType.POI ? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true) : ""); + } + segmentTimeFromToNode.style.margin = "5px"; sponsorTimeButton.appendChild(categoryColorCircle); From a6a9b7dd8c7a88654909c0ee6a3218738d343504 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 5 Jan 2022 17:26:05 -0500 Subject: [PATCH 06/13] Decrease font size of pill --- public/content.css | 3 +++ src/components/CategoryPillComponent.tsx | 6 ++++-- src/content.ts | 2 +- src/render/CategoryPill.tsx | 7 ++++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/public/content.css b/public/content.css index 98a6b4f2..aabc6790 100644 --- a/public/content.css +++ b/public/content.css @@ -619,6 +619,9 @@ input::-webkit-inner-spin-button { padding-right: 8px; margin-right: 3px; color: white; + cursor: pointer; + font-size: 75%; + height: 100%; } .sponsorBlockCategoryPillTitleSection { diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx index 0f20c82f..1ec12695 100644 --- a/src/components/CategoryPillComponent.tsx +++ b/src/components/CategoryPillComponent.tsx @@ -9,6 +9,7 @@ export interface CategoryPillProps { export interface CategoryPillState { segment?: SponsorTime; show: boolean; + open?: boolean; } class CategoryPillComponent extends React.Component { @@ -18,7 +19,8 @@ class CategoryPillComponent extends React.Component + className={"sponsorBlockCategoryPill"} > diff --git a/src/content.ts b/src/content.ts index 23d3b9d3..f9e3172a 100644 --- a/src/content.ts +++ b/src/content.ts @@ -647,7 +647,7 @@ function setupCategoryPill() { categoryPill = new CategoryPill(); } - categoryPill.attachToPage(onMobileYouTube); + categoryPill.attachToPage(onMobileYouTube, onInvidious); } async function sponsorsLookup(id: string, keepOldSubmissions = true) { diff --git a/src/render/CategoryPill.tsx b/src/render/CategoryPill.tsx index 4fe846d0..4681a813 100644 --- a/src/render/CategoryPill.tsx +++ b/src/render/CategoryPill.tsx @@ -16,7 +16,7 @@ export class CategoryPill { this.ref = React.createRef(); } - async attachToPage(onMobileYouTube: boolean): Promise { + async attachToPage(onMobileYouTube: boolean, onInvidious: boolean): Promise { const referenceNode = await GenericUtils.wait(() => // YouTube, Mobile YouTube, Invidious @@ -49,7 +49,7 @@ export class CategoryPill { this.mutationObserver.disconnect(); } - this.mutationObserver = new MutationObserver(() => this.attachToPage(onMobileYouTube)); + this.mutationObserver = new MutationObserver(() => this.attachToPage(onMobileYouTube, onInvidious)); this.mutationObserver.observe(referenceNode, { childList: true, @@ -66,7 +66,8 @@ export class CategoryPill { setVisibility(show: boolean): void { const newState = { - show + show, + open: show ? this.ref.current?.state.open : false }; if (this.ref.current) { From 8e964b40b308c966a53fa3590499827bb1492ab4 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 5 Jan 2022 20:49:56 -0500 Subject: [PATCH 07/13] Add vote buttons to pill that open on click --- public/content.css | 1 + src/components/CategoryPillComponent.tsx | 51 ++++++++++++- src/components/SkipNoticeComponent.tsx | 25 +----- src/content.ts | 73 ++++++++++-------- src/js-components/skipButtonControlBar.ts | 9 ++- src/messageTypes.ts | 5 ++ src/popup.ts | 10 ++- src/render/CategoryPill.tsx | 29 ++++--- src/utils.ts | 92 ----------------------- src/utils/animationUtils.ts | 78 +++++++++++++++++++ src/utils/genericUtils.ts | 26 ++++++- src/utils/noticeUtils.ts | 21 ++++++ 12 files changed, 249 insertions(+), 171 deletions(-) create mode 100644 src/utils/animationUtils.ts create mode 100644 src/utils/noticeUtils.ts diff --git a/public/content.css b/public/content.css index aabc6790..a8730b1d 100644 --- a/public/content.css +++ b/public/content.css @@ -622,6 +622,7 @@ input::-webkit-inner-spin-button { cursor: pointer; font-size: 75%; height: 100%; + align-items: center; } .sponsorBlockCategoryPillTitleSection { diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx index 1ec12695..7b760b1f 100644 --- a/src/components/CategoryPillComponent.tsx +++ b/src/components/CategoryPillComponent.tsx @@ -1,9 +1,16 @@ import * as React from "react"; import Config from "../config"; -import { SponsorTime } from "../types"; +import { Category, SegmentUUID, SponsorTime } from "../types"; + +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 { GenericUtils } from "../utils/genericUtils"; export interface CategoryPillProps { - + vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise; } export interface CategoryPillState { @@ -32,7 +39,8 @@ class CategoryPillComponent extends React.Component + className={"sponsorBlockCategoryPill"} + onClick={() => this.state.show && this.setState({ open: !this.state.open })}> @@ -41,9 +49,46 @@ class CategoryPillComponent extends React.Component + + {this.state.open && ( + <> + {/* Upvote Button */} +
this.vote(event, 1)}> + +
+ + {/* Downvote Button */} +
this.vote(event, 0)}> + +
+ + )}
); } + + private async vote(event: React.MouseEvent, type: number): Promise { + event.stopPropagation(); + if (this.state.segment) { + const stopAnimation = AnimationUtils.applyLoadingAnimation(event.currentTarget as HTMLElement, 0.3); + + const response = await this.props.vote(type, this.state.segment.UUID); + await stopAnimation(); + + if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { + this.setState({ open: false }); + } else if (response.statusCode !== 403) { + alert(GenericUtils.getErrorMessage(response.statusCode, response.responseText)); + } + } + } } export default CategoryPillComponent; diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index da771853..49dac9ab 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -4,7 +4,6 @@ import Config from "../config" import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types"; import NoticeComponent from "./NoticeComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; -import SubmissionNotice from "../render/SubmissionNotice"; import Utils from "../utils"; const utils = new Utils(); @@ -13,15 +12,7 @@ import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils"; import ThumbsUpSvg from "../svg-icons/thumbs_up_svg"; import ThumbsDownSvg from "../svg-icons/thumbs_down_svg"; import PencilSvg from "../svg-icons/pencil_svg"; - -export enum SkipNoticeAction { - None, - Upvote, - Downvote, - CategoryVote, - CopyDownvote, - Unskip -} +import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils"; export interface SkipNoticeProps { segments: SponsorTime[]; @@ -216,7 +207,7 @@ class SkipNoticeComponent extends React.Component this.prepAction(SkipNoticeAction.Downvote)}> - + {/* Copy and Downvote Button */} @@ -279,7 +270,7 @@ class SkipNoticeComponent extends React.Component this.prepAction(SkipNoticeAction.CopyDownvote)}> {chrome.i18n.getMessage("CopyAndDownvote")} @@ -727,16 +718,6 @@ class SkipNoticeComponent extends React.Component 1) { - return (this.state.actionState === downvoteType) ? this.selectedColor : this.unselectedColor; - } else { - // You dont have segment selectors so the lockbutton needs to be colored and cannot be selected. - return Config.config.isVip && this.segments[0].locked === 1 ? this.lockedColor : this.unselectedColor; - } - } - private getUnskipText(): string { switch (this.props.segments[0].actionType) { case ActionType.Mute: { diff --git a/src/content.ts b/src/content.ts index f9e3172a..07bf607c 100644 --- a/src/content.ts +++ b/src/content.ts @@ -11,7 +11,7 @@ import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar"; import SkipNotice from "./render/SkipNotice"; import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SubmissionNotice from "./render/SubmissionNotice"; -import { Message, MessageResponse } from "./messageTypes"; +import { Message, MessageResponse, VoteResponse } from "./messageTypes"; import * as Chat from "./js-components/chat"; import { getCategoryActionType } from "./utils/categoryUtils"; import { SkipButtonControlBar } from "./js-components/skipButtonControlBar"; @@ -19,6 +19,8 @@ import { Tooltip } from "./render/Tooltip"; import { getStartTimeFromUrl } from "./utils/urlParser"; import { getControls } from "./utils/pageUtils"; import { CategoryPill } from "./render/CategoryPill"; +import { AnimationUtils } from "./utils/animationUtils"; +import { GenericUtils } from "./utils/genericUtils"; // Hack to get the CSS loaded on permission-based sites (Invidious) utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); @@ -647,7 +649,7 @@ function setupCategoryPill() { categoryPill = new CategoryPill(); } - categoryPill.attachToPage(onMobileYouTube, onInvidious); + categoryPill.attachToPage(onMobileYouTube, onInvidious, voteAsync); } async function sponsorsLookup(id: string, keepOldSubmissions = true) { @@ -1369,7 +1371,7 @@ async function createButtons(): Promise { && playerButtons["info"]?.button && !controlsWithEventListeners.includes(controlsContainer)) { controlsWithEventListeners.push(controlsContainer); - utils.setupAutoHideAnimation(playerButtons["info"].button, controlsContainer); + AnimationUtils.setupAutoHideAnimation(playerButtons["info"].button, controlsContainer); } } @@ -1649,13 +1651,37 @@ function clearSponsorTimes() { } //if skipNotice is null, it will not affect the UI -function vote(type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) { +async function vote(type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent): Promise { if (skipNotice !== null && skipNotice !== undefined) { //add loading info skipNotice.addVoteButtonInfo.bind(skipNotice)(chrome.i18n.getMessage("Loading")) skipNotice.setNoticeInfoMessage.bind(skipNotice)(); } + const response = await voteAsync(type, UUID, category); + if (response != undefined) { + //see if it was a success or failure + if (skipNotice != null) { + if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { + //success (treat rate limits as a success) + skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category); + } else if (response.successType == -1) { + if (response.statusCode === 403 && response.responseText.startsWith("Vote rejected due to a warning from a moderator.")) { + skipNotice.setNoticeInfoMessageWithOnClick.bind(skipNotice)(() => { + Chat.openWarningChat(response.responseText); + skipNotice.closeListener.call(skipNotice); + }, chrome.i18n.getMessage("voteRejectedWarning")); + } else { + skipNotice.setNoticeInfoMessage.bind(skipNotice)(GenericUtils.getErrorMessage(response.statusCode, response.responseText)) + } + + skipNotice.resetVoteButtonInfo.bind(skipNotice)(); + } + } + } +} + +async function voteAsync(type: number, UUID: SegmentUUID, category?: Category): Promise { const sponsorIndex = utils.getSponsorIndexFromUUID(sponsorTimes, UUID); // Don't vote for preview sponsors @@ -1675,33 +1701,14 @@ function vote(type: number, UUID: SegmentUUID, category?: Category, skipNotice?: Config.config.skipCount = Config.config.skipCount + factor; } - - chrome.runtime.sendMessage({ - message: "submitVote", - type: type, - UUID: UUID, - category: category - }, function(response) { - if (response != undefined) { - //see if it was a success or failure - if (skipNotice != null) { - if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { - //success (treat rate limits as a success) - skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category); - } else if (response.successType == -1) { - if (response.statusCode === 403 && response.responseText.startsWith("Vote rejected due to a warning from a moderator.")) { - skipNotice.setNoticeInfoMessageWithOnClick.bind(skipNotice)(() => { - Chat.openWarningChat(response.responseText); - skipNotice.closeListener.call(skipNotice); - }, chrome.i18n.getMessage("voteRejectedWarning")); - } else { - skipNotice.setNoticeInfoMessage.bind(skipNotice)(utils.getErrorMessage(response.statusCode, response.responseText)) - } - - skipNotice.resetVoteButtonInfo.bind(skipNotice)(); - } - } - } + + return new Promise((resolve) => { + chrome.runtime.sendMessage({ + message: "submitVote", + type: type, + UUID: UUID, + category: category + }, resolve); }); } @@ -1744,7 +1751,7 @@ function submitSponsorTimes() { async function sendSubmitMessage() { // Add loading animation playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker.svg"); - const stopAnimation = utils.applyLoadingAnimation(playerButtons.submit.button, 1, () => updateEditButtonsOnPlayer()); + const stopAnimation = AnimationUtils.applyLoadingAnimation(playerButtons.submit.button, 1, () => updateEditButtonsOnPlayer()); //check if a sponsor exceeds the duration of the video for (let i = 0; i < sponsorTimesSubmitting.length; i++) { @@ -1816,7 +1823,7 @@ async function sendSubmitMessage() { if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a warning from a moderator.")) { Chat.openWarningChat(response.responseText); } else { - alert(utils.getErrorMessage(response.status, response.responseText)); + alert(GenericUtils.getErrorMessage(response.status, response.responseText)); } } } diff --git a/src/js-components/skipButtonControlBar.ts b/src/js-components/skipButtonControlBar.ts index 32018307..a27eefd0 100644 --- a/src/js-components/skipButtonControlBar.ts +++ b/src/js-components/skipButtonControlBar.ts @@ -3,6 +3,7 @@ import { SponsorTime } from "../types"; import { getSkippingText } from "../utils/categoryUtils"; import Utils from "../utils"; +import { AnimationUtils } from "../utils/animationUtils"; const utils = new Utils(); export interface SkipButtonControlBarProps { @@ -80,9 +81,9 @@ export class SkipButtonControlBar { } if (!this.onMobileYouTube) { - utils.setupAutoHideAnimation(this.skipIcon, mountingContainer, false, false); + AnimationUtils.setupAutoHideAnimation(this.skipIcon, mountingContainer, false, false); } else { - const { hide, show } = utils.setupCustomHideAnimation(this.skipIcon, mountingContainer, false, false); + const { hide, show } = AnimationUtils.setupCustomHideAnimation(this.skipIcon, mountingContainer, false, false); this.hideButton = hide; this.showButton = show; } @@ -104,7 +105,7 @@ export class SkipButtonControlBar { this.refreshText(); this.textContainer?.classList?.remove("hidden"); - utils.disableAutoHideAnimation(this.skipIcon); + AnimationUtils.disableAutoHideAnimation(this.skipIcon); this.startTimer(); } @@ -160,7 +161,7 @@ export class SkipButtonControlBar { this.getChapterPrefix()?.classList?.add("hidden"); - utils.enableAutoHideAnimation(this.skipIcon); + AnimationUtils.enableAutoHideAnimation(this.skipIcon); if (this.onMobileYouTube) { this.hideButton(); } diff --git a/src/messageTypes.ts b/src/messageTypes.ts index 4989c741..1b2949ea 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -61,3 +61,8 @@ export type MessageResponse = | IsChannelWhitelistedResponse | Record; +export interface VoteResponse { + successType: number; + statusCode: number; + responseText: string; +} \ No newline at end of file diff --git a/src/popup.ts b/src/popup.ts index 8b5366f3..4d1d6743 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -5,6 +5,8 @@ import { SponsorTime, SponsorHideType, CategoryActionType, ActionType } from "./ import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes"; import { showDonationLink } from "./utils/configUtils"; import { getCategoryActionType } from "./utils/categoryUtils"; +import { AnimationUtils } from "./utils/animationUtils"; +import { GenericUtils } from "./utils/genericUtils"; const utils = new Utils(); interface MessageListener { @@ -449,7 +451,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg"); uuidButton.addEventListener("click", () => { navigator.clipboard.writeText(UUID); - const stopAnimation = utils.applyLoadingAnimation(uuidButton, 0.3); + const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3); stopAnimation(); }); @@ -555,7 +557,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { PageElements.sponsorTimesContributionsContainer.classList.remove("hidden"); } else { - PageElements.setUsernameStatus.innerText = utils.getErrorMessage(response.status, response.responseText); + PageElements.setUsernameStatus.innerText = GenericUtils.getErrorMessage(response.status, response.responseText); } }); @@ -596,7 +598,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { //success (treat rate limits as a success) addVoteMessage(chrome.i18n.getMessage("voted"), UUID); } else if (response.successType == -1) { - addVoteMessage(utils.getErrorMessage(response.statusCode, response.responseText), UUID); + addVoteMessage(GenericUtils.getErrorMessage(response.statusCode, response.responseText), UUID); } } }); @@ -699,7 +701,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { } function refreshSegments() { - const stopAnimation = utils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3); + const stopAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3); messageHandler.query({ active: true, diff --git a/src/render/CategoryPill.tsx b/src/render/CategoryPill.tsx index 4681a813..d3530c38 100644 --- a/src/render/CategoryPill.tsx +++ b/src/render/CategoryPill.tsx @@ -1,7 +1,8 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import CategoryPillComponent, { CategoryPillState } from "../components/CategoryPillComponent"; -import { SponsorTime } from "../types"; +import { VoteResponse } from "../messageTypes"; +import { Category, SegmentUUID, SponsorTime } from "../types"; import { GenericUtils } from "../utils/genericUtils"; export class CategoryPill { @@ -16,7 +17,8 @@ export class CategoryPill { this.ref = React.createRef(); } - async attachToPage(onMobileYouTube: boolean, onInvidious: boolean): Promise { + async attachToPage(onMobileYouTube: boolean, onInvidious: boolean, + vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise): Promise { const referenceNode = await GenericUtils.wait(() => // YouTube, Mobile YouTube, Invidious @@ -35,7 +37,7 @@ export class CategoryPill { } ReactDOM.render( - , + , this.container ); @@ -49,7 +51,7 @@ export class CategoryPill { this.mutationObserver.disconnect(); } - this.mutationObserver = new MutationObserver(() => this.attachToPage(onMobileYouTube, onInvidious)); + this.mutationObserver = new MutationObserver(() => this.attachToPage(onMobileYouTube, onInvidious, vote)); this.mutationObserver.observe(referenceNode, { childList: true, @@ -78,15 +80,18 @@ export class CategoryPill { } setSegment(segment: SponsorTime): void { - const newState = { - segment, - show: true - }; + if (this.ref.current?.state?.segment !== segment) { + const newState = { + segment, + show: true, + open: false + }; - if (this.ref.current) { - this.ref.current?.setState(newState); - } else { - this.unsavedState = newState; + if (this.ref.current) { + this.ref.current?.setState(newState); + } else { + this.unsavedState = newState; + } } } diff --git a/src/utils.ts b/src/utils.ts index 7cffa45a..760b2d65 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -143,75 +143,6 @@ export default class Utils { }); } - /** - * 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 - */ - applyLoadingAnimation(element: HTMLElement, time: number, callback?: () => void): () => void { - element.style.animation = `rotate ${time}s 0s infinite`; - - return () => { - // 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); - }; - - element.addEventListener("animationend", animationEndListener); - } - } - - 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"); - if (!rightSlide) element.classList.add("autoHideLeft"); - - let mouseEntered = false; - - 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 { - element.classList.add("autoHiding"); - element.classList.add("hidden"); - } - - disableAutoHideAnimation(element: Element): void { - element.classList.remove("autoHiding"); - element.classList.remove("hidden"); - } - /** * Merges any overlapping timestamp ranges into single segments and returns them as a new array. */ @@ -343,29 +274,6 @@ export default class Utils { } } - /** - * Gets the error message in a nice string - * - * @param {int} statusCode - * @returns {string} errorMessage - */ - getErrorMessage(statusCode: number, responseText: string): string { - let errorMessage = ""; - const postFix = (responseText ? "\n\n" + responseText : ""); - - if([400, 429, 409, 502, 503, 0].includes(statusCode)) { - //treat them the same - if (statusCode == 503) statusCode = 502; - - errorMessage = chrome.i18n.getMessage(statusCode + "") + " " + chrome.i18n.getMessage("errorCode") + statusCode - + "\n\n" + chrome.i18n.getMessage("statusReminder"); - } else { - errorMessage = chrome.i18n.getMessage("connectionError") + statusCode; - } - - return errorMessage + postFix; - } - /** * Sends a request to a custom server * diff --git a/src/utils/animationUtils.ts b/src/utils/animationUtils.ts new file mode 100644 index 00000000..933e6446 --- /dev/null +++ b/src/utils/animationUtils.ts @@ -0,0 +1,78 @@ + /** + * 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 { + 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("hidden"); + 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("hidden"); + } + }, + show: () => { + mouseEntered = true; + element.classList.remove("animationDone"); + + // Wait for next event loop + setTimeout(() => { + if (mouseEntered) element.classList.remove("hidden") + }, 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("hidden"); +} + +function disableAutoHideAnimation(element: Element): void { + element.classList.remove("autoHiding"); + element.classList.remove("hidden"); +} + +export const AnimationUtils = { + applyLoadingAnimation, + setupAutoHideAnimation, + setupCustomHideAnimation, + enableAutoHideAnimation, + disableAutoHideAnimation +}; \ No newline at end of file diff --git a/src/utils/genericUtils.ts b/src/utils/genericUtils.ts index 32cf83f5..b146e57a 100644 --- a/src/utils/genericUtils.ts +++ b/src/utils/genericUtils.ts @@ -21,6 +21,30 @@ async function wait(condition: () => T | false, timeout = 5000, check = 100): }); } +/** + * Gets the error message in a nice string + * + * @param {int} statusCode + * @returns {string} errorMessage + */ +function getErrorMessage(statusCode: number, responseText: string): string { + let errorMessage = ""; + const postFix = (responseText ? "\n\n" + responseText : ""); + + if([400, 429, 409, 502, 503, 0].includes(statusCode)) { + //treat them the same + if (statusCode == 503) statusCode = 502; + + errorMessage = chrome.i18n.getMessage(statusCode + "") + " " + chrome.i18n.getMessage("errorCode") + statusCode + + "\n\n" + chrome.i18n.getMessage("statusReminder"); + } else { + errorMessage = chrome.i18n.getMessage("connectionError") + statusCode; + } + + return errorMessage + postFix; +} + export const GenericUtils = { - wait + wait, + getErrorMessage } \ No newline at end of file diff --git a/src/utils/noticeUtils.ts b/src/utils/noticeUtils.ts new file mode 100644 index 00000000..5d77063b --- /dev/null +++ b/src/utils/noticeUtils.ts @@ -0,0 +1,21 @@ +import Config from "../config"; +import { SponsorTime } from "../types"; + +export enum SkipNoticeAction { + None, + Upvote, + Downvote, + CategoryVote, + CopyDownvote, + Unskip +} + +export function downvoteButtonColor(segments: SponsorTime[], actionState: SkipNoticeAction, downvoteType: SkipNoticeAction): string { + // Also used for "Copy and Downvote" + if (segments?.length > 1) { + return (actionState === downvoteType) ? Config.config.colorPalette.red : Config.config.colorPalette.white; + } else { + // You dont have segment selectors so the lockbutton needs to be colored and cannot be selected. + return Config.config.isVip && segments[0].locked === 1 ? Config.config.colorPalette.locked : Config.config.colorPalette.white; + } +} \ No newline at end of file From c7d5011cc092470e5c7ff6ab5ef2a22703039d7f Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 6 Jan 2022 01:19:20 -0500 Subject: [PATCH 08/13] Add tooltip recommending full video report for large segments --- public/_locales/en/messages.json | 3 ++ src/components/SponsorTimeEditComponent.tsx | 39 ++++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index c862ba00..7066e5f7 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -741,6 +741,9 @@ "message": "Got it", "description": "Used as the button to dismiss a tooltip" }, + "fullVideoTooltipWarning": { + "message": "This segment is large. If the whole video is about one topic, then change from \"Skip\" to \"Full Video\". See the guidelines for more information." + }, "experiementOptOut": { "message": "Opt-out of all future experiments", "description": "This is used in a popup about a new experiment to get a list of unlisted videos to back up since all unlisted videos uploaded before 2017 will be set to private." diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 1a734cb5..eaf83a91 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -40,6 +40,7 @@ class SponsorTimeEditComponent extends React.Component this.configUpdate(); Config.configListeners.push(this.configUpdate.bind(this)); } + + this.checkToShowFullVideoWarning(); } componentWillUnmount(): void { @@ -82,6 +85,8 @@ class SponsorTimeEditComponent extends React.Component { Config.config.scrollToEditTimeUpdate = true }); + } + } + + showToolTip(text: string, buttonFunction?: () => void): boolean { + const element = document.getElementById("sponsorTimesContainer" + this.idSuffix); + if (element) { new RectangleTooltip({ - text: chrome.i18n.getMessage("SponsorTimeEditScrollNewFeature"), + text, referenceNode: element.parentElement, prependElement: element, timeout: 15, @@ -300,10 +312,27 @@ class SponsorTimeEditComponent extends React.Component { Config.config.scrollToEditTimeUpdate = true }, + buttonFunction, fontSize: "14px", maxHeight: "200px" }); + + return true; + } else { + return false; + } + } + + checkToShowFullVideoWarning(): void { + const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index]; + const segmentDuration = sponsorTime.segment[1] - sponsorTime.segment[0]; + const videoPercentage = segmentDuration / this.props.contentContainer().v.duration; + + if (videoPercentage > 0.6 && !this.fullVideoWarningShown + && (sponsorTime.category === "sponsor" || sponsorTime.category === "selfpromo" || sponsorTime.category === "chooseACategory")) { + if (this.showToolTip(chrome.i18n.getMessage("fullVideoTooltipWarning"))) { + this.fullVideoWarningShown = true; + } } } From 1aac863df0108dd77b32a9602fdf91dad61fa81c Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 6 Jan 2022 01:54:47 -0500 Subject: [PATCH 09/13] Fix error --- src/render/SkipNotice.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index 5113c2da..b3ea571c 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -4,9 +4,10 @@ import * as ReactDOM from "react-dom"; import Utils from "../utils"; const utils = new Utils(); -import SkipNoticeComponent, { SkipNoticeAction } from "../components/SkipNoticeComponent"; +import SkipNoticeComponent from "../components/SkipNoticeComponent"; import { SponsorTime, ContentContainer, NoticeVisbilityMode } from "../types"; import Config from "../config"; +import { SkipNoticeAction } from "../utils/noticeUtils"; class SkipNotice { segments: SponsorTime[]; From 4d724deba3ccd281f169cfb2fd31303181090e52 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 6 Jan 2022 02:06:55 -0500 Subject: [PATCH 10/13] Add title text and hide on downvote --- public/_locales/en/messages.json | 3 +++ src/components/CategoryPillComponent.tsx | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 7066e5f7..d7855a69 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -744,6 +744,9 @@ "fullVideoTooltipWarning": { "message": "This segment is large. If the whole video is about one topic, then change from \"Skip\" to \"Full Video\". See the guidelines for more information." }, + "categoryPillTitleText": { + "message": "This entire video is labeled as this category and is too tightly integrated to be able to separate" + }, "experiementOptOut": { "message": "Opt-out of all future experiments", "description": "This is used in a popup about a new experiment to get a list of unlisted videos to back up since all unlisted videos uploaded before 2017 will be set to private." diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx index 7b760b1f..abe6aaf0 100644 --- a/src/components/CategoryPillComponent.tsx +++ b/src/components/CategoryPillComponent.tsx @@ -40,6 +40,7 @@ class CategoryPillComponent extends React.Component this.state.show && this.setState({ open: !this.state.open })}> Date: Thu, 6 Jan 2022 02:10:28 -0500 Subject: [PATCH 11/13] Fix voting on category pill on mobile --- src/components/CategoryPillComponent.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx index abe6aaf0..c22bbbb9 100644 --- a/src/components/CategoryPillComponent.tsx +++ b/src/components/CategoryPillComponent.tsx @@ -41,7 +41,7 @@ class CategoryPillComponent extends React.Component this.state.show && this.setState({ open: !this.state.open })}> + onClick={(e) => this.toggleOpen(e)}> @@ -58,7 +58,7 @@ class CategoryPillComponent extends React.Component this.vote(event, 1)}> + onClick={(e) => this.vote(e, 1)}> @@ -75,6 +75,14 @@ class CategoryPillComponent extends React.Component { event.stopPropagation(); if (this.state.segment) { From d16a409db27b04feec58de412baa2984f36e3832 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 6 Jan 2022 15:18:40 -0500 Subject: [PATCH 12/13] Show black text on pill for unpaid promotion --- public/content.css | 1 - src/components/CategoryPillComponent.tsx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/content.css b/public/content.css index a8730b1d..bfcd4f30 100644 --- a/public/content.css +++ b/public/content.css @@ -618,7 +618,6 @@ input::-webkit-inner-spin-button { padding-left: 8px; padding-right: 8px; margin-right: 3px; - color: white; cursor: pointer; font-size: 75%; height: 100%; diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx index c22bbbb9..6c7e0437 100644 --- a/src/components/CategoryPillComponent.tsx +++ b/src/components/CategoryPillComponent.tsx @@ -34,7 +34,8 @@ class CategoryPillComponent extends React.Component Date: Thu, 6 Jan 2022 16:26:59 -0500 Subject: [PATCH 13/13] Add option to disable showing full video segments --- public/_locales/en/messages.json | 4 ++++ public/options/options.html | 16 ++++++++++++++++ src/config.ts | 2 ++ src/content.ts | 14 +++++++++++++- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index d7855a69..ac7cbe09 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -624,6 +624,10 @@ "muteSegments": { "message": "Allow segments that mute audio instead of skip" }, + "fullVideoSegments": { + "message": "Show an icon when a video is entirely an advertisement", + "description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo" + }, "colorFormatIncorrect": { "message": "Your color is formatted incorrectly. It should be a 3 or 6 digit hex code with a number sign at the beginning." }, diff --git a/public/options/options.html b/public/options/options.html index 1fbcf1b1..657b2d4d 100644 --- a/public/options/options.html +++ b/public/options/options.html @@ -66,6 +66,22 @@
+
+ + +
+
+
+
+

diff --git a/src/config.ts b/src/config.ts index b4a1a9cd..ae6f45aa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,6 +21,7 @@ interface SBConfig { showTimeWithSkips: boolean, disableSkipping: boolean, muteSegments: boolean, + fullVideoSegments: boolean, trackViewCount: boolean, trackViewCountInPrivate: boolean, dontShowNotice: boolean, @@ -177,6 +178,7 @@ const Config: SBObject = { showTimeWithSkips: true, disableSkipping: false, muteSegments: true, + fullVideoSegments: true, trackViewCount: true, trackViewCountInPrivate: true, dontShowNotice: false, diff --git a/src/content.ts b/src/content.ts index 07bf607c..2b719591 100644 --- a/src/content.ts +++ b/src/content.ts @@ -687,7 +687,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) { 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.Full] : [ActionType.Skip, ActionType.Full], + actionTypes: getEnabledActionTypes(), userAgent: `${chrome.runtime.id}`, ...extraRequestData }); @@ -768,6 +768,18 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) { lookupVipInformation(id); } +function getEnabledActionTypes(): ActionType[] { + const actionTypes = [ActionType.Skip]; + if (Config.config.muteSegments) { + actionTypes.push(ActionType.Mute); + } + if (Config.config.fullVideoSegments) { + actionTypes.push(ActionType.Full); + } + + return actionTypes; +} + function lookupVipInformation(id: string): void { updateVipInfo().then((isVip) => { if (isVip) {