mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2026-01-27 12:50:51 +03:00
Merge pull request #1446 from mini-bomba/clearUnsubmittedSegments
Add a section in options for unsubmitted segments
This commit is contained in:
@@ -1153,5 +1153,60 @@
|
|||||||
},
|
},
|
||||||
"chaptersPage1": {
|
"chaptersPage1": {
|
||||||
"message": "SponsorBlock crowd-sourced chapters feature is only available to people who purchase a license, or for people who are granted access for free due their past contributions"
|
"message": "SponsorBlock crowd-sourced chapters feature is only available to people who purchase a license, or for people who are granted access for free due their past contributions"
|
||||||
|
},
|
||||||
|
"unsubmittedSegmentCounts": {
|
||||||
|
"message": "You currently have {0} on {1}",
|
||||||
|
"description": "Example: You currently have 12 unsubmitted segments on 5 videos"
|
||||||
|
},
|
||||||
|
"unsubmittedSegmentCountsZero": {
|
||||||
|
"message": "You currently have no unsubmitted segments",
|
||||||
|
"description": "Replaces 'unsubmittedSegmentCounts' string when there are no unsubmitted segments"
|
||||||
|
},
|
||||||
|
"unsubmittedSegmentsSingular": {
|
||||||
|
"message": "unsubmitted segment",
|
||||||
|
"description": "Example: You currently have 1 *unsubmitted segment* on 1 video"
|
||||||
|
},
|
||||||
|
"unsubmittedSegmentsPlural": {
|
||||||
|
"message": "unsubmitted segments",
|
||||||
|
"description": "Example: You currently have 12 *unsubmitted segments* on 5 videos"
|
||||||
|
},
|
||||||
|
"videosSingular": {
|
||||||
|
"message": "video",
|
||||||
|
"description": "Example: You currently have 3 unsubmitted segments on 1 *video*"
|
||||||
|
},
|
||||||
|
"videosPlural": {
|
||||||
|
"message": "videos",
|
||||||
|
"description": "Example: You currently have 12 unsubmitted segments on 5 *videos*"
|
||||||
|
},
|
||||||
|
"clearUnsubmittedSegments": {
|
||||||
|
"message": "Clear all segments",
|
||||||
|
"description": "Label for a button in settings"
|
||||||
|
},
|
||||||
|
"clearUnsubmittedSegmentsConfirm": {
|
||||||
|
"message": "Are you sure you want to clear all your unsubmitted segments?",
|
||||||
|
"description": "Confirmation message for the Clear unsubmitted segments button"
|
||||||
|
},
|
||||||
|
"showUnsubmittedSegments": {
|
||||||
|
"message": "Show segments",
|
||||||
|
"description": "Show/hide button for the unsubmitted segments list"
|
||||||
|
},
|
||||||
|
"hideUnsubmittedSegments": {
|
||||||
|
"message": "Hide segments",
|
||||||
|
"description": "Show/hide button for the unsubmitted segments list"
|
||||||
|
},
|
||||||
|
"videoID": {
|
||||||
|
"message": "Video ID",
|
||||||
|
"description": "Header of the unsubmitted segments list"
|
||||||
|
},
|
||||||
|
"segmentCount": {
|
||||||
|
"message": "Segment Count",
|
||||||
|
"description": "Header of the unsubmitted segments list"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"message": "Actions",
|
||||||
|
"description": "Header of the unsubmitted segments list"
|
||||||
|
},
|
||||||
|
"exportSegmentsAsURL": {
|
||||||
|
"message": "Share as URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,6 +362,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-type="react-UnsubmittedVideosComponent"></div>
|
||||||
|
|
||||||
<div data-type="private-text-change" data-sync="*" data-confirm-message="exportOptionsWarning">
|
<div data-type="private-text-change" data-sync="*" data-confirm-message="exportOptionsWarning">
|
||||||
<h2>__MSG_exportOptions__</h2>
|
<h2>__MSG_exportOptions__</h2>
|
||||||
@@ -490,7 +492,7 @@
|
|||||||
|
|
||||||
<div class="small-description">__MSG_copyDebugInformationOptions__</div>
|
<div class="small-description">__MSG_copyDebugInformationOptions__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-type="toggle" data-sync="testingServer" data-confirm-message="testingServerWarning" data-no-safari="true">
|
<div data-type="toggle" data-sync="testingServer" data-confirm-message="testingServerWarning" data-no-safari="true">
|
||||||
<div class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
|
|||||||
72
src/components/options/UnsubmittedVideoListComponent.tsx
Normal file
72
src/components/options/UnsubmittedVideoListComponent.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import Config from "../../config";
|
||||||
|
import UnsubmittedVideoListItem from "./UnsubmittedVideoListItem";
|
||||||
|
|
||||||
|
export interface UnsubmittedVideoListProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnsubmittedVideoListState {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnsubmittedVideoListComponent extends React.Component<UnsubmittedVideoListProps, UnsubmittedVideoListState> {
|
||||||
|
|
||||||
|
constructor(props: UnsubmittedVideoListProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Setup state
|
||||||
|
this.state = {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
// Render nothing if there are no unsubmitted segments
|
||||||
|
if (Object.keys(Config.config.unsubmittedSegments).length == 0)
|
||||||
|
return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table id="unsubmittedVideosList"
|
||||||
|
className="categoryChooserTable"
|
||||||
|
style={{marginTop: "10px"}} >
|
||||||
|
<tbody>
|
||||||
|
{/* Headers */}
|
||||||
|
<tr id="UnsubmittedVideosListHeader"
|
||||||
|
className="categoryTableElement categoryTableHeader">
|
||||||
|
<th id="UnsubmittedVideoID">
|
||||||
|
{chrome.i18n.getMessage("videoID")}
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th id="UnsubmittedSegmentCount">
|
||||||
|
{chrome.i18n.getMessage("segmentCount")}
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th id="UnsubmittedVideoActions">
|
||||||
|
{chrome.i18n.getMessage("actions")}
|
||||||
|
</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{this.getUnsubmittedVideos()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnsubmittedVideos(): JSX.Element[] {
|
||||||
|
const elements: JSX.Element[] = [];
|
||||||
|
|
||||||
|
for (const videoID of Object.keys(Config.config.unsubmittedSegments)) {
|
||||||
|
elements.push(
|
||||||
|
<UnsubmittedVideoListItem videoID={videoID} key={videoID}>
|
||||||
|
</UnsubmittedVideoListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnsubmittedVideoListComponent;
|
||||||
95
src/components/options/UnsubmittedVideoListItem.tsx
Normal file
95
src/components/options/UnsubmittedVideoListItem.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import Config from "../../config";
|
||||||
|
import { exportTimes, exportTimesAsHashParam } from "../../utils/exporter";
|
||||||
|
|
||||||
|
export interface UnsubmittedVideosListItemProps {
|
||||||
|
videoID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnsubmittedVideosListItemState {
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnsubmittedVideoListItem extends React.Component<UnsubmittedVideosListItemProps, UnsubmittedVideosListItemState> {
|
||||||
|
|
||||||
|
constructor(props: UnsubmittedVideosListItemProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Setup state
|
||||||
|
this.state = {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
const segmentCount = Config.config.unsubmittedSegments[this.props.videoID]?.length ?? 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr id={this.props.videoID + "UnsubmittedSegmentsRow"}
|
||||||
|
className="categoryTableElement">
|
||||||
|
<td id={this.props.videoID + "UnsubmittedVideoID"}
|
||||||
|
className="categoryTableLabel">
|
||||||
|
<a href={`https://youtu.be/${this.props.videoID}`}
|
||||||
|
target="_blank" rel="noreferrer">
|
||||||
|
{this.props.videoID}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td id={this.props.videoID + "UnsubmittedSegmentCount"}>
|
||||||
|
{segmentCount}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td id={this.props.videoID + "UnsubmittedVideoActions"}>
|
||||||
|
<div id={this.props.videoID + "ExportSegmentsAction"}
|
||||||
|
className="option-button inline low-profile"
|
||||||
|
onClick={this.exportSegments.bind(this)}>
|
||||||
|
{chrome.i18n.getMessage("exportSegments")}
|
||||||
|
</div>
|
||||||
|
{" "}
|
||||||
|
<div id={this.props.videoID + "ExportSegmentsAsURLAction"}
|
||||||
|
className="option-button inline low-profile"
|
||||||
|
onClick={this.exportSegmentsAsURL.bind(this)}>
|
||||||
|
{chrome.i18n.getMessage("exportSegmentsAsURL")}
|
||||||
|
</div>
|
||||||
|
{" "}
|
||||||
|
<div id={this.props.videoID + "ClearSegmentsAction"}
|
||||||
|
className="option-button inline low-profile"
|
||||||
|
onClick={this.clearSegments.bind(this)}>
|
||||||
|
{chrome.i18n.getMessage("clearTimes")}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSegments(): void {
|
||||||
|
if (confirm(chrome.i18n.getMessage("clearThis"))) {
|
||||||
|
delete Config.config.unsubmittedSegments[this.props.videoID];
|
||||||
|
Config.forceSyncUpdate("unsubmittedSegments");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exportSegments(): void {
|
||||||
|
this.copyToClipboard(exportTimes(Config.config.unsubmittedSegments[this.props.videoID]));
|
||||||
|
}
|
||||||
|
|
||||||
|
exportSegmentsAsURL(): void {
|
||||||
|
this.copyToClipboard(`https://youtube.com/watch?v=${this.props.videoID}${exportTimesAsHashParam(Config.config.unsubmittedSegments[this.props.videoID])}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard(text: string): void {
|
||||||
|
navigator.clipboard.writeText(text)
|
||||||
|
.then(() => {
|
||||||
|
alert(chrome.i18n.getMessage("CopiedExclamation"));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnsubmittedVideoListItem;
|
||||||
55
src/components/options/UnsubmittedVideosComponent.tsx
Normal file
55
src/components/options/UnsubmittedVideosComponent.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Config from "../../config";
|
||||||
|
import UnsubmittedVideoListComponent from "./UnsubmittedVideoListComponent";
|
||||||
|
|
||||||
|
export interface UnsubmittedVideosProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnsubmittedVideosState {
|
||||||
|
tableVisible: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnsubmittedVideosComponent extends React.Component<UnsubmittedVideosProps, UnsubmittedVideosState> {
|
||||||
|
|
||||||
|
constructor(props: UnsubmittedVideosProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
tableVisible: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
const videoCount = Object.keys(Config.config.unsubmittedSegments).length;
|
||||||
|
const segmentCount = Object.values(Config.config.unsubmittedSegments).reduce((acc: number, vid: Array<unknown>) => acc + vid.length, 0);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div style={{marginBottom: "10px"}}>
|
||||||
|
{segmentCount == 0 ?
|
||||||
|
chrome.i18n.getMessage("unsubmittedSegmentCountsZero") :
|
||||||
|
chrome.i18n.getMessage("unsubmittedSegmentCounts")
|
||||||
|
.replace("{0}", `${segmentCount} ${chrome.i18n.getMessage("unsubmittedSegments" + (segmentCount == 1 ? "Singular" : "Plural"))}`)
|
||||||
|
.replace("{1}", `${videoCount} ${chrome.i18n.getMessage("videos" + (videoCount == 1 ? "Singular" : "Plural"))}`)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{videoCount > 0 && <div className="option-button inline" onClick={() => this.setState({tableVisible: !this.state.tableVisible})}>
|
||||||
|
{chrome.i18n.getMessage(this.state.tableVisible ? "hideUnsubmittedSegments" : "showUnsubmittedSegments")}
|
||||||
|
</div>}
|
||||||
|
{" "}
|
||||||
|
<div className="option-button inline" onClick={this.clearAllSegments}>
|
||||||
|
{chrome.i18n.getMessage("clearUnsubmittedSegments")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.state.tableVisible && <UnsubmittedVideoListComponent/>}
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllSegments(): void {
|
||||||
|
if (confirm(chrome.i18n.getMessage("clearUnsubmittedSegmentsConfirm")))
|
||||||
|
Config.config.unsubmittedSegments = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnsubmittedVideosComponent;
|
||||||
@@ -1,11 +1,26 @@
|
|||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, VideoInfo, StorageChangesObject, ChannelIDInfo, ChannelIDStatus, SponsorSourceType, SegmentUUID, Category, SkipToTimeParams, ToggleSkippable, ActionType, ScheduledTime, HashedValue } from "./types";
|
import {
|
||||||
|
ActionType,
|
||||||
import { ContentContainer, Keybind } from "./types";
|
Category,
|
||||||
|
CategorySkipOption,
|
||||||
|
ChannelIDInfo,
|
||||||
|
ChannelIDStatus,
|
||||||
|
ContentContainer,
|
||||||
|
HashedValue,
|
||||||
|
Keybind,
|
||||||
|
ScheduledTime,
|
||||||
|
SegmentUUID,
|
||||||
|
SkipToTimeParams,
|
||||||
|
SponsorHideType,
|
||||||
|
SponsorSourceType,
|
||||||
|
SponsorTime,
|
||||||
|
StorageChangesObject,
|
||||||
|
ToggleSkippable,
|
||||||
|
VideoID,
|
||||||
|
VideoInfo,
|
||||||
|
} from "./types";
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
const utils = new Utils();
|
import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar";
|
||||||
|
|
||||||
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";
|
||||||
@@ -22,6 +37,8 @@ import { importTimes } from "./utils/exporter";
|
|||||||
import { ChapterVote } from "./render/ChapterVote";
|
import { ChapterVote } from "./render/ChapterVote";
|
||||||
import { openWarningDialog } from "./utils/warnings";
|
import { openWarningDialog } from "./utils/warnings";
|
||||||
|
|
||||||
|
const utils = new Utils();
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
@@ -247,7 +264,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
|||||||
let addedSegments = false;
|
let addedSegments = false;
|
||||||
for (const segment of importedSegments) {
|
for (const segment of importedSegments) {
|
||||||
if (!sponsorTimesSubmitting.concat(sponsorTimes ?? []).some(
|
if (!sponsorTimesSubmitting.concat(sponsorTimes ?? []).some(
|
||||||
(s) => Math.abs(s.segment[0] - segment.segment[0]) < 1
|
(s) => Math.abs(s.segment[0] - segment.segment[0]) < 1
|
||||||
&& Math.abs(s.segment[1] - segment.segment[1]) < 1)
|
&& Math.abs(s.segment[1] - segment.segment[1]) < 1)
|
||||||
&& (segment.category !== "chapter" || utils.getCategorySelection("chapter"))) {
|
&& (segment.category !== "chapter" || utils.getCategorySelection("chapter"))) {
|
||||||
sponsorTimesSubmitting.push(segment);
|
sponsorTimesSubmitting.push(segment);
|
||||||
@@ -561,7 +578,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
|||||||
const videoID = sponsorVideoID;
|
const videoID = sponsorVideoID;
|
||||||
const skipBuffer = 0.003;
|
const skipBuffer = 0.003;
|
||||||
|
|
||||||
if (videoMuted && !inMuteSegment(currentTime, skipInfo.index !== -1
|
if (videoMuted && !inMuteSegment(currentTime, skipInfo.index !== -1
|
||||||
&& timeUntilSponsor < skipBuffer && shouldAutoSkip(currentSkip))) {
|
&& timeUntilSponsor < skipBuffer && shouldAutoSkip(currentSkip))) {
|
||||||
video.muted = false;
|
video.muted = false;
|
||||||
videoMuted = false;
|
videoMuted = false;
|
||||||
@@ -628,8 +645,8 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (utils.getCategorySelection(currentSkip.category)?.option === CategorySkipOption.ManualSkip
|
if (utils.getCategorySelection(currentSkip.category)?.option === CategorySkipOption.ManualSkip
|
||||||
|| currentSkip.actionType === ActionType.Mute) {
|
|| currentSkip.actionType === ActionType.Mute) {
|
||||||
forcedSkipTime = skipTime[0] + 0.001;
|
forcedSkipTime = skipTime[0] + 0.001;
|
||||||
} else {
|
} else {
|
||||||
@@ -672,7 +689,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
|
|||||||
}, 1);
|
}, 1);
|
||||||
} else {
|
} else {
|
||||||
logDebug(`Starting timeout to skip ${video.currentTime} to skip at ${skipTime[0]}`);
|
logDebug(`Starting timeout to skip ${video.currentTime} to skip at ${skipTime[0]}`);
|
||||||
|
|
||||||
// Schedule for right before to be more precise than normal timeout
|
// Schedule for right before to be more precise than normal timeout
|
||||||
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 150));
|
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 150));
|
||||||
}
|
}
|
||||||
@@ -692,8 +709,8 @@ function getVirtualTime(): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function inMuteSegment(currentTime: number, includeOverlap: boolean): boolean {
|
function inMuteSegment(currentTime: number, includeOverlap: boolean): boolean {
|
||||||
const checkFunction = (segment) => segment.actionType === ActionType.Mute
|
const checkFunction = (segment) => segment.actionType === ActionType.Mute
|
||||||
&& segment.segment[0] <= currentTime
|
&& segment.segment[0] <= currentTime
|
||||||
&& (segment.segment[1] > currentTime || (includeOverlap && segment.segment[1] + 0.02 > currentTime));
|
&& (segment.segment[1] > currentTime || (includeOverlap && segment.segment[1] + 0.02 > currentTime));
|
||||||
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
|
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
|
||||||
}
|
}
|
||||||
@@ -773,7 +790,7 @@ function setupVideoListeners() {
|
|||||||
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
||||||
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
||||||
// gone back to the begining
|
// gone back to the begining
|
||||||
if (video.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
|
if (video.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
|
||||||
&& video.currentTime === 0) return;
|
&& video.currentTime === 0) return;
|
||||||
|
|
||||||
updateVirtualTime();
|
updateVirtualTime();
|
||||||
@@ -804,7 +821,7 @@ function setupVideoListeners() {
|
|||||||
video.addEventListener('playing', () => {
|
video.addEventListener('playing', () => {
|
||||||
updateVirtualTime();
|
updateVirtualTime();
|
||||||
lastPausedAtZero = false;
|
lastPausedAtZero = false;
|
||||||
|
|
||||||
if (startedWaiting) {
|
if (startedWaiting) {
|
||||||
startedWaiting = false;
|
startedWaiting = false;
|
||||||
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||||
@@ -1062,7 +1079,7 @@ function retryFetch(errorCode: number): void {
|
|||||||
sponsorDataFound = false;
|
sponsorDataFound = false;
|
||||||
|
|
||||||
if (errorCode !== 404 && retryCount > 1) {
|
if (errorCode !== 404 && retryCount > 1) {
|
||||||
// Too many errors (50x), give up
|
// Too many errors (50x), give up
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,7 +1087,7 @@ function retryFetch(errorCode: number): void {
|
|||||||
|
|
||||||
const delay = errorCode === 404 ? (10000 + Math.random() * 30000) : (2000 + Math.random() * 10000);
|
const delay = errorCode === 404 ? (10000 + Math.random() * 30000) : (2000 + Math.random() * 10000);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (sponsorVideoID && sponsorTimes?.length === 0
|
if (sponsorVideoID && sponsorTimes?.length === 0
|
||||||
|| sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) {
|
|| sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) {
|
||||||
sponsorsLookup();
|
sponsorsLookup();
|
||||||
}
|
}
|
||||||
@@ -1334,7 +1351,7 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
|
|||||||
const minSponsorTimeIndexes = GenericUtils.indexesOf(sponsorStartTimes, Math.min(...sponsorStartTimesAfterCurrentTime));
|
const minSponsorTimeIndexes = GenericUtils.indexesOf(sponsorStartTimes, Math.min(...sponsorStartTimesAfterCurrentTime));
|
||||||
// Find auto skipping segments if possible, sort by duration otherwise
|
// Find auto skipping segments if possible, sort by duration otherwise
|
||||||
const minSponsorTimeIndex = minSponsorTimeIndexes.sort(
|
const minSponsorTimeIndex = minSponsorTimeIndexes.sort(
|
||||||
(a, b) => ((autoSkipSorter(submittedArray[a]) - autoSkipSorter(submittedArray[b]))
|
(a, b) => ((autoSkipSorter(submittedArray[a]) - autoSkipSorter(submittedArray[b]))
|
||||||
|| (submittedArray[a].segment[1] - submittedArray[a].segment[0]) - (submittedArray[b].segment[1] - submittedArray[b].segment[0])))[0] ?? -1;
|
|| (submittedArray[a].segment[1] - submittedArray[a].segment[0]) - (submittedArray[b].segment[1] - submittedArray[b].segment[0])))[0] ?? -1;
|
||||||
// Store extra indexes for the non-auto skipping segments if others occur at the exact same start time
|
// Store extra indexes for the non-auto skipping segments if others occur at the exact same start time
|
||||||
const extraIndexes = minSponsorTimeIndexes.filter((i) => i !== minSponsorTimeIndex && autoSkipSorter(submittedArray[i]) !== 0);
|
const extraIndexes = minSponsorTimeIndexes.filter((i) => i !== minSponsorTimeIndex && autoSkipSorter(submittedArray[i]) !== 0);
|
||||||
@@ -1448,7 +1465,7 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
|
|||||||
for (let i = 0; i < possibleTimes.length; i++) {
|
for (let i = 0; i < possibleTimes.length; i++) {
|
||||||
if ((minimum === undefined
|
if ((minimum === undefined
|
||||||
|| ((includeNonIntersectingSegments && possibleTimes[i].scheduledTime >= minimum)
|
|| ((includeNonIntersectingSegments && possibleTimes[i].scheduledTime >= minimum)
|
||||||
|| (includeIntersectingSegments && possibleTimes[i].scheduledTime < minimum && possibleTimes[i].segment[1] > minimum)))
|
|| (includeIntersectingSegments && possibleTimes[i].scheduledTime < minimum && possibleTimes[i].segment[1] > minimum)))
|
||||||
&& (!hideHiddenSponsors || possibleTimes[i].hidden === SponsorHideType.Visible)
|
&& (!hideHiddenSponsors || possibleTimes[i].hidden === SponsorHideType.Visible)
|
||||||
&& possibleTimes[i].segment.length === 2
|
&& possibleTimes[i].segment.length === 2
|
||||||
&& possibleTimes[i].actionType !== ActionType.Poi) {
|
&& possibleTimes[i].actionType !== ActionType.Poi) {
|
||||||
@@ -1576,13 +1593,13 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
|||||||
|
|
||||||
function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, unskipTime: number, startReskip: boolean) {
|
function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, unskipTime: number, startReskip: boolean) {
|
||||||
for (const skipNotice of skipNotices) {
|
for (const skipNotice of skipNotices) {
|
||||||
if (skippingSegments.length === skipNotice.segments.length
|
if (skippingSegments.length === skipNotice.segments.length
|
||||||
&& skippingSegments.every((segment) => skipNotice.segments.some((s) => s.UUID === segment.UUID))) {
|
&& skippingSegments.every((segment) => skipNotice.segments.some((s) => s.UUID === segment.UUID))) {
|
||||||
// Skip notice already exists
|
// Skip notice already exists
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime, startReskip);
|
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime, startReskip);
|
||||||
if (onMobileYouTube || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
|
if (onMobileYouTube || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
|
||||||
skipNotices.push(newSkipNotice);
|
skipNotices.push(newSkipNotice);
|
||||||
@@ -1596,7 +1613,7 @@ function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forc
|
|||||||
video.muted = false;
|
video.muted = false;
|
||||||
videoMuted = false;
|
videoMuted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceSeek || segment.actionType === ActionType.Skip) {
|
if (forceSeek || segment.actionType === ActionType.Skip) {
|
||||||
//add a tiny bit of time to make sure it is not skipped again
|
//add a tiny bit of time to make sure it is not skipped again
|
||||||
video.currentTime = unskipTime ?? segment.segment[0] + 0.001;
|
video.currentTime = unskipTime ?? segment.segment[0] + 0.001;
|
||||||
@@ -1869,10 +1886,10 @@ function openInfoMenu() {
|
|||||||
//hide info button
|
//hide info button
|
||||||
if (playerButtons.info) playerButtons.info.button.style.display = "none";
|
if (playerButtons.info) playerButtons.info.button.style.display = "none";
|
||||||
|
|
||||||
|
|
||||||
const popup = document.createElement("div");
|
const popup = document.createElement("div");
|
||||||
popup.id = "sponsorBlockPopupContainer";
|
popup.id = "sponsorBlockPopupContainer";
|
||||||
|
|
||||||
const frame = document.createElement("iframe");
|
const frame = document.createElement("iframe");
|
||||||
frame.width = "374";
|
frame.width = "374";
|
||||||
frame.height = "500";
|
frame.height = "500";
|
||||||
@@ -1891,7 +1908,7 @@ function openInfoMenu() {
|
|||||||
//old youtube theme
|
//old youtube theme
|
||||||
parentNode = document.getElementById("watch7-sidebar-contents");
|
parentNode = document.getElementById("watch7-sidebar-contents");
|
||||||
}
|
}
|
||||||
|
|
||||||
parentNode.insertBefore(popup, parentNode.firstChild);
|
parentNode.insertBefore(popup, parentNode.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2178,7 +2195,7 @@ function nextChapter(): void {
|
|||||||
.sort((a, b) => a.segment[1] - b.segment[1]);
|
.sort((a, b) => a.segment[1] - b.segment[1]);
|
||||||
if (chapters.length <= 0) return;
|
if (chapters.length <= 0) return;
|
||||||
|
|
||||||
const nextChapter = chapters.findIndex((time) => time.actionType === ActionType.Chapter
|
const nextChapter = chapters.findIndex((time) => time.actionType === ActionType.Chapter
|
||||||
&& time.segment[1] > video.currentTime);
|
&& time.segment[1] > video.currentTime);
|
||||||
if (nextChapter !== -1) {
|
if (nextChapter !== -1) {
|
||||||
reskipSponsorTime(chapters[nextChapter], true);
|
reskipSponsorTime(chapters[nextChapter], true);
|
||||||
@@ -2193,7 +2210,7 @@ function previousChapter(): void {
|
|||||||
|
|
||||||
// subtract 5 seconds to allow skipping back to the previous chapter if close to start of
|
// subtract 5 seconds to allow skipping back to the previous chapter if close to start of
|
||||||
// the current one
|
// the current one
|
||||||
const nextChapter = chapters.findIndex((time) => time.actionType === ActionType.Chapter
|
const nextChapter = chapters.findIndex((time) => time.actionType === ActionType.Chapter
|
||||||
&& time.segment[0] > video.currentTime - Math.min(5, time.segment[1] - time.segment[0]));
|
&& time.segment[0] > video.currentTime - Math.min(5, time.segment[1] - time.segment[0]));
|
||||||
const previousChapter = nextChapter !== -1 ? (nextChapter - 1) : (chapters.length - 1);
|
const previousChapter = nextChapter !== -1 ? (nextChapter - 1) : (chapters.length - 1);
|
||||||
if (previousChapter !== -1) {
|
if (previousChapter !== -1) {
|
||||||
@@ -2320,7 +2337,7 @@ function showTimeWithoutSkips(skippedDuration: number): void {
|
|||||||
|
|
||||||
display.appendChild(duration);
|
display.appendChild(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
const durationAfterSkips = GenericUtils.getFormattedTime(video?.duration - skippedDuration);
|
const durationAfterSkips = GenericUtils.getFormattedTime(video?.duration - skippedDuration);
|
||||||
|
|
||||||
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
|
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
|
||||||
@@ -2343,6 +2360,7 @@ function checkForPreloadedSegment() {
|
|||||||
UUID: GenericUtils.generateUserID() as SegmentUUID,
|
UUID: GenericUtils.generateUserID() as SegmentUUID,
|
||||||
category: segment.category ? segment.category : Config.config.defaultCategory,
|
category: segment.category ? segment.category : Config.config.defaultCategory,
|
||||||
actionType: segment.actionType ? segment.actionType : ActionType.Skip,
|
actionType: segment.actionType ? segment.actionType : ActionType.Skip,
|
||||||
|
description: segment.description ?? "",
|
||||||
source: SponsorSourceType.Local
|
source: SponsorSourceType.Local
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2362,7 +2380,7 @@ function checkForPreloadedSegment() {
|
|||||||
const navigationApiAvailable = "navigation" in window;
|
const navigationApiAvailable = "navigation" in window;
|
||||||
if (navigationApiAvailable) {
|
if (navigationApiAvailable) {
|
||||||
// TODO: Remove type cast once type declarations are updated
|
// TODO: Remove type cast once type declarations are updated
|
||||||
(window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", (e) =>
|
(window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", (e) =>
|
||||||
videoIDChange(getYouTubeVideoID(document, (e as unknown as Record<string, Record<string, string>>).destination.url)));
|
videoIDChange(getYouTubeVideoID(document, (e as unknown as Record<string, Record<string, string>>).destination.url)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ window.SB = Config;
|
|||||||
|
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
import CategoryChooser from "./render/CategoryChooser";
|
import CategoryChooser from "./render/CategoryChooser";
|
||||||
|
import UnsubmittedVideos from "./render/UnsubmittedVideos";
|
||||||
import KeybindComponent from "./components/options/KeybindComponent";
|
import KeybindComponent from "./components/options/KeybindComponent";
|
||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
import { localizeHtmlPage } from "./utils/pageUtils";
|
import { localizeHtmlPage } from "./utils/pageUtils";
|
||||||
@@ -103,7 +104,7 @@ async function init() {
|
|||||||
// Add click listener
|
// Add click listener
|
||||||
checkbox.addEventListener("click", async () => {
|
checkbox.addEventListener("click", async () => {
|
||||||
// Confirm if required
|
// Confirm if required
|
||||||
if (confirmMessage && ((confirmOnTrue && checkbox.checked) || (!confirmOnTrue && !checkbox.checked))
|
if (confirmMessage && ((confirmOnTrue && checkbox.checked) || (!confirmOnTrue && !checkbox.checked))
|
||||||
&& !confirm(chrome.i18n.getMessage(confirmMessage))){
|
&& !confirm(chrome.i18n.getMessage(confirmMessage))){
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
return;
|
return;
|
||||||
@@ -120,7 +121,7 @@ async function init() {
|
|||||||
if (!checkbox.checked) {
|
if (!checkbox.checked) {
|
||||||
// Enable the notice
|
// Enable the notice
|
||||||
Config.config["dontShowNotice"] = false;
|
Config.config["dontShowNotice"] = false;
|
||||||
|
|
||||||
const showNoticeSwitch = <HTMLInputElement> document.querySelector("[data-sync='dontShowNotice'] > div > label > input");
|
const showNoticeSwitch = <HTMLInputElement> document.querySelector("[data-sync='dontShowNotice'] > div > label > input");
|
||||||
showNoticeSwitch.checked = true;
|
showNoticeSwitch.checked = true;
|
||||||
}
|
}
|
||||||
@@ -162,7 +163,7 @@ async function init() {
|
|||||||
}
|
}
|
||||||
case "text-change": {
|
case "text-change": {
|
||||||
const textChangeInput = <HTMLInputElement> optionsElements[i].querySelector(".option-text-box");
|
const textChangeInput = <HTMLInputElement> optionsElements[i].querySelector(".option-text-box");
|
||||||
|
|
||||||
const textChangeSetButton = <HTMLElement> optionsElements[i].querySelector(".text-change-set");
|
const textChangeSetButton = <HTMLElement> optionsElements[i].querySelector(".text-change-set");
|
||||||
|
|
||||||
textChangeInput.value = Config.config[option];
|
textChangeInput.value = Config.config[option];
|
||||||
@@ -292,6 +293,9 @@ async function init() {
|
|||||||
case "react-CategoryChooserComponent":
|
case "react-CategoryChooserComponent":
|
||||||
new CategoryChooser(optionsElements[i]);
|
new CategoryChooser(optionsElements[i]);
|
||||||
break;
|
break;
|
||||||
|
case "react-UnsubmittedVideosComponent":
|
||||||
|
new UnsubmittedVideos(optionsElements[i])
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,8 +342,8 @@ function createStickyHeader() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle special cases where an option shouldn't show
|
* Handle special cases where an option shouldn't show
|
||||||
*
|
*
|
||||||
* @param {String} element
|
* @param {String} element
|
||||||
*/
|
*/
|
||||||
async function shouldHideOption(element: Element): Promise<boolean> {
|
async function shouldHideOption(element: Element): Promise<boolean> {
|
||||||
return (element.getAttribute("data-private-only") === "true" && !(await isIncognitoAllowed()))
|
return (element.getAttribute("data-private-only") === "true" && !(await isIncognitoAllowed()))
|
||||||
@@ -348,8 +352,8 @@ async function shouldHideOption(element: Element): Promise<boolean> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the config is updated
|
* Called when the config is updated
|
||||||
*
|
*
|
||||||
* @param {String} element
|
* @param {String} element
|
||||||
*/
|
*/
|
||||||
function optionsConfigUpdateListener() {
|
function optionsConfigUpdateListener() {
|
||||||
const optionsContainer = document.getElementById("options");
|
const optionsContainer = document.getElementById("options");
|
||||||
@@ -359,14 +363,18 @@ function optionsConfigUpdateListener() {
|
|||||||
switch (optionsElements[i].getAttribute("data-type")) {
|
switch (optionsElements[i].getAttribute("data-type")) {
|
||||||
case "display":
|
case "display":
|
||||||
updateDisplayElement(<HTMLElement> optionsElements[i])
|
updateDisplayElement(<HTMLElement> optionsElements[i])
|
||||||
|
break;
|
||||||
|
case "react-UnsubmittedVideosComponent":
|
||||||
|
new UnsubmittedVideos(optionsElements[i])
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will set display elements to the proper text
|
* Will set display elements to the proper text
|
||||||
*
|
*
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
function updateDisplayElement(element: HTMLElement) {
|
function updateDisplayElement(element: HTMLElement) {
|
||||||
const displayOption = element.getAttribute("data-sync")
|
const displayOption = element.getAttribute("data-sync")
|
||||||
@@ -393,9 +401,9 @@ function updateDisplayElement(element: HTMLElement) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the option to add Invidious instances
|
* Initializes the option to add Invidious instances
|
||||||
*
|
*
|
||||||
* @param element
|
* @param element
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
||||||
const textBox = <HTMLInputElement> element.querySelector(".option-text-box");
|
const textBox = <HTMLInputElement> element.querySelector(".option-text-box");
|
||||||
@@ -447,9 +455,9 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run when the invidious button is being initialized
|
* Run when the invidious button is being initialized
|
||||||
*
|
*
|
||||||
* @param checkbox
|
* @param checkbox
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
function invidiousInit(checkbox: HTMLInputElement, option: string) {
|
function invidiousInit(checkbox: HTMLInputElement, option: string) {
|
||||||
utils.containsInvidiousPermission().then((result) => {
|
utils.containsInvidiousPermission().then((result) => {
|
||||||
@@ -463,9 +471,9 @@ function invidiousInit(checkbox: HTMLInputElement, option: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run whenever the invidious checkbox is clicked
|
* Run whenever the invidious checkbox is clicked
|
||||||
*
|
*
|
||||||
* @param checkbox
|
* @param checkbox
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Promise<void> {
|
async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Promise<void> {
|
||||||
const enabled = await utils.applyInvidiousPermissions(checkbox.checked, option);
|
const enabled = await utils.applyInvidiousPermissions(checkbox.checked, option);
|
||||||
@@ -474,8 +482,8 @@ async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Pro
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Will trigger the textbox to appear to be able to change an option's text.
|
* Will trigger the textbox to appear to be able to change an option's text.
|
||||||
*
|
*
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
function activatePrivateTextChange(element: HTMLElement) {
|
function activatePrivateTextChange(element: HTMLElement) {
|
||||||
const button = element.querySelector(".trigger-button");
|
const button = element.querySelector(".trigger-button");
|
||||||
@@ -492,7 +500,7 @@ function activatePrivateTextChange(element: HTMLElement) {
|
|||||||
element.querySelector(".option-hidden-section").classList.remove("hidden");
|
element.querySelector(".option-hidden-section").classList.remove("hidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = Config.config[option];
|
let result = Config.config[option];
|
||||||
// See if anything extra must be done
|
// See if anything extra must be done
|
||||||
switch (option) {
|
switch (option) {
|
||||||
@@ -503,7 +511,7 @@ function activatePrivateTextChange(element: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textBox.value = result;
|
textBox.value = result;
|
||||||
|
|
||||||
const setButton = element.querySelector(".text-change-set");
|
const setButton = element.querySelector(".text-change-set");
|
||||||
setButton.addEventListener("click", async () => {
|
setButton.addEventListener("click", async () => {
|
||||||
setTextOption(option, element, textBox.value);
|
setTextOption(option, element, textBox.value);
|
||||||
@@ -532,7 +540,7 @@ function activatePrivateTextChange(element: HTMLElement) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to run when a textbox change is submitted
|
* Function to run when a textbox change is submitted
|
||||||
*
|
*
|
||||||
* @param option data-sync value
|
* @param option data-sync value
|
||||||
* @param element main container div
|
* @param element main container div
|
||||||
* @param value new text
|
* @param value new text
|
||||||
@@ -542,7 +550,7 @@ async function setTextOption(option: string, element: HTMLElement, value: string
|
|||||||
const confirmMessage = element.getAttribute("data-confirm-message");
|
const confirmMessage = element.getAttribute("data-confirm-message");
|
||||||
|
|
||||||
if (confirmMessage === null || confirm(chrome.i18n.getMessage(confirmMessage))) {
|
if (confirmMessage === null || confirm(chrome.i18n.getMessage(confirmMessage))) {
|
||||||
|
|
||||||
// See if anything extra must be done
|
// See if anything extra must be done
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case "*":
|
case "*":
|
||||||
@@ -554,13 +562,13 @@ async function setTextOption(option: string, element: HTMLElement, value: string
|
|||||||
|
|
||||||
if (newConfig.supportInvidious) {
|
if (newConfig.supportInvidious) {
|
||||||
const checkbox = <HTMLInputElement> document.querySelector("#support-invidious > div > label > input");
|
const checkbox = <HTMLInputElement> document.querySelector("#support-invidious > div > label > input");
|
||||||
|
|
||||||
checkbox.checked = true;
|
checkbox.checked = true;
|
||||||
await invidiousOnClick(checkbox, "supportInvidious");
|
await invidiousOnClick(checkbox, "supportInvidious");
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(chrome.i18n.getMessage("incorrectlyFormattedOptions"));
|
alert(chrome.i18n.getMessage("incorrectlyFormattedOptions"));
|
||||||
}
|
}
|
||||||
@@ -603,7 +611,7 @@ function uploadConfig(e) {
|
|||||||
/**
|
/**
|
||||||
* Validates the value used for the database server address.
|
* Validates the value used for the database server address.
|
||||||
* Returns null and alerts the user if there is an issue.
|
* Returns null and alerts the user if there is an issue.
|
||||||
*
|
*
|
||||||
* @param input Input server address
|
* @param input Input server address
|
||||||
*/
|
*/
|
||||||
function validateServerAddress(input: string): string {
|
function validateServerAddress(input: string): string {
|
||||||
@@ -637,7 +645,7 @@ function copyDebugOutputToClipboard() {
|
|||||||
|
|
||||||
// Sanitise sensitive user config values
|
// Sanitise sensitive user config values
|
||||||
delete output.config.userID;
|
delete output.config.userID;
|
||||||
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
|
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
|
||||||
? "Default server address" : "Custom server address";
|
? "Default server address" : "Custom server address";
|
||||||
output.config.invidiousInstances = output.config.invidiousInstances.length;
|
output.config.invidiousInstances = output.config.invidiousInstances.length;
|
||||||
output.config.whitelistedChannels = output.config.whitelistedChannels.length;
|
output.config.whitelistedChannels = output.config.whitelistedChannels.length;
|
||||||
|
|||||||
15
src/render/UnsubmittedVideos.tsx
Normal file
15
src/render/UnsubmittedVideos.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import UnsubmittedVideosComponent from "../components/options/UnsubmittedVideosComponent";
|
||||||
|
|
||||||
|
class UnsubmittedVideos {
|
||||||
|
|
||||||
|
constructor(element: Element) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<UnsubmittedVideosComponent/>,
|
||||||
|
element
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnsubmittedVideos;
|
||||||
@@ -73,4 +73,15 @@ export function importTimes(data: string, videoDuration: number): SponsorTime[]
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function exportTimesAsHashParam(segments: SponsorTime[]): string {
|
||||||
|
const hashparamSegments = segments.map(segment => ({
|
||||||
|
actionType: segment.actionType,
|
||||||
|
category: segment.category,
|
||||||
|
segment: segment.segment,
|
||||||
|
...(segment.description ? {description: segment.description} : {}) // don't include the description param if empty
|
||||||
|
}));
|
||||||
|
|
||||||
|
return `#segments=${JSON.stringify(hashparamSegments)}`;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user