mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-07 12:07:11 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/mchangrh/1116
This commit is contained in:
@@ -4,8 +4,8 @@
|
|||||||
"serverAddressComment": "This specifies the default SponsorBlock server to connect to",
|
"serverAddressComment": "This specifies the default SponsorBlock server to connect to",
|
||||||
"categoryList": ["sponsor", "selfpromo", "interaction", "poi_highlight", "intro", "outro", "preview", "filler", "music_offtopic"],
|
"categoryList": ["sponsor", "selfpromo", "interaction", "poi_highlight", "intro", "outro", "preview", "filler", "music_offtopic"],
|
||||||
"categorySupport": {
|
"categorySupport": {
|
||||||
"sponsor": ["skip", "mute"],
|
"sponsor": ["skip", "mute", "full"],
|
||||||
"selfpromo": ["skip", "mute"],
|
"selfpromo": ["skip", "mute", "full"],
|
||||||
"interaction": ["skip", "mute"],
|
"interaction": ["skip", "mute"],
|
||||||
"intro": ["skip", "mute"],
|
"intro": ["skip", "mute"],
|
||||||
"outro": ["skip", "mute"],
|
"outro": ["skip", "mute"],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "__MSG_fullName__",
|
"name": "__MSG_fullName__",
|
||||||
"short_name": "SponsorBlock",
|
"short_name": "SponsorBlock",
|
||||||
"version": "3.7.1",
|
"version": "3.7.2",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"description": "__MSG_Description__",
|
"description": "__MSG_Description__",
|
||||||
"homepage_url": "https://sponsor.ajay.app",
|
"homepage_url": "https://sponsor.ajay.app",
|
||||||
|
|||||||
@@ -302,6 +302,10 @@
|
|||||||
"mute": {
|
"mute": {
|
||||||
"message": "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": {
|
"skip_category": {
|
||||||
"message": "Skip {0}?"
|
"message": "Skip {0}?"
|
||||||
},
|
},
|
||||||
@@ -620,6 +624,10 @@
|
|||||||
"muteSegments": {
|
"muteSegments": {
|
||||||
"message": "Allow segments that mute audio instead of skip"
|
"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": {
|
"colorFormatIncorrect": {
|
||||||
"message": "Your color is formatted incorrectly. It should be a 3 or 6 digit hex code with a number sign at the beginning."
|
"message": "Your color is formatted incorrectly. It should be a 3 or 6 digit hex code with a number sign at the beginning."
|
||||||
},
|
},
|
||||||
@@ -737,6 +745,12 @@
|
|||||||
"message": "Got it",
|
"message": "Got it",
|
||||||
"description": "Used as the button to dismiss a tooltip"
|
"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."
|
||||||
|
},
|
||||||
|
"categoryPillTitleText": {
|
||||||
|
"message": "This entire video is labeled as this category and is too tightly integrated to be able to separate"
|
||||||
|
},
|
||||||
"experiementOptOut": {
|
"experiementOptOut": {
|
||||||
"message": "Opt-out of all future experiments",
|
"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."
|
"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."
|
||||||
|
|||||||
@@ -613,3 +613,18 @@ input::-webkit-inner-spin-button {
|
|||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sponsorBlockCategoryPill {
|
||||||
|
border-radius: 25px;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
margin-right: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 75%;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsorBlockCategoryPillTitleSection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@@ -66,6 +66,22 @@
|
|||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div option-type="toggle" sync-option="fullVideoSegments">
|
||||||
|
<label class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<div class="switch-label">
|
||||||
|
__MSG_fullVideoSegments__
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|||||||
107
src/components/CategoryPillComponent.tsx
Normal file
107
src/components/CategoryPillComponent.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Config from "../config";
|
||||||
|
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<VoteResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryPillState {
|
||||||
|
segment?: SponsorTime;
|
||||||
|
show: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryPillState> {
|
||||||
|
|
||||||
|
constructor(props: CategoryPillProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
segment: null,
|
||||||
|
show: false,
|
||||||
|
open: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
const style: React.CSSProperties = {
|
||||||
|
backgroundColor: Config.config.barTypes["preview-" + this.state.segment?.category]?.color,
|
||||||
|
display: this.state.show ? "flex" : "none",
|
||||||
|
color: this.state.segment?.category === "sponsor" ? "white" : "black",
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span style={style}
|
||||||
|
className={"sponsorBlockCategoryPill"}
|
||||||
|
title={chrome.i18n.getMessage("categoryPillTitleText")}
|
||||||
|
onClick={(e) => this.toggleOpen(e)}>
|
||||||
|
<span className="sponsorBlockCategoryPillTitleSection">
|
||||||
|
<img className="sponsorSkipLogo sponsorSkipObject"
|
||||||
|
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
||||||
|
</img>
|
||||||
|
<span className="sponsorBlockCategoryPillTitle">
|
||||||
|
{chrome.i18n.getMessage("category_" + this.state.segment?.category)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{this.state.open && (
|
||||||
|
<>
|
||||||
|
{/* Upvote Button */}
|
||||||
|
<div id={"sponsorTimesDownvoteButtonsContainerUpvoteCategoryPill"}
|
||||||
|
className="voteButton"
|
||||||
|
style={{marginLeft: "5px"}}
|
||||||
|
title={chrome.i18n.getMessage("upvoteButtonInfo")}
|
||||||
|
onClick={(e) => this.vote(e, 1)}>
|
||||||
|
<ThumbsUpSvg fill={Config.config.colorPalette.white} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Downvote Button */}
|
||||||
|
<div id={"sponsorTimesDownvoteButtonsContainerDownvoteCategoryPill"}
|
||||||
|
className="voteButton"
|
||||||
|
title={chrome.i18n.getMessage("reportButtonInfo")}
|
||||||
|
onClick={(event) => this.vote(event, 0)}>
|
||||||
|
<ThumbsDownSvg fill={downvoteButtonColor(null, null, SkipNoticeAction.Downvote)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleOpen(event: React.MouseEvent): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (this.state.show) {
|
||||||
|
this.setState({ open: !this.state.open });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async vote(event: React.MouseEvent, type: number): Promise<void> {
|
||||||
|
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,
|
||||||
|
show: type === 1
|
||||||
|
});
|
||||||
|
} else if (response.statusCode !== 403) {
|
||||||
|
alert(GenericUtils.getErrorMessage(response.statusCode, response.responseText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoryPillComponent;
|
||||||
@@ -4,7 +4,6 @@ import Config from "../config"
|
|||||||
import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
|
import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
|
||||||
import NoticeComponent from "./NoticeComponent";
|
import NoticeComponent from "./NoticeComponent";
|
||||||
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
||||||
import SubmissionNotice from "../render/SubmissionNotice";
|
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
@@ -13,15 +12,7 @@ import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils";
|
|||||||
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
|
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
|
||||||
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
||||||
import PencilSvg from "../svg-icons/pencil_svg";
|
import PencilSvg from "../svg-icons/pencil_svg";
|
||||||
|
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
||||||
export enum SkipNoticeAction {
|
|
||||||
None,
|
|
||||||
Upvote,
|
|
||||||
Downvote,
|
|
||||||
CategoryVote,
|
|
||||||
CopyDownvote,
|
|
||||||
Unskip
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SkipNoticeProps {
|
export interface SkipNoticeProps {
|
||||||
segments: SponsorTime[];
|
segments: SponsorTime[];
|
||||||
@@ -216,7 +207,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
|||||||
style={{marginRight: "5px", marginLeft: "5px"}}
|
style={{marginRight: "5px", marginLeft: "5px"}}
|
||||||
title={chrome.i18n.getMessage("reportButtonInfo")}
|
title={chrome.i18n.getMessage("reportButtonInfo")}
|
||||||
onClick={() => this.prepAction(SkipNoticeAction.Downvote)}>
|
onClick={() => this.prepAction(SkipNoticeAction.Downvote)}>
|
||||||
<ThumbsDownSvg fill={this.downvoteButtonColor(SkipNoticeAction.Downvote)} />
|
<ThumbsDownSvg fill={downvoteButtonColor(this.segments, this.state.actionState, SkipNoticeAction.Downvote)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Copy and Downvote Button */}
|
{/* Copy and Downvote Button */}
|
||||||
@@ -279,7 +270,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
|||||||
{/* Copy Segment */}
|
{/* Copy Segment */}
|
||||||
<button className="sponsorSkipObject sponsorSkipNoticeButton"
|
<button className="sponsorSkipObject sponsorSkipNoticeButton"
|
||||||
title={chrome.i18n.getMessage("CopyDownvoteButtonInfo")}
|
title={chrome.i18n.getMessage("CopyDownvoteButtonInfo")}
|
||||||
style={{color: this.downvoteButtonColor(SkipNoticeAction.Downvote)}}
|
style={{color: downvoteButtonColor(this.segments, this.state.actionState, SkipNoticeAction.Downvote)}}
|
||||||
onClick={() => this.prepAction(SkipNoticeAction.CopyDownvote)}>
|
onClick={() => this.prepAction(SkipNoticeAction.CopyDownvote)}>
|
||||||
{chrome.i18n.getMessage("CopyAndDownvote")}
|
{chrome.i18n.getMessage("CopyAndDownvote")}
|
||||||
</button>
|
</button>
|
||||||
@@ -727,16 +718,6 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
downvoteButtonColor(downvoteType: SkipNoticeAction): string {
|
|
||||||
// Also used for "Copy and Downvote"
|
|
||||||
if (this.segments.length > 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 {
|
private getUnskipText(): string {
|
||||||
switch (this.props.segments[0].actionType) {
|
switch (this.props.segments[0].actionType) {
|
||||||
case ActionType.Mute: {
|
case ActionType.Mute: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as CompileConfig from "../../config.json";
|
import * as CompileConfig from "../../config.json";
|
||||||
import Config from "../config";
|
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 Utils from "../utils";
|
||||||
import { getCategoryActionType } from "../utils/categoryUtils";
|
import { getCategoryActionType } from "../utils/categoryUtils";
|
||||||
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
|
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
|
||||||
@@ -40,6 +40,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
|
|
||||||
previousSkipType: CategoryActionType;
|
previousSkipType: CategoryActionType;
|
||||||
timeBeforeChangingToPOI: number; // Initialized when first selecting POI
|
timeBeforeChangingToPOI: number; // Initialized when first selecting POI
|
||||||
|
fullVideoWarningShown = false;
|
||||||
|
|
||||||
constructor(props: SponsorTimeEditProps) {
|
constructor(props: SponsorTimeEditProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -73,6 +74,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
this.configUpdateListener = () => this.configUpdate();
|
this.configUpdateListener = () => this.configUpdate();
|
||||||
Config.configListeners.push(this.configUpdate.bind(this));
|
Config.configListeners.push(this.configUpdate.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkToShowFullVideoWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
componentWillUnmount(): void {
|
||||||
@@ -82,6 +85,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactElement {
|
render(): React.ReactElement {
|
||||||
|
this.checkToShowFullVideoWarning();
|
||||||
|
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
};
|
};
|
||||||
@@ -100,11 +105,14 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
};
|
};
|
||||||
// Create time display
|
// Create time display
|
||||||
let timeDisplay: JSX.Element;
|
let timeDisplay: JSX.Element;
|
||||||
|
const timeDisplayStyle: React.CSSProperties = {};
|
||||||
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
|
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
|
||||||
const segment = sponsorTime.segment;
|
const segment = sponsorTime.segment;
|
||||||
|
if (sponsorTime?.actionType === ActionType.Full) timeDisplayStyle.display = "none";
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
timeDisplay = (
|
timeDisplay = (
|
||||||
<div id={"sponsorTimesContainer" + this.idSuffix}
|
<div id={"sponsorTimesContainer" + this.idSuffix}
|
||||||
|
style={timeDisplayStyle}
|
||||||
className="sponsorTimeDisplay">
|
className="sponsorTimeDisplay">
|
||||||
|
|
||||||
<span id={"nowButton0" + this.idSuffix}
|
<span id={"nowButton0" + this.idSuffix}
|
||||||
@@ -155,6 +163,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
timeDisplay = (
|
timeDisplay = (
|
||||||
|
|
||||||
<div id={"sponsorTimesContainer" + this.idSuffix}
|
<div id={"sponsorTimesContainer" + this.idSuffix}
|
||||||
|
style={timeDisplayStyle}
|
||||||
className="sponsorTimeDisplay"
|
className="sponsorTimeDisplay"
|
||||||
onClick={this.toggleEditTime.bind(this)}>
|
onClick={this.toggleEditTime.bind(this)}>
|
||||||
{utils.getFormattedTime(segment[0], true) +
|
{utils.getFormattedTime(segment[0], true) +
|
||||||
@@ -246,7 +255,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
const before = utils.getFormattedTimeToSeconds(sponsorTimeEdits[index]);
|
const before = utils.getFormattedTimeToSeconds(sponsorTimeEdits[index]);
|
||||||
const after = utils.getFormattedTimeToSeconds(targetValue);
|
const after = utils.getFormattedTimeToSeconds(targetValue);
|
||||||
const difference = Math.abs(before - after);
|
const difference = Math.abs(before - after);
|
||||||
if (0 < difference && difference< 0.5) this.showToolTip();
|
if (0 < difference && difference< 0.5) this.showScrollToEditToolTip();
|
||||||
|
|
||||||
sponsorTimeEdits[index] = targetValue;
|
sponsorTimeEdits[index] = targetValue;
|
||||||
if (index === 0 && getCategoryActionType(sponsorTime.category) === CategoryActionType.POI) sponsorTimeEdits[1] = targetValue;
|
if (index === 0 && getCategoryActionType(sponsorTime.category) === CategoryActionType.POI) sponsorTimeEdits[1] = targetValue;
|
||||||
@@ -254,6 +263,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
this.setState({sponsorTimeEdits});
|
this.setState({sponsorTimeEdits});
|
||||||
this.saveEditTimes();
|
this.saveEditTimes();
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTimesWhenScrolling(index: number, e: React.WheelEvent, sponsorTime: SponsorTime): void {
|
changeTimesWhenScrolling(index: number, e: React.WheelEvent, sponsorTime: SponsorTime): void {
|
||||||
let step = 0;
|
let step = 0;
|
||||||
// shift + ctrl = 1
|
// shift + ctrl = 1
|
||||||
@@ -284,11 +294,17 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showToolTip(): void {
|
showScrollToEditToolTip(): void {
|
||||||
if (!Config.config.scrollToEditTimeUpdate && document.getElementById("sponsorRectangleTooltip" + "sponsorTimesContainer" + this.idSuffix) === null) {
|
if (!Config.config.scrollToEditTimeUpdate && document.getElementById("sponsorRectangleTooltip" + "sponsorTimesContainer" + this.idSuffix) === null) {
|
||||||
const element = document.getElementById("sponsorTimesContainer" + this.idSuffix);
|
this.showToolTip(chrome.i18n.getMessage("SponsorTimeEditScrollNewFeature"), () => { Config.config.scrollToEditTimeUpdate = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showToolTip(text: string, buttonFunction?: () => void): boolean {
|
||||||
|
const element = document.getElementById("sponsorTimesContainer" + this.idSuffix);
|
||||||
|
if (element) {
|
||||||
new RectangleTooltip({
|
new RectangleTooltip({
|
||||||
text: chrome.i18n.getMessage("SponsorTimeEditScrollNewFeature"),
|
text,
|
||||||
referenceNode: element.parentElement,
|
referenceNode: element.parentElement,
|
||||||
prependElement: element,
|
prependElement: element,
|
||||||
timeout: 15,
|
timeout: 15,
|
||||||
@@ -296,10 +312,27 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
leftOffset: -318 + "px",
|
leftOffset: -318 + "px",
|
||||||
backgroundColor: "rgba(28, 28, 28, 1.0)",
|
backgroundColor: "rgba(28, 28, 28, 1.0)",
|
||||||
htmlId: "sponsorTimesContainer" + this.idSuffix,
|
htmlId: "sponsorTimesContainer" + this.idSuffix,
|
||||||
buttonFunction: () => { Config.config.scrollToEditTimeUpdate = true },
|
buttonFunction,
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
maxHeight: "200px"
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,6 +477,12 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
Config.config.segmentTimes.set(this.props.contentContainer().sponsorVideoID, sponsorTimesSubmitting);
|
Config.config.segmentTimes.set(this.props.contentContainer().sponsorVideoID, sponsorTimesSubmitting);
|
||||||
|
|
||||||
this.props.contentContainer().updatePreviewBar();
|
this.props.contentContainer().updatePreviewBar();
|
||||||
|
|
||||||
|
if (sponsorTimesSubmitting[this.props.index].actionType === ActionType.Full
|
||||||
|
&& (sponsorTimesSubmitting[this.props.index].segment[0] !== 0 || sponsorTimesSubmitting[this.props.index].segment[1] !== 0)) {
|
||||||
|
this.setTimeTo(0, 0);
|
||||||
|
this.setTimeTo(1, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previewTime(ctrlPressed = false, shiftPressed = false): void {
|
previewTime(ctrlPressed = false, shiftPressed = false): void {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface SBConfig {
|
|||||||
showTimeWithSkips: boolean,
|
showTimeWithSkips: boolean,
|
||||||
disableSkipping: boolean,
|
disableSkipping: boolean,
|
||||||
muteSegments: boolean,
|
muteSegments: boolean,
|
||||||
|
fullVideoSegments: boolean,
|
||||||
trackViewCount: boolean,
|
trackViewCount: boolean,
|
||||||
trackViewCountInPrivate: boolean,
|
trackViewCountInPrivate: boolean,
|
||||||
dontShowNotice: boolean,
|
dontShowNotice: boolean,
|
||||||
@@ -177,6 +178,7 @@ const Config: SBObject = {
|
|||||||
showTimeWithSkips: true,
|
showTimeWithSkips: true,
|
||||||
disableSkipping: false,
|
disableSkipping: false,
|
||||||
muteSegments: true,
|
muteSegments: true,
|
||||||
|
fullVideoSegments: true,
|
||||||
trackViewCount: true,
|
trackViewCount: true,
|
||||||
trackViewCountInPrivate: true,
|
trackViewCountInPrivate: true,
|
||||||
dontShowNotice: false,
|
dontShowNotice: false,
|
||||||
|
|||||||
109
src/content.ts
109
src/content.ts
@@ -11,13 +11,16 @@ import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar";
|
|||||||
import SkipNotice from "./render/SkipNotice";
|
import SkipNotice from "./render/SkipNotice";
|
||||||
import SkipNoticeComponent from "./components/SkipNoticeComponent";
|
import SkipNoticeComponent from "./components/SkipNoticeComponent";
|
||||||
import SubmissionNotice from "./render/SubmissionNotice";
|
import SubmissionNotice from "./render/SubmissionNotice";
|
||||||
import { Message, MessageResponse } from "./messageTypes";
|
import { Message, MessageResponse, VoteResponse } from "./messageTypes";
|
||||||
import * as Chat from "./js-components/chat";
|
import * as Chat from "./js-components/chat";
|
||||||
import { getCategoryActionType } from "./utils/categoryUtils";
|
import { getCategoryActionType } from "./utils/categoryUtils";
|
||||||
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
||||||
import { Tooltip } from "./render/Tooltip";
|
import { Tooltip } from "./render/Tooltip";
|
||||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||||
import { findValidElement, getControls, isVisible } from "./utils/pageUtils";
|
import { findValidElement, getControls, isVisible } 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)
|
// Hack to get the CSS loaded on permission-based sites (Invidious)
|
||||||
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
|
||||||
@@ -75,9 +78,11 @@ let lastCheckVideoTime = -1;
|
|||||||
//is this channel whitelised from getting sponsors skipped
|
//is this channel whitelised from getting sponsors skipped
|
||||||
let channelWhitelisted = false;
|
let channelWhitelisted = false;
|
||||||
|
|
||||||
// create preview bar
|
|
||||||
let previewBar: PreviewBar = null;
|
let previewBar: PreviewBar = null;
|
||||||
|
// Skip to highlight button
|
||||||
let skipButtonControlBar: SkipButtonControlBar = null;
|
let skipButtonControlBar: SkipButtonControlBar = null;
|
||||||
|
// For full video sponsors/selfpromo
|
||||||
|
let categoryPill: CategoryPill = null;
|
||||||
|
|
||||||
/** Element containing the player controls on the YouTube player. */
|
/** Element containing the player controls on the YouTube player. */
|
||||||
let controls: HTMLElement | null = null;
|
let controls: HTMLElement | null = null;
|
||||||
@@ -264,6 +269,7 @@ function resetValues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipButtonControlBar?.disable();
|
skipButtonControlBar?.disable();
|
||||||
|
categoryPill?.setVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoIDChange(id) {
|
async function videoIDChange(id) {
|
||||||
@@ -560,6 +566,7 @@ function refreshVideoAttachments() {
|
|||||||
|
|
||||||
setupVideoListeners();
|
setupVideoListeners();
|
||||||
setupSkipButtonControlBar();
|
setupSkipButtonControlBar();
|
||||||
|
setupCategoryPill();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new bar in the new video element
|
// Create a new bar in the new video element
|
||||||
@@ -657,6 +664,14 @@ function setupSkipButtonControlBar() {
|
|||||||
skipButtonControlBar.attachToPage();
|
skipButtonControlBar.attachToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupCategoryPill() {
|
||||||
|
if (!categoryPill) {
|
||||||
|
categoryPill = new CategoryPill();
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryPill.attachToPage(onMobileYouTube, onInvidious, voteAsync);
|
||||||
|
}
|
||||||
|
|
||||||
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
||||||
if (!video || !isVisible(video)) refreshVideoAttachments();
|
if (!video || !isVisible(video)) refreshVideoAttachments();
|
||||||
//there is still no video here
|
//there is still no video here
|
||||||
@@ -692,7 +707,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
|||||||
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
|
||||||
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||||
categories,
|
categories,
|
||||||
actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute] : [ActionType.Skip],
|
actionTypes: getEnabledActionTypes(),
|
||||||
userAgent: `${chrome.runtime.id}`,
|
userAgent: `${chrome.runtime.id}`,
|
||||||
...extraRequestData
|
...extraRequestData
|
||||||
});
|
});
|
||||||
@@ -773,6 +788,18 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
|
|||||||
lookupVipInformation(id);
|
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 {
|
function lookupVipInformation(id: string): void {
|
||||||
updateVipInfo().then((isVip) => {
|
updateVipInfo().then((isVip) => {
|
||||||
if (isVip) {
|
if (isVip) {
|
||||||
@@ -884,6 +911,11 @@ function startSkipScheduleCheckingForStartSponsors() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fullVideoSegment = sponsorTimes.filter((time) => time.actionType === ActionType.Full)[0];
|
||||||
|
if (fullVideoSegment) {
|
||||||
|
categoryPill?.setSegment(fullVideoSegment);
|
||||||
|
}
|
||||||
|
|
||||||
if (startingSegmentTime !== -1) {
|
if (startingSegmentTime !== -1) {
|
||||||
startSponsorSchedule(undefined, startingSegmentTime);
|
startSponsorSchedule(undefined, startingSegmentTime);
|
||||||
} else {
|
} else {
|
||||||
@@ -1005,6 +1037,7 @@ function updatePreviewBar(): void {
|
|||||||
segment: segment.segment as [number, number],
|
segment: segment.segment as [number, number],
|
||||||
category: segment.category,
|
category: segment.category,
|
||||||
unsubmitted: false,
|
unsubmitted: false,
|
||||||
|
actionType: segment.actionType,
|
||||||
showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI
|
showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1015,11 +1048,12 @@ function updatePreviewBar(): void {
|
|||||||
segment: segment.segment as [number, number],
|
segment: segment.segment as [number, number],
|
||||||
category: segment.category,
|
category: segment.category,
|
||||||
unsubmitted: true,
|
unsubmitted: true,
|
||||||
|
actionType: segment.actionType,
|
||||||
showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI
|
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) {
|
if (Config.config.showTimeWithSkips) {
|
||||||
const skippedDuration = utils.getTimestampsDuration(previewBarSegments.map(({segment}) => segment));
|
const skippedDuration = utils.getTimestampsDuration(previewBarSegments.map(({segment}) => segment));
|
||||||
@@ -1391,7 +1425,7 @@ async function createButtons(): Promise<void> {
|
|||||||
&& playerButtons["info"]?.button && !controlsWithEventListeners.includes(controlsContainer)) {
|
&& playerButtons["info"]?.button && !controlsWithEventListeners.includes(controlsContainer)) {
|
||||||
controlsWithEventListeners.push(controlsContainer);
|
controlsWithEventListeners.push(controlsContainer);
|
||||||
|
|
||||||
utils.setupAutoHideAnimation(playerButtons["info"].button, controlsContainer);
|
AnimationUtils.setupAutoHideAnimation(playerButtons["info"].button, controlsContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1671,13 +1705,37 @@ function clearSponsorTimes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//if skipNotice is null, it will not affect the UI
|
//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<void> {
|
||||||
if (skipNotice !== null && skipNotice !== undefined) {
|
if (skipNotice !== null && skipNotice !== undefined) {
|
||||||
//add loading info
|
//add loading info
|
||||||
skipNotice.addVoteButtonInfo.bind(skipNotice)(chrome.i18n.getMessage("Loading"))
|
skipNotice.addVoteButtonInfo.bind(skipNotice)(chrome.i18n.getMessage("Loading"))
|
||||||
skipNotice.setNoticeInfoMessage.bind(skipNotice)();
|
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<VoteResponse> {
|
||||||
const sponsorIndex = utils.getSponsorIndexFromUUID(sponsorTimes, UUID);
|
const sponsorIndex = utils.getSponsorIndexFromUUID(sponsorTimes, UUID);
|
||||||
|
|
||||||
// Don't vote for preview sponsors
|
// Don't vote for preview sponsors
|
||||||
@@ -1697,33 +1755,14 @@ function vote(type: number, UUID: SegmentUUID, category?: Category, skipNotice?:
|
|||||||
|
|
||||||
Config.config.skipCount = Config.config.skipCount + factor;
|
Config.config.skipCount = Config.config.skipCount + factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.runtime.sendMessage({
|
return new Promise((resolve) => {
|
||||||
message: "submitVote",
|
chrome.runtime.sendMessage({
|
||||||
type: type,
|
message: "submitVote",
|
||||||
UUID: UUID,
|
type: type,
|
||||||
category: category
|
UUID: UUID,
|
||||||
}, function(response) {
|
category: category
|
||||||
if (response != undefined) {
|
}, resolve);
|
||||||
//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)();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1766,7 +1805,7 @@ function submitSponsorTimes() {
|
|||||||
async function sendSubmitMessage() {
|
async function sendSubmitMessage() {
|
||||||
// Add loading animation
|
// Add loading animation
|
||||||
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker.svg");
|
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
|
//check if a sponsor exceeds the duration of the video
|
||||||
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
|
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
|
||||||
@@ -1838,7 +1877,7 @@ async function sendSubmitMessage() {
|
|||||||
if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a warning from a moderator.")) {
|
if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a warning from a moderator.")) {
|
||||||
Chat.openWarningChat(response.responseText);
|
Chat.openWarningChat(response.responseText);
|
||||||
} else {
|
} else {
|
||||||
alert(utils.getErrorMessage(response.status, response.responseText));
|
alert(GenericUtils.getErrorMessage(response.status, response.responseText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
|
import { ActionType } from "../types";
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export interface PreviewBarSegment {
|
|||||||
segment: [number, number];
|
segment: [number, number];
|
||||||
category: string;
|
category: string;
|
||||||
unsubmitted: boolean;
|
unsubmitted: boolean;
|
||||||
|
actionType: ActionType;
|
||||||
showLarger: boolean;
|
showLarger: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { SponsorTime } from "../types";
|
|||||||
import { getSkippingText } from "../utils/categoryUtils";
|
import { getSkippingText } from "../utils/categoryUtils";
|
||||||
|
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
|
import { AnimationUtils } from "../utils/animationUtils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
export interface SkipButtonControlBarProps {
|
export interface SkipButtonControlBarProps {
|
||||||
@@ -80,9 +81,9 @@ export class SkipButtonControlBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.onMobileYouTube) {
|
if (!this.onMobileYouTube) {
|
||||||
utils.setupAutoHideAnimation(this.skipIcon, mountingContainer, false, false);
|
AnimationUtils.setupAutoHideAnimation(this.skipIcon, mountingContainer, false, false);
|
||||||
} else {
|
} 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.hideButton = hide;
|
||||||
this.showButton = show;
|
this.showButton = show;
|
||||||
}
|
}
|
||||||
@@ -104,7 +105,7 @@ export class SkipButtonControlBar {
|
|||||||
|
|
||||||
this.refreshText();
|
this.refreshText();
|
||||||
this.textContainer?.classList?.remove("hidden");
|
this.textContainer?.classList?.remove("hidden");
|
||||||
utils.disableAutoHideAnimation(this.skipIcon);
|
AnimationUtils.disableAutoHideAnimation(this.skipIcon);
|
||||||
|
|
||||||
this.startTimer();
|
this.startTimer();
|
||||||
}
|
}
|
||||||
@@ -160,7 +161,7 @@ export class SkipButtonControlBar {
|
|||||||
|
|
||||||
this.getChapterPrefix()?.classList?.add("hidden");
|
this.getChapterPrefix()?.classList?.add("hidden");
|
||||||
|
|
||||||
utils.enableAutoHideAnimation(this.skipIcon);
|
AnimationUtils.enableAutoHideAnimation(this.skipIcon);
|
||||||
if (this.onMobileYouTube) {
|
if (this.onMobileYouTube) {
|
||||||
this.hideButton();
|
this.hideButton();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,3 +61,8 @@ export type MessageResponse =
|
|||||||
| IsChannelWhitelistedResponse
|
| IsChannelWhitelistedResponse
|
||||||
| Record<string, never>;
|
| Record<string, never>;
|
||||||
|
|
||||||
|
export interface VoteResponse {
|
||||||
|
successType: number;
|
||||||
|
statusCode: number;
|
||||||
|
responseText: string;
|
||||||
|
}
|
||||||
19
src/popup.ts
19
src/popup.ts
@@ -1,10 +1,12 @@
|
|||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
|
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
import { SponsorTime, SponsorHideType, CategoryActionType } from "./types";
|
import { SponsorTime, SponsorHideType, CategoryActionType, ActionType } from "./types";
|
||||||
import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes";
|
import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes";
|
||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
import { getCategoryActionType } from "./utils/categoryUtils";
|
import { getCategoryActionType } from "./utils/categoryUtils";
|
||||||
|
import { AnimationUtils } from "./utils/animationUtils";
|
||||||
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
interface MessageListener {
|
interface MessageListener {
|
||||||
@@ -405,10 +407,15 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
|
|
||||||
const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo);
|
const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo);
|
||||||
const segmentTimeFromToNode = document.createElement("div");
|
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
|
(getCategoryActionType(segmentTimes[i].category) !== CategoryActionType.POI
|
||||||
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true)
|
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true)
|
||||||
: "");
|
: "");
|
||||||
|
}
|
||||||
|
|
||||||
segmentTimeFromToNode.style.margin = "5px";
|
segmentTimeFromToNode.style.margin = "5px";
|
||||||
|
|
||||||
sponsorTimeButton.appendChild(categoryColorCircle);
|
sponsorTimeButton.appendChild(categoryColorCircle);
|
||||||
@@ -444,7 +451,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
|
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
|
||||||
uuidButton.addEventListener("click", () => {
|
uuidButton.addEventListener("click", () => {
|
||||||
navigator.clipboard.writeText(UUID);
|
navigator.clipboard.writeText(UUID);
|
||||||
const stopAnimation = utils.applyLoadingAnimation(uuidButton, 0.3);
|
const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3);
|
||||||
stopAnimation();
|
stopAnimation();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -550,7 +557,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
|
|
||||||
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
|
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
PageElements.setUsernameStatus.innerText = utils.getErrorMessage(response.status, response.responseText);
|
PageElements.setUsernameStatus.innerText = GenericUtils.getErrorMessage(response.status, response.responseText);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -591,7 +598,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
//success (treat rate limits as a success)
|
//success (treat rate limits as a success)
|
||||||
addVoteMessage(chrome.i18n.getMessage("voted"), UUID);
|
addVoteMessage(chrome.i18n.getMessage("voted"), UUID);
|
||||||
} else if (response.successType == -1) {
|
} else if (response.successType == -1) {
|
||||||
addVoteMessage(utils.getErrorMessage(response.statusCode, response.responseText), UUID);
|
addVoteMessage(GenericUtils.getErrorMessage(response.statusCode, response.responseText), UUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -694,7 +701,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshSegments() {
|
function refreshSegments() {
|
||||||
const stopAnimation = utils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
|
const stopAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
|
||||||
|
|
||||||
messageHandler.query({
|
messageHandler.query({
|
||||||
active: true,
|
active: true,
|
||||||
|
|||||||
98
src/render/CategoryPill.tsx
Normal file
98
src/render/CategoryPill.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import CategoryPillComponent, { CategoryPillState } from "../components/CategoryPillComponent";
|
||||||
|
import { VoteResponse } from "../messageTypes";
|
||||||
|
import { Category, SegmentUUID, SponsorTime } from "../types";
|
||||||
|
import { GenericUtils } from "../utils/genericUtils";
|
||||||
|
|
||||||
|
export class CategoryPill {
|
||||||
|
container: HTMLElement;
|
||||||
|
ref: React.RefObject<CategoryPillComponent>;
|
||||||
|
|
||||||
|
unsavedState: CategoryPillState;
|
||||||
|
|
||||||
|
mutationObserver?: MutationObserver;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.ref = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
async attachToPage(onMobileYouTube: boolean, onInvidious: boolean,
|
||||||
|
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>): Promise<void> {
|
||||||
|
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";
|
||||||
|
this.container.style.display = "relative";
|
||||||
|
|
||||||
|
referenceNode.prepend(this.container);
|
||||||
|
referenceNode.style.display = "flex";
|
||||||
|
|
||||||
|
if (this.ref.current) {
|
||||||
|
this.unsavedState = this.ref.current.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<CategoryPillComponent ref={this.ref} vote={vote} />,
|
||||||
|
this.container
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.unsavedState) {
|
||||||
|
this.ref.current?.setState(this.unsavedState);
|
||||||
|
this.unsavedState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onMobileYouTube) {
|
||||||
|
if (this.mutationObserver) {
|
||||||
|
this.mutationObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mutationObserver = new MutationObserver(() => this.attachToPage(onMobileYouTube, onInvidious, vote));
|
||||||
|
|
||||||
|
this.mutationObserver.observe(referenceNode, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
ReactDOM.unmountComponentAtNode(this.container);
|
||||||
|
this.container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisibility(show: boolean): void {
|
||||||
|
const newState = {
|
||||||
|
show,
|
||||||
|
open: show ? this.ref.current?.state.open : false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.ref.current) {
|
||||||
|
this.ref.current?.setState(newState);
|
||||||
|
} else {
|
||||||
|
this.unsavedState = newState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSegment(segment: SponsorTime): void {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,10 @@ import * as ReactDOM from "react-dom";
|
|||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
import SkipNoticeComponent, { SkipNoticeAction } from "../components/SkipNoticeComponent";
|
import SkipNoticeComponent from "../components/SkipNoticeComponent";
|
||||||
import { SponsorTime, ContentContainer, NoticeVisbilityMode } from "../types";
|
import { SponsorTime, ContentContainer, NoticeVisbilityMode } from "../types";
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
|
import { SkipNoticeAction } from "../utils/noticeUtils";
|
||||||
|
|
||||||
class SkipNotice {
|
class SkipNotice {
|
||||||
segments: SponsorTime[];
|
segments: SponsorTime[];
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ export enum CategoryActionType {
|
|||||||
|
|
||||||
export enum ActionType {
|
export enum ActionType {
|
||||||
Skip = "skip",
|
Skip = "skip",
|
||||||
Mute = "mute"
|
Mute = "mute",
|
||||||
|
Full = "full"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionTypes = [ActionType.Skip, ActionType.Mute];
|
export const ActionTypes = [ActionType.Skip, ActionType.Mute];
|
||||||
|
|||||||
114
src/utils.ts
114
src/utils.ts
@@ -3,6 +3,7 @@ import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContaine
|
|||||||
|
|
||||||
import * as CompileConfig from "../config.json";
|
import * as CompileConfig from "../config.json";
|
||||||
import { findValidElementFromSelector } from "./utils/pageUtils";
|
import { findValidElementFromSelector } from "./utils/pageUtils";
|
||||||
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
|
|
||||||
export default class Utils {
|
export default class Utils {
|
||||||
|
|
||||||
@@ -24,27 +25,8 @@ export default class Utils {
|
|||||||
this.backgroundScriptContainer = backgroundScriptContainer;
|
this.backgroundScriptContainer = backgroundScriptContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Function that can be used to wait for a condition before returning. */
|
|
||||||
async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
||||||
return await new Promise((resolve, reject) => {
|
return GenericUtils.wait(condition, timeout, check);
|
||||||
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();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {
|
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {
|
||||||
@@ -162,75 +144,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.
|
* Merges any overlapping timestamp ranges into single segments and returns them as a new array.
|
||||||
*/
|
*/
|
||||||
@@ -362,29 +275,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
|
* Sends a request to a custom server
|
||||||
*
|
*
|
||||||
|
|||||||
78
src/utils/animationUtils.ts
Normal file
78
src/utils/animationUtils.ts
Normal file
@@ -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<void> {
|
||||||
|
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
|
||||||
|
};
|
||||||
50
src/utils/genericUtils.ts
Normal file
50
src/utils/genericUtils.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/** Function that can be used to wait for a condition before returning. */
|
||||||
|
async function wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
getErrorMessage
|
||||||
|
}
|
||||||
21
src/utils/noticeUtils.ts
Normal file
21
src/utils/noticeUtils.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user