From e20b60979c38873f792e0af879f49d0205e271f3 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 12 Oct 2021 23:33:41 -0400 Subject: [PATCH 001/101] Add initial chapter name rendering --- src/content.ts | 7 ++++- src/js-components/previewBar.ts | 47 +++++++++++++++++++++++++++++++++ src/types.ts | 4 ++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/content.ts b/src/content.ts index 4b480ddd..2b24039e 100644 --- a/src/content.ts +++ b/src/content.ts @@ -419,9 +419,12 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?: return; } - if (!video || video.paused) return; + if (!video) return; if (currentTime === undefined || currentTime === null) currentTime = video.currentTime; + previewBar.updateChapterText(sponsorTimes, currentTime); + + if (video.paused) return; if (videoMuted && !inMuteSegment(currentTime)) { video.muted = false; videoMuted = false; @@ -601,6 +604,8 @@ function setupVideoListeners() { lastCheckVideoTime = video.currentTime; startSponsorSchedule(); + } else { + previewBar.updateChapterText(sponsorTimes, video.currentTime); } if (!Config.config.dontShowNotice) { diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 191053b9..bdfa6989 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -6,7 +6,9 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd 'use strict'; import Config from "../config"; +import { ActionType, ActionTypes, SponsorTime } from "../types"; import Utils from "../utils"; +import { getSkippingText } from "../utils/categoryUtils"; const utils = new Utils(); const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; @@ -203,6 +205,51 @@ class PreviewBar { return bar; } + updateChapterText(segments: SponsorTime[], currentTime: number): void { + if (!segments) return; + + const activeSegments = segments.filter((segment) => { + return segment.segment[0] <= currentTime && segment.segment[1] >= currentTime; + }); + + this.setActiveSegments(activeSegments); + } + + /** + * Adds the text to the chapters slot if not filled by default + */ + private setActiveSegments(segments: SponsorTime[]): void { + const chaptersContainer = document.querySelector(".ytp-chapter-container") as HTMLDivElement; + + if (chaptersContainer) { + // TODO: Check if existing chapters exist (if big chapters menu is available?) + + if (segments.length > 0) { + chaptersContainer.style.removeProperty("display"); + + const chosenSegment = segments.sort((a, b) => { + if (a.actionType === ActionType.Chapter && b.actionType !== ActionType.Chapter) { + return -1; + } else if (a.actionType !== ActionType.Chapter && b.actionType === ActionType.Chapter) { + return 1; + } else { + return (a.segment[0] - b.segment[1]); + } + })[0]; + + const chapterButton = chaptersContainer.querySelector("button.ytp-chapter-title") as HTMLButtonElement; + chapterButton.classList.remove("ytp-chapter-container-disabled"); + chapterButton.disabled = false; + + const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement; + chapterTitle.innerText = chosenSegment.description || utils.shortCategoryName(chosenSegment.category); + } else { + // Hide chapters menu again + chaptersContainer.style.display = "none"; + } + } + } + remove(): void { this.container.remove(); diff --git a/src/types.ts b/src/types.ts index c60e52bb..cd2dca43 100644 --- a/src/types.ts +++ b/src/types.ts @@ -58,7 +58,8 @@ export enum CategoryActionType { export enum ActionType { Skip = "skip", - Mute = "mute" + Mute = "mute", + Chapter = "chapter", } export const ActionTypes = [ActionType.Skip, ActionType.Mute]; @@ -77,6 +78,7 @@ export interface SponsorTime { category: Category; actionType: ActionType; + description?: string; hidden?: SponsorHideType; source?: SponsorSourceType; From acd2720372837efca09d8ef11d13217be0b33d32 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 13 Oct 2021 23:50:17 -0400 Subject: [PATCH 002/101] Add messages for chapter --- public/_locales/en/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 3848137c..e63b0f06 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -578,6 +578,12 @@ "category_poi_highlight_description": { "message": "The part of the video that most people are looking for. Similar to \"Video starts at x\" comments." }, + "category_chapter": { + "message": "Chapter" + }, + "category_chapter_description": { + "message": "Custom named chapters describing major sections of a video." + }, "category_livestream_messages": { "message": "Livestream: Donation/Message Readings" }, From f2c1ee48946f4ea7d03e05f2f5fb1c365cd59bfb Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Thu, 14 Oct 2021 00:03:48 -0400 Subject: [PATCH 003/101] Use default action type from config --- config.json.example | 5 +++-- src/components/SponsorTimeEditComponent.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index ffc482a5..44797b0c 100644 --- a/config.json.example +++ b/config.json.example @@ -2,7 +2,7 @@ "serverAddress": "https://sponsor.ajay.app", "testingServerAddress": "https://sponsor.ajay.app/test", "serverAddressComment": "This specifies the default SponsorBlock server to connect to", - "categoryList": ["sponsor", "selfpromo", "interaction", "poi_highlight", "intro", "outro", "preview", "music_offtopic"], + "categoryList": ["sponsor", "selfpromo", "interaction", "poi_highlight", "intro", "outro", "preview", "music_offtopic", "chapter"], "categorySupport": { "sponsor": ["skip", "mute"], "selfpromo": ["skip", "mute"], @@ -11,6 +11,7 @@ "outro": ["skip"], "preview": ["skip"], "music_offtopic": ["skip"], - "poi_highlight": ["skip"] + "poi_highlight": ["skip"], + "chapter": ["chapter"] } } diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index a4422703..cc37d90e 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -365,9 +365,11 @@ class SponsorTimeEditComponent extends React.Component Date: Sat, 16 Oct 2021 01:36:44 -0400 Subject: [PATCH 004/101] Add chapter name option when submitting --- src/components/SponsorTimeEditComponent.tsx | 60 ++++++++++++++------- src/content.ts | 1 + 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 3466e78a..0e4f72d6 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -35,6 +35,7 @@ class SponsorTimeEditComponent extends React.Component; actionTypeOptionRef: React.RefObject; + descriptionOptionRef: React.RefObject; configUpdateListener: () => void; @@ -43,9 +44,11 @@ class SponsorTimeEditComponent extends React.Component {this.handleOnChange(0, e, sponsorTime, e.target.value)}} - onWheel={(e) => {this.changeTimesWhenScrolling(0, e, sponsorTime)}}> + onChange={(e) => this.handleOnChange(0, e, sponsorTime, e.target.value)} + onWheel={(e) => this.changeTimesWhenScrolling(0, e, sponsorTime)}> {getCategoryActionType(sponsorTime.category) === CategoryActionType.Skippable ? ( @@ -128,8 +131,8 @@ class SponsorTimeEditComponent extends React.Component {this.handleOnChange(1, e, sponsorTime, e.target.value)}} - onWheel={(e) => {this.changeTimesWhenScrolling(1, e, sponsorTime)}}> + onChange={(e) => this.handleOnChange(1, e, sponsorTime, e.target.value)} + onWheel={(e) => this.changeTimesWhenScrolling(1, e, sponsorTime)}> +
{timeDisplay} @@ -172,7 +175,7 @@ class SponsorTimeEditComponent extends React.Component + onChange={(event) => this.categorySelectionChange(event)}> {this.getCategoryOptions()} @@ -199,6 +202,19 @@ class SponsorTimeEditComponent extends React.Component ): ""} + {/* Chapter Name */} + {sponsorTime.actionType === ActionType.Chapter ? ( +
+ this.saveEditTimes()}> + +
+ ): ""} +
{/* Editing Tools */} @@ -243,14 +259,14 @@ class SponsorTimeEditComponent extends React.Component this.saveEditTimes()); } + changeTimesWhenScrolling(index: number, e: React.WheelEvent, sponsorTime: SponsorTime): void { let step = 0; // shift + ctrl = 1 @@ -273,6 +289,7 @@ class SponsorTimeEditComponent extends React.Component): void { + const chosenCategory = event.target.value as Category; + // See if show more categories was pressed if (event.target.value !== DEFAULT_CATEGORY && !Config.config.categorySelections.some((category) => category.name === event.target.value)) { - const chosenCategory = event.target.value; event.target.value = DEFAULT_CATEGORY; // Alert that they have to enable this category first @@ -338,16 +356,16 @@ class SponsorTimeEditComponent extends React.Component segment.category === event.target.value && i !== this.props.index)) { + .some((segment, i) => segment.category === chosenCategory && i !== this.props.index)) { alert(chrome.i18n.getMessage("poiOnlyOneSegment")); } } - + this.saveEditTimes(); } @@ -387,7 +405,7 @@ class SponsorTimeEditComponent extends React.Component this.saveEditTimes()); } toggleEditTime(): void { @@ -429,9 +447,15 @@ class SponsorTimeEditComponent extends React.Component Date: Tue, 26 Oct 2021 20:18:08 -0400 Subject: [PATCH 005/101] Add basic chapter rendering for segments --- src/js-components/previewBar.ts | 108 +++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 8 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 6838b887..4d186566 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -6,16 +6,16 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd 'use strict'; import Config from "../config"; -import { ActionType, ActionTypes, SponsorTime } from "../types"; +import { ActionType, ActionTypes, Category, CategoryActionType, SponsorTime } from "../types"; import Utils from "../utils"; -import { getSkippingText } from "../utils/categoryUtils"; +import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils"; const utils = new Utils(); const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; export interface PreviewBarSegment { segment: [number, number]; - category: string; + category: Category; unsubmitted: boolean; showLarger: boolean; } @@ -177,14 +177,17 @@ class PreviewBar { this.segments = segments; this.videoDuration = videoDuration; - this.segments.sort(({segment: a}, {segment: b}) => { + const sortedSegments = this.segments.sort(({segment: a}, {segment: b}) => { // Sort longer segments before short segments to make shorter segments render later return (b[1] - b[0]) - (a[1] - a[0]); - }).forEach((segment) => { + }); + for (const segment of sortedSegments) { const bar = this.createBar(segment); this.container.appendChild(bar); - }); + } + + this.createChaptersBar(segments.sort((a, b) => a.segment[0] - b.segment[0])); } createBar({category, unsubmitted, segment, showLarger}: PreviewBarSegment): HTMLLIElement { @@ -200,12 +203,101 @@ class PreviewBar { bar.style.position = "absolute"; const duration = segment[1] - segment[0]; - if (segment[1] - segment[0] > 0) bar.style.width = this.timeToPercentage(segment[1] - segment[0]); - bar.style.left = this.timeToPercentage(Math.min(this.videoDuration - Math.max(0, duration), segment[0])); + if (segment[1] - segment[0] > 0) bar.style.width = `calc(${this.timeToPercentage(segment[1] - segment[0])} - 2px)`; + bar.style.left = `calc(${this.timeToPercentage(Math.min(this.videoDuration - Math.max(0, duration), segment[0]))})`; return bar; } + createChaptersBar(segments: PreviewBarSegment[]): void { + //
+ // set specific width (use calc(% - 4px)) + + // TODO: run this only once, then just update it in another function + + const progressBar = document.querySelector('.ytp-progress-bar'); + const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement; + if (!progressBar || !chapterBar || segments?.length <= 0) return; + + const observer = new MutationObserver((mutations) => { + const changes: Record = {}; + for (const mutation of mutations) { + const currentElement = mutation.target as HTMLElement; + if (mutation.type === "attributes" && mutation.attributeName === "style" + && currentElement.parentElement.classList.contains("ytp-chapter-hover-container")) { + changes[currentElement.classList[0]] = currentElement.style; + } + } + + // newChapterBar.querySelector(`.${className}).style.width = `calc(${width * someFactor}% - 4px)`; + }); + + // observer.observe(chapterBar, { + // childList: true, + // subtree: true + // }); + + // Create it from cloning + const newChapterBar = chapterBar.cloneNode(true) as HTMLElement; + newChapterBar.classList.add("sponsorBlockChapterBar"); + const originalSectionClone = newChapterBar.querySelector(".ytp-chapter-hover-container"); + + // Merge overlapping chapters + const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI) + .reduce((acc, curr) => { + if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { + acc.push(curr); + } else { + acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); + } + + return acc; + }, [] as PreviewBarSegment[]); + + // Modify it to have sections for each segment + for (let i = 0; i < mergedSegments.length; i++) { + const segment = mergedSegments[i]; + if (i === 0 && segment.segment[0] > 0) { + const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement; + const blankDuration = segment.segment[0]; + + newBlankSection.style.marginRight = "2px"; + newBlankSection.style.width = `calc(${this.timeToPercentage(blankDuration)} - 2px)`; + newChapterBar.appendChild(newBlankSection); + } + + const duration = segment.segment[1] - segment.segment[0]; + const newSection = originalSectionClone.cloneNode(true) as HTMLElement; + + newSection.style.marginRight = "2px"; + newSection.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; + newChapterBar.appendChild(newSection); + + if (segment.segment[1] < this.videoDuration) { + const nextSegment = mergedSegments[i + 1]; + const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement; + const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; + const blankDuration = nextTime - segment.segment[1]; + + newBlankSection.style.marginRight = "2px"; + newBlankSection.style.width = `calc(${this.timeToPercentage(blankDuration)} - 2px)`; + console.log(blankDuration + "\t" + this.videoDuration) + newChapterBar.appendChild(newBlankSection); + } + } + + originalSectionClone.remove(); + progressBar.prepend(newChapterBar); + + // Hide old bar + chapterBar.style.display = "none"; + + // clone stuff + // Setup mutation listener + // Modify sizes to meet new scales values + // Hide old element + } + updateChapterText(segments: SponsorTime[], currentTime: number): void { if (!segments) return; From 6dee56dc95430fb5b413110bb7e407b938763527 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 27 Oct 2021 23:21:22 -0400 Subject: [PATCH 006/101] Add mutation listener to update progress indicators --- src/js-components/previewBar.ts | 41 +++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 4d186566..90445776 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -224,18 +224,39 @@ class PreviewBar { for (const mutation of mutations) { const currentElement = mutation.target as HTMLElement; if (mutation.type === "attributes" && mutation.attributeName === "style" - && currentElement.parentElement.classList.contains("ytp-chapter-hover-container")) { + && currentElement.parentElement.classList.contains("ytp-progress-list")) { changes[currentElement.classList[0]] = currentElement.style; } } - // newChapterBar.querySelector(`.${className}).style.width = `calc(${width * someFactor}% - 4px)`; + // Go through each newly generated chapter bar and update the width based on changes array + const generatedChapterBar = document.querySelector(".sponsorBlockChapterBar"); + if (generatedChapterBar) { + // Width reached so far in decimal percent + let cursor = 0; + + const sections = generatedChapterBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf; + for (const section of sections) { + const sectionWidth = parseFloat(section.getAttribute("decimal-width")); + + for (const className in changes) { + const currentChangedElement = section.querySelector(`.${className}`) as HTMLElement; + if (currentChangedElement) { + const transformScale = parseFloat(changes[className].transform.match(/scaleX\(([0-9.]+?)\)/)[1]); + currentChangedElement.style.transform = `scaleX(${Math.min(1, (transformScale - cursor) / sectionWidth)}`; + } + } + + cursor += sectionWidth; + } + } }); - // observer.observe(chapterBar, { - // childList: true, - // subtree: true - // }); + observer.observe(chapterBar, { + subtree: true, + attributes: true, + attributeFilter: ["style"] //TODO: check for left too + }); // Create it from cloning const newChapterBar = chapterBar.cloneNode(true) as HTMLElement; @@ -263,6 +284,7 @@ class PreviewBar { newBlankSection.style.marginRight = "2px"; newBlankSection.style.width = `calc(${this.timeToPercentage(blankDuration)} - 2px)`; + newBlankSection.setAttribute("decimal-width", String(this.timeToDecimal(blankDuration))); newChapterBar.appendChild(newBlankSection); } @@ -271,6 +293,7 @@ class PreviewBar { newSection.style.marginRight = "2px"; newSection.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; + newSection.setAttribute("decimal-width", String(this.timeToDecimal(duration))); newChapterBar.appendChild(newSection); if (segment.segment[1] < this.videoDuration) { @@ -281,7 +304,7 @@ class PreviewBar { newBlankSection.style.marginRight = "2px"; newBlankSection.style.width = `calc(${this.timeToPercentage(blankDuration)} - 2px)`; - console.log(blankDuration + "\t" + this.videoDuration) + newBlankSection.setAttribute("decimal-width", String(this.timeToDecimal(blankDuration))); newChapterBar.appendChild(newBlankSection); } } @@ -361,6 +384,10 @@ class PreviewBar { return Math.min(100, time / this.videoDuration * 100) + '%'; } + timeToDecimal(time: number): number { + return Math.min(1, time / this.videoDuration); + } + /* * Approximate size on preview bar for smallest element (due to  ) */ From 2d3e293d838b708764dbea7d07d2d7404a533c97 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Thu, 28 Oct 2021 00:38:46 -0400 Subject: [PATCH 007/101] Support left style changes for chapters bar and fix negative size --- src/js-components/previewBar.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 90445776..85865e36 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -220,12 +220,12 @@ class PreviewBar { if (!progressBar || !chapterBar || segments?.length <= 0) return; const observer = new MutationObserver((mutations) => { - const changes: Record = {}; + const changes: Record = {}; for (const mutation of mutations) { const currentElement = mutation.target as HTMLElement; - if (mutation.type === "attributes" && mutation.attributeName === "style" + if (mutation.type === "attributes" && currentElement.parentElement.classList.contains("ytp-progress-list")) { - changes[currentElement.classList[0]] = currentElement.style; + changes[currentElement.classList[0]] = mutation; } } @@ -237,17 +237,24 @@ class PreviewBar { const sections = generatedChapterBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf; for (const section of sections) { - const sectionWidth = parseFloat(section.getAttribute("decimal-width")); + const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width")); for (const className in changes) { const currentChangedElement = section.querySelector(`.${className}`) as HTMLElement; if (currentChangedElement) { - const transformScale = parseFloat(changes[className].transform.match(/scaleX\(([0-9.]+?)\)/)[1]); - currentChangedElement.style.transform = `scaleX(${Math.min(1, (transformScale - cursor) / sectionWidth)}`; + const currentChange = changes[className]; + const changedElement = changes[className].target as HTMLElement; + const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; + if (currentChange.attributeName === "style") { + const transformScale = parseFloat(changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/)[1]) + left; + currentChangedElement.style.transform = `scaleX(${Math.max(0, Math.min(1, (transformScale - cursor) / sectionWidthDecimal))}`; + } else if (currentChange.attributeName === "left") { + currentChangedElement.style.left = `${Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)) * 100}%`; + } } } - cursor += sectionWidth; + cursor += sectionWidthDecimal; } } }); @@ -255,7 +262,7 @@ class PreviewBar { observer.observe(chapterBar, { subtree: true, attributes: true, - attributeFilter: ["style"] //TODO: check for left too + attributeFilter: ["style", "left"] }); // Create it from cloning From 7c4a0628b756e6af2ab03078793e6d0ad63006fa Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Thu, 28 Oct 2021 23:57:53 -0400 Subject: [PATCH 008/101] Add growing chapter on hover --- public/content.css | 4 ++++ src/js-components/previewBar.ts | 37 ++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/public/content.css b/public/content.css index 4751d293..bcedc211 100644 --- a/public/content.css +++ b/public/content.css @@ -49,6 +49,10 @@ transform: translateY(1em) !important; } +.sponsorBlockChapterBar:hover { + z-index: 41; +} + /* */ .popup { diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 85865e36..946b1837 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -32,6 +32,9 @@ class PreviewBar { segments: PreviewBarSegment[] = []; videoDuration = 0; + // For chapter bar + hoveredSection?: HTMLElement; + constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) { this.container = document.createElement('ul'); this.container.id = 'previewbar'; @@ -246,8 +249,11 @@ class PreviewBar { const changedElement = changes[className].target as HTMLElement; const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; if (currentChange.attributeName === "style") { - const transformScale = parseFloat(changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/)[1]) + left; - currentChangedElement.style.transform = `scaleX(${Math.max(0, Math.min(1, (transformScale - cursor) / sectionWidthDecimal))}`; + const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); + if (transformMatch) { + const transformScale = parseFloat(transformMatch[1]) + left; + currentChangedElement.style.transform = `scaleX(${Math.max(0, Math.min(1, (transformScale - cursor) / sectionWidthDecimal))}`; + } } else if (currentChange.attributeName === "left") { currentChangedElement.style.left = `${Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)) * 100}%`; } @@ -289,18 +295,14 @@ class PreviewBar { const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement; const blankDuration = segment.segment[0]; - newBlankSection.style.marginRight = "2px"; - newBlankSection.style.width = `calc(${this.timeToPercentage(blankDuration)} - 2px)`; - newBlankSection.setAttribute("decimal-width", String(this.timeToDecimal(blankDuration))); + this.setupChapterSection(newBlankSection, blankDuration); newChapterBar.appendChild(newBlankSection); } const duration = segment.segment[1] - segment.segment[0]; const newSection = originalSectionClone.cloneNode(true) as HTMLElement; - newSection.style.marginRight = "2px"; - newSection.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; - newSection.setAttribute("decimal-width", String(this.timeToDecimal(duration))); + this.setupChapterSection(newSection, duration); newChapterBar.appendChild(newSection); if (segment.segment[1] < this.videoDuration) { @@ -309,9 +311,7 @@ class PreviewBar { const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; const blankDuration = nextTime - segment.segment[1]; - newBlankSection.style.marginRight = "2px"; - newBlankSection.style.width = `calc(${this.timeToPercentage(blankDuration)} - 2px)`; - newBlankSection.setAttribute("decimal-width", String(this.timeToDecimal(blankDuration))); + this.setupChapterSection(newBlankSection, blankDuration); newChapterBar.appendChild(newBlankSection); } } @@ -321,11 +321,18 @@ class PreviewBar { // Hide old bar chapterBar.style.display = "none"; + } - // clone stuff - // Setup mutation listener - // Modify sizes to meet new scales values - // Hide old element + private setupChapterSection(section: HTMLElement, duration: number): void { + section.style.marginRight = "2px"; + section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; + section.setAttribute("decimal-width", String(this.timeToDecimal(duration))); + + section.addEventListener("mouseenter", () => { + this.hoveredSection?.classList.remove("ytp-exp-chapter-hover-effect"); + section.classList.add("ytp-exp-chapter-hover-effect"); + this.hoveredSection = section; + }); } updateChapterText(segments: SponsorTime[], currentTime: number): void { From bf4eb8fafc344f60c051f2d597091d0899bce9ac Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Fri, 29 Oct 2021 00:02:46 -0400 Subject: [PATCH 009/101] Fix z-index not applying --- public/content.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/content.css b/public/content.css index bcedc211..cd44b152 100644 --- a/public/content.css +++ b/public/content.css @@ -50,7 +50,7 @@ } .sponsorBlockChapterBar:hover { - z-index: 41; + z-index: 41 !important; } /* */ From 37ac5c8cbd4fbbed57c9be3f06bebcb87757d50d Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sat, 30 Oct 2021 15:11:22 -0400 Subject: [PATCH 010/101] Listen for class changes for ytp-hover-progress-light and fix left --- src/js-components/previewBar.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 946b1837..eb241ff2 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -243,20 +243,26 @@ class PreviewBar { const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width")); for (const className in changes) { - const currentChangedElement = section.querySelector(`.${className}`) as HTMLElement; - if (currentChangedElement) { - const currentChange = changes[className]; + const customChangedElement = section.querySelector(`.${className}`) as HTMLElement; + if (customChangedElement) { const changedElement = changes[className].target as HTMLElement; + const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; - if (currentChange.attributeName === "style") { - const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); - if (transformMatch) { - const transformScale = parseFloat(transformMatch[1]) + left; - currentChangedElement.style.transform = `scaleX(${Math.max(0, Math.min(1, (transformScale - cursor) / sectionWidthDecimal))}`; - } - } else if (currentChange.attributeName === "left") { - currentChangedElement.style.left = `${Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)) * 100}%`; + const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)); + if (!isNaN(left) && !isNaN(calculatedLeft)) { + customChangedElement.style.left = `${calculatedLeft * 100}%`; + customChangedElement.style.removeProperty("display"); } + + const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); + if (transformMatch) { + const transformScale = parseFloat(transformMatch[1]) + left; + customChangedElement.style.transform = + `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, + (transformScale - cursor) / sectionWidthDecimal - calculatedLeft))}`; + } + + customChangedElement.className = changedElement.className; } } @@ -268,7 +274,7 @@ class PreviewBar { observer.observe(chapterBar, { subtree: true, attributes: true, - attributeFilter: ["style", "left"] + attributeFilter: ["style", "class"], }); // Create it from cloning From c7acb902a43da79ac36cfb0e276711334a1d0b9f Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sat, 30 Oct 2021 21:32:54 -0400 Subject: [PATCH 011/101] Fix issues with hover failing after hovering scrubber --- public/content.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/content.css b/public/content.css index cd44b152..ebda9024 100644 --- a/public/content.css +++ b/public/content.css @@ -49,7 +49,7 @@ transform: translateY(1em) !important; } -.sponsorBlockChapterBar:hover { +div:hover > .sponsorBlockChapterBar { z-index: 41 !important; } From b4a2f31520b69b31fa5aeb20dc18011e0f385bdd Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sat, 30 Oct 2021 21:48:52 -0400 Subject: [PATCH 012/101] Render preview bar behind scrubber --- public/content.css | 2 +- src/content.ts | 2 +- src/js-components/previewBar.ts | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/public/content.css b/public/content.css index ebda9024..108b4dda 100644 --- a/public/content.css +++ b/public/content.css @@ -8,7 +8,7 @@ height: 100%; transform: scaleY(0.6) translateY(-30%) translateY(1.5px); - z-index: 40; + z-index: 42; transition: transform .1s cubic-bezier(0,0,0.2,1); } diff --git a/src/content.ts b/src/content.ts index 7a6c2334..8c9e240a 100644 --- a/src/content.ts +++ b/src/content.ts @@ -366,7 +366,7 @@ function createPreviewBar(): void { // For mobile YouTube ".progress-bar-background", // For YouTube - ".ytp-progress-bar-container", + ".ytp-progress-bar", ".no-model.cue-range-markers", // For Invidious/VideoJS ".vjs-progress-holder" diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index eb241ff2..ea00f832 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -158,8 +158,6 @@ class PreviewBar { this.parent.addEventListener("mouseleave", () => this.container.classList.remove("hovered")); } - - // On the seek bar this.parent.prepend(this.container); } @@ -323,7 +321,11 @@ class PreviewBar { } originalSectionClone.remove(); - progressBar.prepend(newChapterBar); + if (this.container?.parentElement === progressBar) { + progressBar.insertBefore(newChapterBar, this.container.nextSibling); + } else { + progressBar.prepend(newChapterBar); + } // Hide old bar chapterBar.style.display = "none"; From 9ed9f9b873bbae0b0f088d73ef007ff5f09e889b Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Mon, 1 Nov 2021 20:59:04 -0400 Subject: [PATCH 013/101] Only create one chapter bar, support videos with no segments and preview segments --- src/js-components/previewBar.ts | 155 ++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 65 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index ea00f832..364b2ce1 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -33,7 +33,9 @@ class PreviewBar { videoDuration = 0; // For chapter bar - hoveredSection?: HTMLElement; + hoveredSection: HTMLElement; + customChaptersBar: HTMLElement; + chaptersBarSegments: PreviewBarSegment[]; constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) { this.container = document.createElement('ul'); @@ -44,6 +46,7 @@ class PreviewBar { this.onInvidious = onInvidious; this.createElement(parent); + this.createChapterMutationObserver(); this.setupHoverText(); } @@ -218,71 +221,29 @@ class PreviewBar { const progressBar = document.querySelector('.ytp-progress-bar'); const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement; - if (!progressBar || !chapterBar || segments?.length <= 0) return; + if (!progressBar || !chapterBar) return; - const observer = new MutationObserver((mutations) => { - const changes: Record = {}; - for (const mutation of mutations) { - const currentElement = mutation.target as HTMLElement; - if (mutation.type === "attributes" - && currentElement.parentElement.classList.contains("ytp-progress-list")) { - changes[currentElement.classList[0]] = mutation; - } - } + if (segments === this.chaptersBarSegments) return; + this.customChaptersBar?.remove(); - // Go through each newly generated chapter bar and update the width based on changes array - const generatedChapterBar = document.querySelector(".sponsorBlockChapterBar"); - if (generatedChapterBar) { - // Width reached so far in decimal percent - let cursor = 0; - - const sections = generatedChapterBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf; - for (const section of sections) { - const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width")); - - for (const className in changes) { - const customChangedElement = section.querySelector(`.${className}`) as HTMLElement; - if (customChangedElement) { - const changedElement = changes[className].target as HTMLElement; - - const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; - const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)); - if (!isNaN(left) && !isNaN(calculatedLeft)) { - customChangedElement.style.left = `${calculatedLeft * 100}%`; - customChangedElement.style.removeProperty("display"); - } - - const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); - if (transformMatch) { - const transformScale = parseFloat(transformMatch[1]) + left; - customChangedElement.style.transform = - `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, - (transformScale - cursor) / sectionWidthDecimal - calculatedLeft))}`; - } - - customChangedElement.className = changedElement.className; - } - } - - cursor += sectionWidthDecimal; - } - } - }); - - observer.observe(chapterBar, { - subtree: true, - attributes: true, - attributeFilter: ["style", "class"], - }); + if (segments?.length <= 0) { + chapterBar.style.removeProperty("display"); + return; + } // Create it from cloning const newChapterBar = chapterBar.cloneNode(true) as HTMLElement; newChapterBar.classList.add("sponsorBlockChapterBar"); - const originalSectionClone = newChapterBar.querySelector(".ytp-chapter-hover-container"); + newChapterBar.style.removeProperty("display"); + const originalSection = newChapterBar.querySelector(".ytp-chapter-hover-container"); + + this.customChaptersBar = newChapterBar; + this.chaptersBarSegments = segments; // Merge overlapping chapters - const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI) - .reduce((acc, curr) => { + const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI + && segment.segment.length === 2) + .reduce((acc, curr) => { if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { acc.push(curr); } else { @@ -296,7 +257,7 @@ class PreviewBar { for (let i = 0; i < mergedSegments.length; i++) { const segment = mergedSegments[i]; if (i === 0 && segment.segment[0] > 0) { - const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement; + const newBlankSection = originalSection.cloneNode(true) as HTMLElement; const blankDuration = segment.segment[0]; this.setupChapterSection(newBlankSection, blankDuration); @@ -304,14 +265,14 @@ class PreviewBar { } const duration = segment.segment[1] - segment.segment[0]; - const newSection = originalSectionClone.cloneNode(true) as HTMLElement; + const newSection = originalSection.cloneNode(true) as HTMLElement; this.setupChapterSection(newSection, duration); newChapterBar.appendChild(newSection); if (segment.segment[1] < this.videoDuration) { const nextSegment = mergedSegments[i + 1]; - const newBlankSection = originalSectionClone.cloneNode(true) as HTMLElement; + const newBlankSection = originalSection.cloneNode(true) as HTMLElement; const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; const blankDuration = nextTime - segment.segment[1]; @@ -320,15 +281,15 @@ class PreviewBar { } } - originalSectionClone.remove(); + // Hide old bar + chapterBar.style.display = "none"; + + originalSection.remove(); if (this.container?.parentElement === progressBar) { progressBar.insertBefore(newChapterBar, this.container.nextSibling); } else { progressBar.prepend(newChapterBar); } - - // Hide old bar - chapterBar.style.display = "none"; } private setupChapterSection(section: HTMLElement, duration: number): void { @@ -343,6 +304,70 @@ class PreviewBar { }); } + private createChapterMutationObserver(): void { + const progressBar = document.querySelector('.ytp-progress-bar'); + const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement; + if (!progressBar || !chapterBar) return; + + const observer = new MutationObserver((mutations) => { + const changes: Record = {}; + for (const mutation of mutations) { + const currentElement = mutation.target as HTMLElement; + if (mutation.type === "attributes" + && currentElement.parentElement.classList.contains("ytp-progress-list")) { + changes[currentElement.classList[0]] = mutation; + } + } + + this.updateChapterMutation(changes, progressBar); + }); + + observer.observe(chapterBar, { + subtree: true, + attributes: true, + attributeFilter: ["style", "class"], + }); + } + + private updateChapterMutation(changes: Record, progressBar: HTMLElement): void { + // Go through each newly generated chapter bar and update the width based on changes array + if (this.customChaptersBar) { + // Width reached so far in decimal percent + let cursor = 0; + + const sections = this.customChaptersBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf; + for (const section of sections) { + const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width")); + + for (const className in changes) { + const customChangedElement = section.querySelector(`.${className}`) as HTMLElement; + if (customChangedElement) { + const changedElement = changes[className].target as HTMLElement; + + const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; + const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)); + if (!isNaN(left) && !isNaN(calculatedLeft)) { + customChangedElement.style.left = `${calculatedLeft * 100}%`; + customChangedElement.style.removeProperty("display"); + } + + const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); + if (transformMatch) { + const transformScale = parseFloat(transformMatch[1]) + left; + customChangedElement.style.transform = + `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, + (transformScale - cursor) / sectionWidthDecimal - calculatedLeft))}`; + } + + customChangedElement.className = changedElement.className; + } + } + + cursor += sectionWidthDecimal; + } + } + } + updateChapterText(segments: SponsorTime[], currentTime: number): void { if (!segments) return; From a804da06f59d95f31f334509866f09a79184b396 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Mon, 1 Nov 2021 21:10:43 -0400 Subject: [PATCH 014/101] Update chapter bar progress right after clone --- src/js-components/previewBar.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 364b2ce1..88ce3b31 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -219,7 +219,7 @@ class PreviewBar { // TODO: run this only once, then just update it in another function - const progressBar = document.querySelector('.ytp-progress-bar'); + const progressBar = document.querySelector('.ytp-progress-bar') as HTMLElement; const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement; if (!progressBar || !chapterBar) return; @@ -290,6 +290,8 @@ class PreviewBar { } else { progressBar.prepend(newChapterBar); } + + this.updateChapterAllMutation(chapterBar, progressBar); } private setupChapterSection(section: HTMLElement, duration: number): void { @@ -305,17 +307,17 @@ class PreviewBar { } private createChapterMutationObserver(): void { - const progressBar = document.querySelector('.ytp-progress-bar'); + const progressBar = document.querySelector('.ytp-progress-bar') as HTMLElement; const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement; if (!progressBar || !chapterBar) return; const observer = new MutationObserver((mutations) => { - const changes: Record = {}; + const changes: Record = {}; for (const mutation of mutations) { const currentElement = mutation.target as HTMLElement; if (mutation.type === "attributes" && currentElement.parentElement.classList.contains("ytp-progress-list")) { - changes[currentElement.classList[0]] = mutation; + changes[currentElement.classList[0]] = mutation.target as HTMLElement; } } @@ -328,8 +330,18 @@ class PreviewBar { attributeFilter: ["style", "class"], }); } + + private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement): void { + const elements = originalChapterBar.querySelectorAll(".ytp-progress-list > *"); + const changes: Record = {}; + for (const element of elements) { + changes[element.classList[0]] = element as HTMLElement; + } - private updateChapterMutation(changes: Record, progressBar: HTMLElement): void { + this.updateChapterMutation(changes, progressBar); + } + + private updateChapterMutation(changes: Record, progressBar: HTMLElement): void { // Go through each newly generated chapter bar and update the width based on changes array if (this.customChaptersBar) { // Width reached so far in decimal percent @@ -342,7 +354,7 @@ class PreviewBar { for (const className in changes) { const customChangedElement = section.querySelector(`.${className}`) as HTMLElement; if (customChangedElement) { - const changedElement = changes[className].target as HTMLElement; + const changedElement = changes[className]; const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)); From fb3635cdf81eaece71a4fc5b6952f26b88eea07d Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Mon, 1 Nov 2021 21:18:03 -0400 Subject: [PATCH 015/101] Fix animation when chapter bar is recreated --- src/js-components/previewBar.ts | 50 ++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 88ce3b31..2fea499c 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -152,7 +152,7 @@ class PreviewBar { if (this.onMobileYouTube) { parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)"; parent.style.opacity = "1"; - + this.container.style.transform = "none"; } else if (!this.onInvidious) { // Hover listener @@ -181,7 +181,7 @@ class PreviewBar { this.segments = segments; this.videoDuration = videoDuration; - const sortedSegments = this.segments.sort(({segment: a}, {segment: b}) => { + const sortedSegments = this.segments.sort(({ segment: a }, { segment: b }) => { // Sort longer segments before short segments to make shorter segments render later return (b[1] - b[0]) - (a[1] - a[0]); }); @@ -194,7 +194,7 @@ class PreviewBar { this.createChaptersBar(segments.sort((a, b) => a.segment[0] - b.segment[0])); } - createBar({category, unsubmitted, segment, showLarger}: PreviewBarSegment): HTMLLIElement { + createBar({ category, unsubmitted, segment, showLarger }: PreviewBarSegment): HTMLLIElement { const bar = document.createElement('li'); bar.classList.add('previewbar'); bar.innerHTML = showLarger ? '  ' : ' '; @@ -241,17 +241,17 @@ class PreviewBar { this.chaptersBarSegments = segments; // Merge overlapping chapters - const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI - && segment.segment.length === 2) - .reduce((acc, curr) => { - if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { - acc.push(curr); - } else { - acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); - } + const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI + && segment.segment.length === 2) + .reduce((acc, curr) => { + if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { + acc.push(curr); + } else { + acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); + } - return acc; - }, [] as PreviewBarSegment[]); + return acc; + }, [] as PreviewBarSegment[]); // Modify it to have sections for each segment for (let i = 0; i < mergedSegments.length; i++) { @@ -291,7 +291,7 @@ class PreviewBar { progressBar.prepend(newChapterBar); } - this.updateChapterAllMutation(chapterBar, progressBar); + this.updateChapterAllMutation(chapterBar, progressBar, true); } private setupChapterSection(section: HTMLElement, duration: number): void { @@ -315,8 +315,8 @@ class PreviewBar { const changes: Record = {}; for (const mutation of mutations) { const currentElement = mutation.target as HTMLElement; - if (mutation.type === "attributes" - && currentElement.parentElement.classList.contains("ytp-progress-list")) { + if (mutation.type === "attributes" + && currentElement.parentElement.classList.contains("ytp-progress-list")) { changes[currentElement.classList[0]] = mutation.target as HTMLElement; } } @@ -330,18 +330,18 @@ class PreviewBar { attributeFilter: ["style", "class"], }); } - - private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement): void { + + private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement, firstUpdate = false): void { const elements = originalChapterBar.querySelectorAll(".ytp-progress-list > *"); const changes: Record = {}; for (const element of elements) { changes[element.classList[0]] = element as HTMLElement; } - this.updateChapterMutation(changes, progressBar); + this.updateChapterMutation(changes, progressBar, firstUpdate); } - private updateChapterMutation(changes: Record, progressBar: HTMLElement): void { + private updateChapterMutation(changes: Record, progressBar: HTMLElement, firstUpdate = false): void { // Go through each newly generated chapter bar and update the width based on changes array if (this.customChaptersBar) { // Width reached so far in decimal percent @@ -366,9 +366,15 @@ class PreviewBar { const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); if (transformMatch) { const transformScale = parseFloat(transformMatch[1]) + left; - customChangedElement.style.transform = - `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, + customChangedElement.style.transform = + `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, (transformScale - cursor) / sectionWidthDecimal - calculatedLeft))}`; + + // Prevent transition from occuring + if (firstUpdate) { + customChangedElement.style.transition = "none"; + setTimeout(() => customChangedElement.style.transition = "inherit", 50); + } } customChangedElement.className = changedElement.className; From 517e53a2e347a8bc70c4074abe54c58b3d1c0cc7 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Mon, 1 Nov 2021 21:44:39 -0400 Subject: [PATCH 016/101] Fix small segments breaking chapters --- src/js-components/previewBar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 2fea499c..dca5b7b5 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -242,7 +242,7 @@ class PreviewBar { // Merge overlapping chapters const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI - && segment.segment.length === 2) + && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > 0.003) .reduce((acc, curr) => { if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { acc.push(curr); From f5fa758ac1399b9f54a69ac6ce9b4b8bdf1d0c7c Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 2 Nov 2021 23:33:19 -0400 Subject: [PATCH 017/101] Don't shrink preview bar if segment not a chapter --- src/js-components/previewBar.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index dca5b7b5..ca17e2eb 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -194,7 +194,9 @@ class PreviewBar { this.createChaptersBar(segments.sort((a, b) => a.segment[0] - b.segment[0])); } - createBar({ category, unsubmitted, segment, showLarger }: PreviewBarSegment): HTMLLIElement { + createBar(barSegment: PreviewBarSegment): HTMLLIElement { + const { category, unsubmitted, segment, showLarger } = barSegment; + const bar = document.createElement('li'); bar.classList.add('previewbar'); bar.innerHTML = showLarger ? '  ' : ' '; @@ -207,8 +209,8 @@ class PreviewBar { bar.style.position = "absolute"; const duration = segment[1] - segment[0]; - if (segment[1] - segment[0] > 0) bar.style.width = `calc(${this.timeToPercentage(segment[1] - segment[0])} - 2px)`; - bar.style.left = `calc(${this.timeToPercentage(Math.min(this.videoDuration - Math.max(0, duration), segment[0]))})`; + if (segment[1] - segment[0] > 0) bar.style.width = `calc(${this.timeToPercentage(segment[1] - segment[0])}${this.chapterFilter(barSegment) ? '- 2px' : ''})`; + bar.style.left = this.timeToPercentage(Math.min(this.videoDuration - Math.max(0, duration), segment[0])); return bar; } @@ -241,8 +243,7 @@ class PreviewBar { this.chaptersBarSegments = segments; // Merge overlapping chapters - const mergedSegments = segments.filter((segment) => getCategoryActionType(segment.category) !== CategoryActionType.POI - && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > 0.003) + const mergedSegments = segments.filter((segment) => this.chapterFilter(segment)) .reduce((acc, curr) => { if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { acc.push(curr); @@ -445,6 +446,11 @@ class PreviewBar { } } + private chapterFilter(segment: PreviewBarSegment): boolean { + return getCategoryActionType(segment.category) !== CategoryActionType.POI + && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > 0.003; + } + timeToPercentage(time: number): string { return Math.min(100, time / this.videoDuration * 100) + '%'; } From 781449397464cef88abe4d18dfeb45b39d5bd773 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Thu, 4 Nov 2021 00:25:58 -0400 Subject: [PATCH 018/101] Fix preview bar width and remove chapter refresh check --- src/js-components/previewBar.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index ca17e2eb..4f397399 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -209,23 +209,17 @@ class PreviewBar { bar.style.position = "absolute"; const duration = segment[1] - segment[0]; - if (segment[1] - segment[0] > 0) bar.style.width = `calc(${this.timeToPercentage(segment[1] - segment[0])}${this.chapterFilter(barSegment) ? '- 2px' : ''})`; + if (segment[1] - segment[0] > 0) bar.style.width = `calc(${this.timeToPercentage(segment[1] - segment[0])}${this.chapterFilter(barSegment) ? ' - 2px' : ''})`; bar.style.left = this.timeToPercentage(Math.min(this.videoDuration - Math.max(0, duration), segment[0])); return bar; } createChaptersBar(segments: PreviewBarSegment[]): void { - //
- // set specific width (use calc(% - 4px)) - - // TODO: run this only once, then just update it in another function - const progressBar = document.querySelector('.ytp-progress-bar') as HTMLElement; const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement; if (!progressBar || !chapterBar) return; - if (segments === this.chaptersBarSegments) return; this.customChaptersBar?.remove(); if (segments?.length <= 0) { From 4a2ebe4b035887cb688bfb95a00b504b0d65ed8b Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Thu, 4 Nov 2021 00:34:21 -0400 Subject: [PATCH 019/101] Fix undefined error when making segment --- src/components/SponsorTimeEditComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index a86ee4e9..d6d7c44a 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -458,8 +458,8 @@ class SponsorTimeEditComponent extends React.Component Date: Sat, 6 Nov 2021 19:57:46 -0400 Subject: [PATCH 020/101] Show description in hover bar --- src/content.ts | 8 +++++--- src/js-components/previewBar.ts | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/content.ts b/src/content.ts index 8b830a8d..7110a9b8 100644 --- a/src/content.ts +++ b/src/content.ts @@ -677,7 +677,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) { const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4); const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { categories, - actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute] : [ActionType.Skip], + actionTypes: Config.config.muteSegments ? [ActionType.Skip, ActionType.Mute, ActionType.Chapter] : [ActionType.Skip, ActionType.Chapter], userAgent: `${chrome.runtime.id}`, ...extraRequestData }); @@ -968,7 +968,8 @@ function updatePreviewBar(): void { segment: segment.segment as [number, number], category: segment.category, unsubmitted: false, - showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI + showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI, + description: segment.description, }); }); } @@ -978,7 +979,8 @@ function updatePreviewBar(): void { segment: segment.segment as [number, number], category: segment.category, unsubmitted: true, - showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI + showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI, + description: segment.description, }); }); diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 9d39386f..c242e4df 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -18,6 +18,7 @@ export interface PreviewBarSegment { category: Category; unsubmitted: boolean; showLarger: boolean; + description: string; } class PreviewBar { @@ -129,10 +130,11 @@ class PreviewBar { } else if (segment !== null) { this.tooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS); + const name = segment.description || utils.shortCategoryName(segment.category); if (segment.unsubmitted) { - this.categoryTooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + utils.shortCategoryName(segment.category); + this.categoryTooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + name; } else { - this.categoryTooltip.textContent = utils.shortCategoryName(segment.category); + this.categoryTooltip.textContent = name; } // Use the class if the timestamp text uses it to prevent overlapping From 9e6e3b023de98377c1c7a5605daf2f2122ce2178 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sat, 6 Nov 2021 21:20:36 -0400 Subject: [PATCH 021/101] Fix small time differences between segments causing issues --- src/js-components/previewBar.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index c242e4df..c451edd2 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -12,6 +12,7 @@ import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils"; const utils = new Utils(); const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; +const MIN_CHAPTER_SIZE = 0.003; export interface PreviewBarSegment { segment: [number, number]; @@ -269,10 +270,10 @@ class PreviewBar { this.setupChapterSection(newSection, duration); newChapterBar.appendChild(newSection); - if (segment.segment[1] < this.videoDuration) { - const nextSegment = mergedSegments[i + 1]; + const nextSegment = mergedSegments[i + 1]; + const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; + if (this.timeToDecimal(nextTime - segment.segment[1]) > MIN_CHAPTER_SIZE) { const newBlankSection = originalSection.cloneNode(true) as HTMLElement; - const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; const blankDuration = nextTime - segment.segment[1]; this.setupChapterSection(newBlankSection, blankDuration); @@ -446,7 +447,7 @@ class PreviewBar { private chapterFilter(segment: PreviewBarSegment): boolean { return getCategoryActionType(segment.category) !== CategoryActionType.POI - && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > 0.003; + && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > MIN_CHAPTER_SIZE; } timeToPercentage(time: number): string { From a3e67b6cde7138406eee135736ba39f7461b6565 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 01:05:32 -0400 Subject: [PATCH 022/101] Add chapter name autocomplete --- public/content.css | 64 ++++++++++++++++++--- src/components/SelectorComponent.tsx | 55 ++++++++++++++++++ src/components/SponsorTimeEditComponent.tsx | 64 +++++++++++++++++++-- src/content.ts | 3 +- src/types.ts | 3 +- 5 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 src/components/SelectorComponent.tsx diff --git a/public/content.css b/public/content.css index 8a25ee13..24490e85 100644 --- a/public/content.css +++ b/public/content.css @@ -1,3 +1,12 @@ +:root { + --skip-notice-right: 10px; + --skip-notice-padding: 5px; + --skip-notice-margin: 5px; + --skip-notice-border-horizontal: 5px; + --skip-notice-border-vertical: 10px; + --sb-dark-red-outline: rgb(130,0,0,0.9); +} + .hidden { display: none; } @@ -112,8 +121,8 @@ div:hover > .sponsorBlockChapterBar { .sponsorSkipObject { font-family: Roboto, Arial, Helvetica, sans-serif; - margin-left: 2px; - margin-right: 2px; + margin-left: var(--skip-notice-margin); + margin-right: var(--skip-notice-margin); } .sponsorSkipLogo { @@ -144,23 +153,23 @@ div:hover > .sponsorBlockChapterBar { position: absolute; right: 5px; bottom: 100px; - right: 10px; + right: var(--skip-notice-right); } .sponsorSkipNoticeParent { position: absolute; bottom: 100px; - right: 10px; + right: var(--skip-notice-right); } .sponsorSkipNoticeParent, .sponsorSkipNotice { min-width: 350px; max-width: 50%; - border-spacing: 5px 10px; - padding-left: 5px; - padding-right: 5px; + border-spacing: var(--skip-notice-border-horizontal) var(--skip-notice-border-vertical); + padding-left: var(--skip-notice-padding); + padding-right: var(--skip-notice-padding); border-collapse: unset; } @@ -505,12 +514,51 @@ input::-webkit-inner-spin-button { margin-bottom: 5px; background-color: rgba(28, 28, 28, 0.9); - border-color: rgb(130,0,0,0.9); + border-color: var(--sb-dark-red-outline); color: white; border-width: 3px; padding: 3px; } +/* Start SelectorComponent */ + +.sbSelector { + position: absolute; + text-align: center; + width: calc(100% - var(--skip-notice-right) - var(--skip-notice-padding) * 2 - var(--skip-notice-margin) * 2 - var(--skip-notice-border-horizontal) * 2); + + z-index: 1000; +} + +.sbSelectorBackground { + text-align: center; + + background-color: rgba(28, 28, 28, 0.9); + border-radius: 6px; + padding: 3px; + margin: auto; + width: 170px; +} + +.sbSelectorOption { + cursor: pointer; + background-color: rgb(43, 43, 43); + padding: 5px; + margin: 5px; + color: white; + border-radius: 5px; + font-size: 14px; + + margin-left: auto; + margin-right: auto; +} + +.sbSelectorOption:hover { + background-color: #3a0000; +} + +/* End SelectorComponent */ + .helpButton { height: 25px; cursor: pointer; diff --git a/src/components/SelectorComponent.tsx b/src/components/SelectorComponent.tsx new file mode 100644 index 00000000..018d78a6 --- /dev/null +++ b/src/components/SelectorComponent.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; + +export interface SelectorOption { + label: string; +} + +export interface SelectorProps { + id: string; + options: SelectorOption[]; + onChange: (value: string) => void; +} + +export interface SelectorState { + +} + +class SelectorComponent extends React.Component { + + constructor(props: SelectorProps) { + super(props); + + // Setup state + this.state = { + + } + } + + render(): React.ReactElement { + return ( +
+
+ {this.getOptions()} +
+
+ ); + } + + getOptions(): React.ReactElement[] { + const result: React.ReactElement[] = []; + for (const option of this.props.options) { + result.push( +
this.props.onChange(option.label)} + key={option.label}> + {option.label} +
+ ); + } + + return result; + } +} + +export default SelectorComponent; \ No newline at end of file diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index d6d7c44a..926b2ff4 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -1,11 +1,12 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config"; -import { ActionType, ActionTypes, Category, CategoryActionType, ContentContainer, SponsorTime } from "../types"; +import { ActionType, ActionTypes, Category, CategoryActionType, ChannelIDStatus, ContentContainer, SponsorTime } from "../types"; import Utils from "../utils"; import { getCategoryActionType } from "../utils/categoryUtils"; import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; import { RectangleTooltip } from "../render/RectangleTooltip"; +import SelectorComponent, { SelectorOption } from "./SelectorComponent"; const utils = new Utils(); @@ -25,6 +26,9 @@ export interface SponsorTimeEditState { editing: boolean; sponsorTimeEdits: [string, string]; selectedCategory: Category; + description: string; + suggestedNames: SelectorOption[]; + chapterNameSelectorOpen: boolean; } const DEFAULT_CATEGORY = "chooseACategory"; @@ -42,6 +46,9 @@ class SponsorTimeEditComponent extends React.Component +
this.setState({chapterNameSelectorOpen: false})}> this.saveEditTimes()}> + value={this.state.description} + onChange={(e) => this.descriptionUpdate(e.target.value)} + onFocus={() => this.setState({chapterNameSelectorOpen: true})}> + {this.state.chapterNameSelectorOpen && this.state.description && + this.descriptionUpdate(v)} + /> + }
): ""} @@ -520,6 +539,41 @@ class SponsorTimeEditComponent extends React.Component { + if (this.props.contentContainer().channelIDInfo.status !== ChannelIDStatus.Found) return; + + this.fetchingSuggestions = true; + const result = await utils.asyncRequestToServer("GET", "/api/chapterNames", { + description, + channelID: this.props.contentContainer().channelIDInfo.id + }); + + if (result.ok) { + try { + const names = JSON.parse(result.responseText) as {description: string}[]; + this.setState({ + suggestedNames: names.map(n => ({ + label: n.description + })) + }); + } catch (e) {} //eslint-disable-line no-empty + } + + this.fetchingSuggestions = false; + } + configUpdate(): void { this.forceUpdate(); } diff --git a/src/content.ts b/src/content.ts index 7110a9b8..9f65370c 100644 --- a/src/content.ts +++ b/src/content.ts @@ -124,7 +124,8 @@ const skipNoticeContentContainer: ContentContainer = () => ({ previewTime, videoInfo, getRealCurrentTime: getRealCurrentTime, - lockedCategories + lockedCategories, + channelIDInfo }); // value determining when to count segment as skipped and send telemetry to server (percent based) diff --git a/src/types.ts b/src/types.ts index cc8d65c4..9d877d67 100644 --- a/src/types.ts +++ b/src/types.ts @@ -21,7 +21,8 @@ export interface ContentContainer { previewTime: (time: number, unpause?: boolean) => void, videoInfo: VideoInfo, getRealCurrentTime: () => number, - lockedCategories: string[] + lockedCategories: string[], + channelIDInfo: ChannelIDInfo } } From a69c19581d2a94156137b4dd97d845ec893cf8a7 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 01:28:23 -0400 Subject: [PATCH 023/101] Fix chapter bar showing as empty sometimes --- src/js-components/previewBar.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index c451edd2..390b806a 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -227,7 +227,19 @@ class PreviewBar { this.customChaptersBar?.remove(); - if (segments?.length <= 0) { + // Merge overlapping chapters + const mergedSegments = segments?.filter((segment) => this.chapterFilter(segment)) + .reduce((acc, curr) => { + if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { + acc.push(curr); + } else { + acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); + } + + return acc; + }, [] as PreviewBarSegment[]); + + if (mergedSegments?.length <= 0) { chapterBar.style.removeProperty("display"); return; } @@ -241,18 +253,6 @@ class PreviewBar { this.customChaptersBar = newChapterBar; this.chaptersBarSegments = segments; - // Merge overlapping chapters - const mergedSegments = segments.filter((segment) => this.chapterFilter(segment)) - .reduce((acc, curr) => { - if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { - acc.push(curr); - } else { - acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); - } - - return acc; - }, [] as PreviewBarSegment[]); - // Modify it to have sections for each segment for (let i = 0; i < mergedSegments.length; i++) { const segment = mergedSegments[i]; From 8e022bfb289019d8fc7ea4058e256070637482e7 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 01:06:34 -0500 Subject: [PATCH 024/101] Fix offset on popup for chapters --- src/popup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/popup.ts b/src/popup.ts index 41bf1062..4ea1b4da 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -1,7 +1,7 @@ import Config from "./config"; import Utils from "./utils"; -import { SponsorTime, SponsorHideType, CategoryActionType } from "./types"; +import { SponsorTime, SponsorHideType, CategoryActionType, ActionType } from "./types"; import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes"; import { showDonationLink } from "./utils/configUtils"; import { getCategoryActionType } from "./utils/categoryUtils"; @@ -411,7 +411,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { : ""); segmentTimeFromToNode.style.margin = "5px"; - sponsorTimeButton.appendChild(categoryColorCircle); + if (segmentTimes[i].actionType !== ActionType.Chapter) sponsorTimeButton.appendChild(categoryColorCircle); sponsorTimeButton.appendChild(textNode); sponsorTimeButton.appendChild(segmentTimeFromToNode); From 3a2d9c0e0e63c0302200304a9da5884a61c4ebe3 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 01:12:01 -0500 Subject: [PATCH 025/101] Update options page for chapters --- config.json.example | 3 +- .../CategorySkipOptionsComponent.tsx | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/config.json.example b/config.json.example index 787aab2e..a77a3646 100644 --- a/config.json.example +++ b/config.json.example @@ -24,6 +24,7 @@ "music_offtopic": "https://wiki.sponsor.ajay.app/w/Music:_Non-Music_Section", "poi_highlight": "https://wiki.sponsor.ajay.app/w/Highlight", "guidelines": "https://wiki.sponsor.ajay.app/w/Guidelines", - "mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment" + "mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment", + "chapter": "https://wiki.sponsor.ajay.app/w/Chapter" } } diff --git a/src/components/CategorySkipOptionsComponent.tsx b/src/components/CategorySkipOptionsComponent.tsx index 2d18000a..b65898df 100644 --- a/src/components/CategorySkipOptionsComponent.tsx +++ b/src/components/CategorySkipOptionsComponent.tsx @@ -69,23 +69,27 @@ class CategorySkipOptionsComponent extends React.Component - - this.setColorState(event, false)} - value={this.state.color} /> - + {this.props.category !== "chapter" && + + this.setColorState(event, false)} + value={this.state.color} /> + + } - - this.setColorState(event, true)} - value={this.state.previewColor} /> - + {this.props.category !== "chapter" && + + this.setColorState(event, true)} + value={this.state.previewColor} /> + + } @@ -154,7 +158,8 @@ class CategorySkipOptionsComponent extends React.Component Date: Sun, 7 Nov 2021 15:26:00 -0500 Subject: [PATCH 026/101] Add chapter sorting method to show small chapters in the middle of large ones --- src/config.ts | 2 + src/js-components/previewBar.ts | 132 ++++++-- src/types.ts | 5 +- test/previewBar.test.ts | 575 ++++++++++++++++++++++++++++++++ 4 files changed, 677 insertions(+), 37 deletions(-) create mode 100644 test/previewBar.test.ts diff --git a/src/config.ts b/src/config.ts index 51c1f06e..f76f634b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -427,6 +427,8 @@ function migrateOldFormats(config: SBConfig) { } async function setupConfig() { + if (typeof(chrome) === "undefined") return; + await fetchConfig(); addDefaults(); convertJSON(); diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 390b806a..37bc7098 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -6,9 +6,9 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd 'use strict'; import Config from "../config"; -import { ActionType, ActionTypes, Category, CategoryActionType, SponsorTime } from "../types"; +import { ActionType, Category, CategoryActionType, SegmentContainer, SponsorTime } from "../types"; import Utils from "../utils"; -import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils"; +import { getCategoryActionType } from "../utils/categoryUtils"; const utils = new Utils(); const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; @@ -22,6 +22,10 @@ export interface PreviewBarSegment { description: string; } +interface ChapterGroup extends SegmentContainer { + originalDuration: number +} + class PreviewBar { container: HTMLUListElement; categoryTooltip?: HTMLDivElement; @@ -39,7 +43,8 @@ class PreviewBar { customChaptersBar: HTMLElement; chaptersBarSegments: PreviewBarSegment[]; - constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) { + constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, test=false) { + if (test) return; this.container = document.createElement('ul'); this.container.id = 'previewbar'; @@ -228,18 +233,10 @@ class PreviewBar { this.customChaptersBar?.remove(); // Merge overlapping chapters - const mergedSegments = segments?.filter((segment) => this.chapterFilter(segment)) - .reduce((acc, curr) => { - if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { - acc.push(curr); - } else { - acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); - } + const filteredSegments = segments?.filter((segment) => this.chapterFilter(segment)); + const chaptersToRender = this.createChapterRenderGroups(filteredSegments).filter((segment) => this.chapterGroupFilter(segment)); - return acc; - }, [] as PreviewBarSegment[]); - - if (mergedSegments?.length <= 0) { + if (chaptersToRender?.length <= 0) { chapterBar.style.removeProperty("display"); return; } @@ -254,31 +251,13 @@ class PreviewBar { this.chaptersBarSegments = segments; // Modify it to have sections for each segment - for (let i = 0; i < mergedSegments.length; i++) { - const segment = mergedSegments[i]; - if (i === 0 && segment.segment[0] > 0) { - const newBlankSection = originalSection.cloneNode(true) as HTMLElement; - const blankDuration = segment.segment[0]; - - this.setupChapterSection(newBlankSection, blankDuration); - newChapterBar.appendChild(newBlankSection); - } - - const duration = segment.segment[1] - segment.segment[0]; + for (let i = 0; i < chaptersToRender.length; i++) { + const chapter = chaptersToRender[i].segment; + const duration = chapter[1] - chapter[0]; const newSection = originalSection.cloneNode(true) as HTMLElement; this.setupChapterSection(newSection, duration); newChapterBar.appendChild(newSection); - - const nextSegment = mergedSegments[i + 1]; - const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; - if (this.timeToDecimal(nextTime - segment.segment[1]) > MIN_CHAPTER_SIZE) { - const newBlankSection = originalSection.cloneNode(true) as HTMLElement; - const blankDuration = nextTime - segment.segment[1]; - - this.setupChapterSection(newBlankSection, blankDuration); - newChapterBar.appendChild(newBlankSection); - } } // Hide old bar @@ -294,6 +273,83 @@ class PreviewBar { this.updateChapterAllMutation(chapterBar, progressBar, true); } + createChapterRenderGroups(segments: PreviewBarSegment[]): ChapterGroup[] { + const result: ChapterGroup[] = []; + + segments?.forEach((segment, index) => { + const latestChapter = result[result.length - 1]; + if (latestChapter && latestChapter.segment[1] > segment.segment[0]) { + const segmentDuration = segment.segment[1] - segment.segment[0]; + if (segmentDuration < latestChapter.originalDuration) { + // Remove latest if it starts too late + let latestValidChapter = latestChapter; + const chaptersToAddBack: ChapterGroup[] = [] + while (latestValidChapter?.segment[0] >= segment.segment[0]) { + const invalidChapter = result.pop(); + if (invalidChapter.segment[1] > segment.segment[1]) { + if (invalidChapter.segment[0] === segment.segment[0]) { + invalidChapter.segment[0] = segment.segment[1]; + } + chaptersToAddBack.push(invalidChapter); + } + latestValidChapter = result[result.length - 1]; + } + + // Split the latest chapter if smaller + result.push({ + segment: [segment.segment[0], segment.segment[1]], + originalDuration: segmentDuration, + }); + if (latestValidChapter?.segment[1] > segment.segment[1]) { + result.push({ + segment: [segment.segment[1], latestValidChapter.segment[1]], + originalDuration: latestValidChapter.originalDuration + }); + } + + result.push(...chaptersToAddBack); + if (latestValidChapter) latestValidChapter.segment[1] = segment.segment[0]; + } else { + // Start at end of old one if bigger + result.push({ + segment: [latestChapter.segment[1], segment.segment[1]], + originalDuration: segmentDuration + }); + } + } else { + // Add empty buffer before segment if needed + const lastTime = latestChapter?.segment[1] || 0; + if (segment.segment[0] > lastTime) { + result.push({ + segment: [lastTime, segment.segment[0]], + originalDuration: 0 + }); + } + + // Normal case + result.push({ + segment: [segment.segment[0], segment.segment[1]], + originalDuration: segment.segment[1] - segment.segment[0] + }); + } + + // Add empty buffer after segment if needed + if (index === segments.length - 1) { + const nextSegment = segments[index + 1]; + const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; + const lastTime = result[result.length - 1]?.segment[1] || segment.segment[1]; + if (this.timeToDecimal(nextTime - lastTime) > MIN_CHAPTER_SIZE) { + result.push({ + segment: [lastTime, nextTime], + originalDuration: 0 + }); + } + } + }); + + return result; + } + private setupChapterSection(section: HTMLElement, duration: number): void { section.style.marginRight = "2px"; section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; @@ -447,7 +503,11 @@ class PreviewBar { private chapterFilter(segment: PreviewBarSegment): boolean { return getCategoryActionType(segment.category) !== CategoryActionType.POI - && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > MIN_CHAPTER_SIZE; + && this.chapterGroupFilter(segment); + } + + private chapterGroupFilter(segment: SegmentContainer): boolean { + return segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > MIN_CHAPTER_SIZE; } timeToPercentage(time: number): string { diff --git a/src/types.ts b/src/types.ts index 9d877d67..0c1a7d87 100644 --- a/src/types.ts +++ b/src/types.ts @@ -74,8 +74,11 @@ export enum SponsorSourceType { Local = 1 } -export interface SponsorTime { +export interface SegmentContainer { segment: [number] | [number, number]; +} + +export interface SponsorTime extends SegmentContainer { UUID: SegmentUUID; locked?: number; diff --git a/test/previewBar.test.ts b/test/previewBar.test.ts new file mode 100644 index 00000000..755d79a6 --- /dev/null +++ b/test/previewBar.test.ts @@ -0,0 +1,575 @@ +import PreviewBar, { PreviewBarSegment } from "../src/js-components/previewBar"; + +describe("createChapterRenderGroups", () => { + let previewBar: PreviewBar; + beforeEach(() => { + previewBar = new PreviewBar(null, null, null, true); + }) + + it("Two unrelated times", () => { + previewBar.videoDuration = 315; + const groups = previewBar.createChapterRenderGroups([{ + segment: [2, 30], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }, { + segment: [50, 80], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }] as PreviewBarSegment[]); + + expect(groups).toStrictEqual([{ + segment: [0, 2], + originalDuration: 0 + }, { + segment: [2, 30], + originalDuration: 30 - 2 + }, { + segment: [30, 50], + originalDuration: 0 + }, { + segment: [50, 80], + originalDuration: 80 - 50 + }, { + segment: [80, 315], + originalDuration: 0 + }]); + }); + + it("Small time in bigger time", () => { + previewBar.videoDuration = 315; + const groups = previewBar.createChapterRenderGroups([{ + segment: [2.52, 30], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }, { + segment: [20, 25], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }] as PreviewBarSegment[]); + + expect(groups).toStrictEqual([{ + segment: [0, 2.52], + originalDuration: 0 + }, { + segment: [2.52, 20], + originalDuration: 30 - 2.52 + }, { + segment: [20, 25], + originalDuration: 25 - 20 + }, { + segment: [25, 30], + originalDuration: 30 - 2.52 + }, { + segment: [30, 315], + originalDuration: 0 + }]); + }); + + it("Same start time", () => { + previewBar.videoDuration = 315; + const groups = previewBar.createChapterRenderGroups([{ + segment: [2.52, 30], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }, { + segment: [2.52, 40], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }] as PreviewBarSegment[]); + + expect(groups).toStrictEqual([{ + segment: [0, 2.52], + originalDuration: 0 + }, { + segment: [2.52, 30], + originalDuration: 30 - 2.52 + }, { + segment: [30, 40], + originalDuration: 40 - 2.52 + }, { + segment: [40, 315], + originalDuration: 0 + }]); + }); + + it("Lots of overlapping segments", () => { + previewBar.videoDuration = 315.061; + const groups = previewBar.createChapterRenderGroups([ + { + "category": "chapter", + "segment": [ + 0, + 49.977 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "b1919787a85cd422af07136a913830eda1364d32e8a9e12104cf5e3bad8f6f45", + "description": "Start of video" + }, + { + "category": "sponsor", + "segment": [ + 2.926, + 5 + ], + "locked": 1, + "votes": 2, + "videoDuration": 316, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "chapter", + "segment": [ + 14.487, + 37.133 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "b1919787a85cd422af07136a913830eda1364d32e8a9e12104cf5e3bad8f6f45", + "description": "Subset of start" + }, + { + "category": "sponsor", + "segment": [ + 23.450537, + 34.486084 + ], + "locked": 0, + "votes": -1, + "videoDuration": 315.061, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "interaction", + "segment": [ + 50.015343, + 56.775314 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "b2a85e8cdfbf21dd504babbcaca7f751b55a5a2df8179c1a83a121d0f5d56c0e", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 62.51888, + 74.33331 + ], + "locked": 0, + "votes": -1, + "videoDuration": 316, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 88.71328, + 96.05933 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "6c08c092db2b7a31210717cc1f2652e7e97d032e03c82b029a27c81cead1f90c", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 101.50703, + 115.088326 + ], + "votes": 0, + "videoDuration": 315.061, + "userID": "2db207ad4b7a535a548fab293f4567bf97373997e67aadb47df8f91b673f6e53", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 122.211845, + 137.42178 + ], + "locked": 0, + "votes": 1, + "videoDuration": 0, + "userID": "0312cbfa514d9d2dfb737816dc45f52aba7c23f0a3f0911273a6993b2cb57fcc", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 144.08913, + 160.14084 + ], + "locked": 0, + "votes": -1, + "videoDuration": 316, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 164.22084, + 170.98082 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "845c4377060d5801f5324f8e1be1e8373bfd9addcf6c68fc5a3c038111b506a3", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 180.56674, + 189.16516 + ], + "locked": 0, + "votes": -1, + "videoDuration": 315.061, + "userID": "7c6b015687db7800c05756a0fd226fd8d101f5a1e1bfb1e5d97c440331fd6cb7", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 204.10468, + 211.87865 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "3472e8ee00b5da957377ae32d59ddd3095c2b634c2c0c970dfabfb81d143699f", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 214.92064, + 222.0186 + ], + "locked": 0, + "votes": 0, + "videoDuration": 0, + "userID": "62a00dffb344d27de7adf8ea32801c2fc0580087dc8d282837923e4bda6a1745", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 233.0754, + 244.56734 + ], + "locked": 0, + "votes": -1, + "videoDuration": 315, + "userID": "dcf7fb0a6c071d5a93273ebcbecaee566e0ff98181057a36ed855e9b92bf25ea", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 260.64053, + 269.35938 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "e0238059ae4e711567af5b08a3afecfe45601c995b0ea2f37ca43f15fca4e298", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 288.686, + 301.96 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "e0238059ae4e711567af5b08a3afecfe45601c995b0ea2f37ca43f15fca4e298", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 288.686, + 295 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "e0238059ae4e711567af5b08a3afecfe45601c995b0ea2f37ca43f15fca4e298", + "description": "" + }] as unknown as PreviewBarSegment[]); + + expect(groups).toStrictEqual([ + { + "segment": [ + 0, + 2.926 + ], + "originalDuration": 49.977 + }, + { + "segment": [ + 2.926, + 5 + ], + "originalDuration": 2.074 + }, + { + "segment": [ + 5, + 14.487 + ], + "originalDuration": 49.977 + }, + { + "segment": [ + 14.487, + 23.450537 + ], + "originalDuration": 22.646 + }, + { + "segment": [ + 23.450537, + 34.486084 + ], + "originalDuration": 11.035546999999998 + }, + { + "segment": [ + 34.486084, + 37.133 + ], + "originalDuration": 22.646 + }, + { + "segment": [ + 37.133, + 49.977 + ], + "originalDuration": 49.977 + }, + { + "segment": [ + 49.977, + 50.015343 + ], + "originalDuration": 0 + }, + { + "segment": [ + 50.015343, + 56.775314 + ], + "originalDuration": 6.759971 + }, + { + "segment": [ + 56.775314, + 62.51888 + ], + "originalDuration": 0 + }, + { + "segment": [ + 62.51888, + 74.33331 + ], + "originalDuration": 11.814429999999994 + }, + { + "segment": [ + 74.33331, + 88.71328 + ], + "originalDuration": 0 + }, + { + "segment": [ + 88.71328, + 96.05933 + ], + "originalDuration": 7.346050000000005 + }, + { + "segment": [ + 96.05933, + 101.50703 + ], + "originalDuration": 0 + }, + { + "segment": [ + 101.50703, + 115.088326 + ], + "originalDuration": 13.581295999999995 + }, + { + "segment": [ + 115.088326, + 122.211845 + ], + "originalDuration": 0 + }, + { + "segment": [ + 122.211845, + 137.42178 + ], + "originalDuration": 15.209935000000016 + }, + { + "segment": [ + 137.42178, + 144.08913 + ], + "originalDuration": 0 + }, + { + "segment": [ + 144.08913, + 160.14084 + ], + "originalDuration": 16.051709999999986 + }, + { + "segment": [ + 160.14084, + 164.22084 + ], + "originalDuration": 0 + }, + { + "segment": [ + 164.22084, + 170.98082 + ], + "originalDuration": 6.759979999999985 + }, + { + "segment": [ + 170.98082, + 180.56674 + ], + "originalDuration": 0 + }, + { + "segment": [ + 180.56674, + 189.16516 + ], + "originalDuration": 8.598419999999976 + }, + { + "segment": [ + 189.16516, + 204.10468 + ], + "originalDuration": 0 + }, + { + "segment": [ + 204.10468, + 211.87865 + ], + "originalDuration": 7.773969999999991 + }, + { + "segment": [ + 211.87865, + 214.92064 + ], + "originalDuration": 0 + }, + { + "segment": [ + 214.92064, + 222.0186 + ], + "originalDuration": 7.0979600000000005 + }, + { + "segment": [ + 222.0186, + 233.0754 + ], + "originalDuration": 0 + }, + { + "segment": [ + 233.0754, + 244.56734 + ], + "originalDuration": 11.49194 + }, + { + "segment": [ + 244.56734, + 260.64053 + ], + "originalDuration": 0 + }, + { + "segment": [ + 260.64053, + 269.35938 + ], + "originalDuration": 8.718849999999975 + }, + { + "segment": [ + 269.35938, + 288.686 + ], + "originalDuration": 0 + }, + { + "segment": [ + 288.686, + 295 + ], + "originalDuration": 6.314000000000021 + }, + { + "segment": [ + 295, + 301.96 + ], + "originalDuration": 13.274000000000001 + }, + { + "segment": [ + 301.96, + 315.061 + ], + "originalDuration": 0 + } + ]); + }) +}) \ No newline at end of file From 0d0459a3a30f97d3d28a2911e56e5bbdcce2a0c7 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 15:31:08 -0500 Subject: [PATCH 027/101] Make tests have config.json --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 83a957e4..035179b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,8 @@ jobs: with: node-version: '16' - run: npm ci - + - name: Copy configuration + run: cp config.json.example config.json + - name: Run tests run: npm run test-without-building \ No newline at end of file From af547ce74514e574f6c4ae38f95bd0cb2e28e7a6 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 15:38:41 -0500 Subject: [PATCH 028/101] Show category description in popup --- src/popup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/popup.ts b/src/popup.ts index 4ea1b4da..56ffd528 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -403,7 +403,8 @@ async function runThePopup(messageListener?: MessageListener): Promise { extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")"; } - const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo); + const name = segmentTimes[i].description || utils.shortCategoryName(segmentTimes[i].category); + const textNode = document.createTextNode(name + extraInfo); const segmentTimeFromToNode = document.createElement("div"); segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) + (getCategoryActionType(segmentTimes[i].category) !== CategoryActionType.POI From c38cc07e0a761de531809f45ea1efb0d4729c344 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 7 Nov 2021 16:09:01 -0500 Subject: [PATCH 029/101] Fix stack overflow issue with unfinished preview segments --- src/content.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/content.ts b/src/content.ts index 9f65370c..2104ea40 100644 --- a/src/content.ts +++ b/src/content.ts @@ -1154,6 +1154,7 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: || (includeIntersectingSegments && possibleTimes[i].scheduledTime < minimum && possibleTimes[i].segment[1] > minimum))) && (!onlySkippableSponsors || shouldSkip(possibleTimes[i])) && (!hideHiddenSponsors || possibleTimes[i].hidden === SponsorHideType.Visible) + && possibleTimes[i].segment.length === 2 && getCategoryActionType(possibleTimes[i].category) === CategoryActionType.Skippable) { scheduledTimes.push(possibleTimes[i].scheduledTime); From 798fd8b3f36bf4cbc5d2e7f62690a00df68d1a21 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Dec 2021 02:13:25 -0500 Subject: [PATCH 030/101] Add tabs for chapters and other segments --- public/_locales/en/messages.json | 6 ++++ public/popup.css | 34 +++++++++++++++++++++++ public/popup.html | 8 ++++++ src/popup.ts | 47 ++++++++++++++++++++++++++++++-- 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 714eb582..d20fc8d7 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -25,6 +25,12 @@ "Segments": { "message": "segments" }, + "SegmentsCap": { + "message": "Segments" + }, + "Chapters": { + "message": "Chapters" + }, "upvoteButtonInfo": { "message": "Upvote this submission" }, diff --git a/public/popup.css b/public/popup.css index eab8dcd3..28e50758 100644 --- a/public/popup.css +++ b/public/popup.css @@ -409,3 +409,37 @@ label>p, #disableExtension>p, #usernameValue, #usernameElement > div > p,#sponso border-radius: 5px; } + +#issueReporterTabs { + margin: 5px; +} + +#issueReporterTabs > span { + padding: 2px 4px; + margin: 0 3px; + cursor: pointer; + background-color: #444848; + border-radius: 10px; +} + +#issueReporterTabs > span > span { + position: relative; + padding: 0.2em 0; +} + +#issueReporterTabs > span > span::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 0.1em; + background-color: rgb(145, 0, 0); + transition: transform 300ms; + transform: scaleX(0); + transform-origin: center; +} + +#issueReporterTabs > span.sbSelected > span::after { + transform: scaleX(0.8); +} \ No newline at end of file diff --git a/public/popup.html b/public/popup.html index f21c674d..618cb986 100644 --- a/public/popup.html +++ b/public/popup.html @@ -24,6 +24,14 @@
+
diff --git a/src/popup.ts b/src/popup.ts index 56ffd528..ad018620 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -107,7 +107,10 @@ async function runThePopup(messageListener?: MessageListener): Promise { //"downloadedSponsorMessageTimes", "refreshSegmentsButton", "whitelistButton", - "sbDonate" + "sbDonate", + "issueReporterTabs", + "issueReporterTabSegments", + "issueReporterTabChapters" ].forEach(id => PageElements[id] = document.getElementById(id)); // Hide donate button if wanted (Safari, or user choice) @@ -147,6 +150,12 @@ async function runThePopup(messageListener?: MessageListener): Promise { //current video ID of this tab let currentVideoID = null; + enum SegmentTab { + Segments, + Chapters + } + let segmentTab = SegmentTab.Segments; + //show proper disable skipping button const disableSkipping = Config.config.disableSkipping; if (disableSkipping != undefined && disableSkipping) { @@ -240,6 +249,22 @@ async function runThePopup(messageListener?: MessageListener): Promise { getSegmentsFromContentScript(false); + PageElements.issueReporterTabSegments.addEventListener("click", () => { + PageElements.issueReporterTabSegments.classList.add("sbSelected"); + PageElements.issueReporterTabChapters.classList.remove("sbSelected"); + + segmentTab = SegmentTab.Segments; + getSegmentsFromContentScript(true); + }); + + PageElements.issueReporterTabChapters.addEventListener("click", () => { + PageElements.issueReporterTabSegments.classList.remove("sbSelected"); + PageElements.issueReporterTabChapters.classList.add("sbSelected"); + + segmentTab = SegmentTab.Chapters; + getSegmentsFromContentScript(true); + }); + function onTabs(tabs, updating: boolean): void { messageHandler.sendMessage(tabs[0].id, { message: 'getVideoID' }, function (result) { if (result !== undefined && result.videoID) { @@ -369,8 +394,25 @@ async function runThePopup(messageListener?: MessageListener): Promise { //display the video times from the array at the top, in a different section function displayDownloadedSponsorTimes(request: { found: boolean, sponsorTimes: SponsorTime[] }) { if (request.sponsorTimes != undefined) { + let currentSegmentTab = segmentTab; + if (!request.sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter)) { + PageElements.issueReporterTabs.classList.add("hidden"); + currentSegmentTab = SegmentTab.Segments; + } else { + PageElements.issueReporterTabs.classList.remove("hidden"); + } + // Sort list by start time const segmentTimes = request.sponsorTimes + .filter((segment) => { + if (currentSegmentTab === SegmentTab.Segments) { + return segment.actionType !== ActionType.Chapter; + } else if (currentSegmentTab === SegmentTab.Chapters) { + return segment.actionType === ActionType.Chapter; + } else { + return true; + } + }) .sort((a, b) => a.segment[1] - b.segment[1]) .sort((a, b) => a.segment[0] - b.segment[0]); @@ -438,7 +480,6 @@ async function runThePopup(messageListener?: MessageListener): Promise { downvoteButton.addEventListener("click", () => vote(0, UUID)); //uuid button - const uuidButton = document.createElement("img"); uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID; uuidButton.className = "voteButton"; @@ -563,6 +604,8 @@ async function runThePopup(messageListener?: MessageListener): Promise { //this is not a YouTube video page function displayNoVideo() { document.getElementById("loadingIndicator").innerText = chrome.i18n.getMessage("noVideoID"); + + PageElements.issueReporterTabs.classList.add("hidden"); } function addVoteMessage(message, UUID) { From 05153a152d35def07cee9f616afff22d9b2c11cd Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Dec 2021 20:35:36 -0500 Subject: [PATCH 031/101] Add skip button to popup for segments and chapters --- manifest/manifest.json | 1 + public/icons/skip.svg | 1 + src/content.ts | 8 +++++-- src/messageTypes.ts | 9 ++++++-- src/popup.ts | 51 +++++++++++++++++++++++++++++++++++++----- 5 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 public/icons/skip.svg diff --git a/manifest/manifest.json b/manifest/manifest.json index 74f5e48d..2f6a536c 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -47,6 +47,7 @@ "icons/beep.ogg", "icons/pause.svg", "icons/stop.svg", + "icons/skip.svg", "icons/PlayerInfoIconSponsorBlocker.svg", "icons/PlayerDeleteIconSponsorBlocker.svg", "popup.html", diff --git a/public/icons/skip.svg b/public/icons/skip.svg new file mode 100644 index 00000000..774e741c --- /dev/null +++ b/public/icons/skip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/content.ts b/src/content.ts index 2104ea40..81bad72b 100644 --- a/src/content.ts +++ b/src/content.ts @@ -198,13 +198,17 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo })); return true; + case "unskip": + unskipSponsorTime(sponsorTimes.find((segment) => segment.UUID === request.UUID)); + break; + case "reskip": + reskipSponsorTime(sponsorTimes.find((segment) => segment.UUID === request.UUID)); + break; } } /** * Called when the config is updated - * - * @param {String} changes */ function contentConfigUpdateListener(changes: StorageChangesObject) { for (const key in changes) { diff --git a/src/messageTypes.ts b/src/messageTypes.ts index 4989c741..5f6f25c2 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -2,7 +2,7 @@ // Message and Response Types // -import { SponsorTime } from "./types"; +import { SegmentUUID, SponsorTime } from "./types"; interface BaseMessage { from?: string; @@ -29,7 +29,12 @@ interface IsInfoFoundMessage { updating: boolean; } -export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage); +interface SkipMessage { + message: "unskip" | "reskip"; + UUID: SegmentUUID; +} + +export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SkipMessage); export interface IsInfoFoundMessageResponse { found: boolean; diff --git a/src/popup.ts b/src/popup.ts index ad018620..26c6c287 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -1,7 +1,7 @@ import Config from "./config"; import Utils from "./utils"; -import { SponsorTime, SponsorHideType, CategoryActionType, ActionType } from "./types"; +import { SponsorTime, SponsorHideType, CategoryActionType, ActionType, SegmentUUID } from "./types"; import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes"; import { showDonationLink } from "./utils/configUtils"; import { getCategoryActionType } from "./utils/categoryUtils"; @@ -426,13 +426,15 @@ async function runThePopup(messageListener?: MessageListener): Promise { for (let i = 0; i < segmentTimes.length; i++) { const UUID = segmentTimes[i].UUID; const locked = segmentTimes[i].locked; + const category = segmentTimes[i].category; + const actionType = segmentTimes[i].actionType; const sponsorTimeButton = document.createElement("button"); sponsorTimeButton.className = "segmentTimeButton popupElement"; const categoryColorCircle = document.createElement("span"); categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID; - categoryColorCircle.style.backgroundColor = Config.config.barTypes[segmentTimes[i].category]?.color; + categoryColorCircle.style.backgroundColor = Config.config.barTypes[category]?.color; categoryColorCircle.classList.add("dot"); categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle"); @@ -445,16 +447,16 @@ async function runThePopup(messageListener?: MessageListener): Promise { extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")"; } - const name = segmentTimes[i].description || utils.shortCategoryName(segmentTimes[i].category); + const name = segmentTimes[i].description || utils.shortCategoryName(category); const textNode = document.createTextNode(name + extraInfo); const segmentTimeFromToNode = document.createElement("div"); segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) + - (getCategoryActionType(segmentTimes[i].category) !== CategoryActionType.POI + (getCategoryActionType(category) !== CategoryActionType.POI ? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true) : ""); segmentTimeFromToNode.style.margin = "5px"; - if (segmentTimes[i].actionType !== ActionType.Chapter) sponsorTimeButton.appendChild(categoryColorCircle); + if (actionType !== ActionType.Chapter) sponsorTimeButton.appendChild(categoryColorCircle); sponsorTimeButton.appendChild(textNode); sponsorTimeButton.appendChild(segmentTimeFromToNode); @@ -490,10 +492,18 @@ async function runThePopup(messageListener?: MessageListener): Promise { stopAnimation(); }); + const skipButton = document.createElement("img"); + skipButton.id = "sponsorTimesSkipButtonContainer" + UUID; + skipButton.className = "voteButton"; + skipButton.src = chrome.runtime.getURL("icons/skip.svg"); + skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton)); + container.addEventListener("dblclick", () => skipSegment(actionType, UUID)); + //add thumbs up, thumbs down and uuid copy buttons to the container voteButtonsContainer.appendChild(upvoteButton); voteButtonsContainer.appendChild(downvoteButton); voteButtonsContainer.appendChild(uuidButton); + voteButtonsContainer.appendChild(skipButton); //add click listener to open up vote panel sponsorTimeButton.addEventListener("click", function () { @@ -756,6 +766,37 @@ async function runThePopup(messageListener?: MessageListener): Promise { ); } + function skipSegment(actionType: ActionType, UUID: SegmentUUID, element?: HTMLElement): void { + if (actionType === ActionType.Chapter) { + sendMessage({ + message: "unskip", + UUID: UUID + }); + } else { + sendMessage({ + message: "reskip", + UUID: UUID + }); + } + + if (element) { + const stopAnimation = utils.applyLoadingAnimation(element, 0.3); + stopAnimation(); + } + } + + function sendMessage(request: Message): void { + messageHandler.query({ + active: true, + currentWindow: true + }, tabs => { + messageHandler.sendMessage( + tabs[0].id, + request + ); + }); + } + /** * Should skipping be disabled (visuals stay) */ From e4f4a109650819b104f217d3775b3a7120a1a117 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Dec 2021 21:52:33 -0500 Subject: [PATCH 032/101] Fix seek bar chapter name not updating and schedule end times --- src/content.ts | 18 +++++++----------- src/js-components/previewBar.ts | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/content.ts b/src/content.ts index 81bad72b..f40cd2f1 100644 --- a/src/content.ts +++ b/src/content.ts @@ -470,9 +470,6 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?: } } - // Don't skip if this category should not be skipped - if (!shouldSkip(currentSkip) && !sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)) return; - const skippingFunction = () => { let forcedSkipTime: number = null; let forcedIncludeIntersectingSegments = false; @@ -480,7 +477,8 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?: if (incorrectVideoCheck(videoID, currentSkip)) return; - if (video.currentTime >= skipTime[0] && video.currentTime < skipTime[1]) { + if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)) + && video.currentTime >= skipTime[0] && video.currentTime < skipTime[1]) { skipToTime({ v: video, skipTime, @@ -1044,14 +1042,14 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool const { includedTimes: submittedArray, scheduledTimes: sponsorStartTimes } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments); - const { scheduledTimes: sponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true, true); + const { scheduledTimes: sponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true); const minSponsorTimeIndex = sponsorStartTimes.indexOf(Math.min(...sponsorStartTimesAfterCurrentTime)); const endTimeIndex = getLatestEndTimeIndex(submittedArray, minSponsorTimeIndex); const { includedTimes: unsubmittedArray, scheduledTimes: unsubmittedSponsorStartTimes } = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments); - const { scheduledTimes: unsubmittedSponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, false, false); + const { scheduledTimes: unsubmittedSponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimesSubmitting, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, false); const minUnsubmittedSponsorTimeIndex = unsubmittedSponsorStartTimes.indexOf(Math.min(...unsubmittedSponsorStartTimesAfterCurrentTime)); const previewEndTimeIndex = getLatestEndTimeIndex(unsubmittedArray, minUnsubmittedSponsorTimeIndex); @@ -1130,7 +1128,7 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH * the current time, but end after */ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean, - minimum?: number, onlySkippableSponsors = false, hideHiddenSponsors = false): {includedTimes: ScheduledTime[], scheduledTimes: number[]} { + minimum?: number, hideHiddenSponsors = false): {includedTimes: ScheduledTime[], scheduledTimes: number[]} { if (!sponsorTimes) return {includedTimes: [], scheduledTimes: []}; const includedTimes: ScheduledTime[] = []; @@ -1141,9 +1139,8 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: scheduledTime: sponsorTime.segment[0] })); - // Schedule at the end time to know when to unmute - sponsorTimes.filter(sponsorTime => sponsorTime.actionType === ActionType.Mute) - .forEach(sponsorTime => { + // Schedule at the end time to know when to unmute and remove title from seek bar + sponsorTimes.forEach(sponsorTime => { if (!possibleTimes.some((time) => sponsorTime.segment[1] === time.scheduledTime)) { possibleTimes.push({ ...sponsorTime, @@ -1156,7 +1153,6 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: if ((minimum === undefined || ((includeNonIntersectingSegments && possibleTimes[i].scheduledTime >= minimum) || (includeIntersectingSegments && possibleTimes[i].scheduledTime < minimum && possibleTimes[i].segment[1] > minimum))) - && (!onlySkippableSponsors || shouldSkip(possibleTimes[i])) && (!hideHiddenSponsors || possibleTimes[i].hidden === SponsorHideType.Visible) && possibleTimes[i].segment.length === 2 && getCategoryActionType(possibleTimes[i].category) === CategoryActionType.Skippable) { diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 37bc7098..f658f18a 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -446,7 +446,7 @@ class PreviewBar { if (!segments) return; const activeSegments = segments.filter((segment) => { - return segment.segment[0] <= currentTime && segment.segment[1] >= currentTime; + return segment.segment[0] <= currentTime && segment.segment[1] > currentTime; }); this.setActiveSegments(activeSegments); From ab1520c560183cd63239f00f4746a032e72bc704 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 25 Dec 2021 01:09:34 -0500 Subject: [PATCH 033/101] Show both categories and chapter names in hover text --- public/content.css | 21 +++++++ src/content.ts | 2 + src/js-components/previewBar.ts | 97 ++++++++++++++++++++++----------- src/utils/arrayUtils.ts | 6 ++ 4 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 src/utils/arrayUtils.ts diff --git a/public/content.css b/public/content.css index 24490e85..1acd1cd4 100644 --- a/public/content.css +++ b/public/content.css @@ -45,23 +45,44 @@ transform: translateY(-1em) !important; } +.ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips { + transform: translateY(-2em) !important; +} + .ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible { transform: translateY(-2em) !important; } +.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips { + transform: translateY(-4em) !important; +} + #movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper { transform: translateY(1em) !important; } +#movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper { + transform: translateY(2em) !important; +} + .ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper { transform: translateY(0.5em) !important; } +.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper { + transform: translateY(1em) !important; +} + .ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper > .ytp-tooltip-text { display: block !important; transform: translateY(1em) !important; } +.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper > .ytp-tooltip-text { + display: block !important; + transform: translateY(2em) !important; +} + div:hover > .sponsorBlockChapterBar { z-index: 41 !important; } diff --git a/src/content.ts b/src/content.ts index f40cd2f1..da4434f1 100644 --- a/src/content.ts +++ b/src/content.ts @@ -970,6 +970,7 @@ function updatePreviewBar(): void { previewBarSegments.push({ segment: segment.segment as [number, number], category: segment.category, + actionType: segment.actionType, unsubmitted: false, showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI, description: segment.description, @@ -981,6 +982,7 @@ function updatePreviewBar(): void { previewBarSegments.push({ segment: segment.segment as [number, number], category: segment.category, + actionType: segment.actionType, unsubmitted: true, showLarger: getCategoryActionType(segment.category) === CategoryActionType.POI, description: segment.description, diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index f658f18a..c35f6501 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -8,6 +8,7 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd import Config from "../config"; import { ActionType, Category, CategoryActionType, SegmentContainer, SponsorTime } from "../types"; import Utils from "../utils"; +import { partition } from "../utils/arrayUtils"; import { getCategoryActionType } from "../utils/categoryUtils"; const utils = new Utils(); @@ -17,6 +18,7 @@ const MIN_CHAPTER_SIZE = 0.003; export interface PreviewBarSegment { segment: [number, number]; category: Category; + actionType: ActionType; unsubmitted: boolean; showLarger: boolean; description: string; @@ -29,7 +31,8 @@ interface ChapterGroup extends SegmentContainer { class PreviewBar { container: HTMLUListElement; categoryTooltip?: HTMLDivElement; - tooltipContainer?: HTMLElement; + categoryTooltipContainer?: HTMLElement; + chapterTooltip?: HTMLDivElement; parent: HTMLElement; onMobileYouTube: boolean; @@ -64,16 +67,19 @@ class PreviewBar { // Create label placeholder this.categoryTooltip = document.createElement("div"); this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip"; + this.chapterTooltip = document.createElement("div"); + this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip"; const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper"); if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return; // Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init - this.tooltipContainer = tooltipTextWrapper.parentElement; + this.categoryTooltipContainer = tooltipTextWrapper.parentElement; const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title"); - if (!this.tooltipContainer || !titleTooltip) return; + if (!this.categoryTooltipContainer || !titleTooltip) return; tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling); + tooltipTextWrapper.insertBefore(this.chapterTooltip, titleTooltip.nextSibling); const seekBar = document.querySelector(".ytp-progress-bar-container"); if (!seekBar) return; @@ -89,7 +95,7 @@ class PreviewBar { }); const observer = new MutationObserver((mutations) => { - if (!mouseOnSeekBar || !this.categoryTooltip || !this.tooltipContainer) return; + if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer) return; // If the mutation observed is only for our tooltip text, ignore if (mutations.length === 1 && (mutations[0].target as HTMLElement).classList.contains("sponsorCategoryTooltip")) { @@ -114,37 +120,26 @@ class PreviewBar { if (timeInSeconds === null) return; // Find the segment at that location, using the shortest if multiple found - let segment: PreviewBarSegment | null = null; - let currentSegmentLength = Infinity; + const [normalSegments, chapterSegments] = partition(this.segments, (segment) => segment.actionType !== ActionType.Chapter) + const normalSegment = this.getSmallestSegment(timeInSeconds, normalSegments); + const chapterSegment = this.getSmallestSegment(timeInSeconds, chapterSegments); - for (const seg of this.segments) {// - const segmentLength = seg.segment[1] - seg.segment[0]; - const minSize = this.getMinimumSize(seg.showLarger); - - const startTime = segmentLength !== 0 ? seg.segment[0] : Math.floor(seg.segment[0]); - const endTime = segmentLength > minSize ? seg.segment[1] : Math.ceil(seg.segment[0] + minSize); - if (startTime <= timeInSeconds && endTime >= timeInSeconds) { - if (segmentLength < currentSegmentLength) { - currentSegmentLength = segmentLength; - segment = seg; - } - } - } - - if (segment === null && this.tooltipContainer.classList.contains(TOOLTIP_VISIBLE_CLASS)) { - this.tooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); - } else if (segment !== null) { - this.tooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS); - - const name = segment.description || utils.shortCategoryName(segment.category); - if (segment.unsubmitted) { - this.categoryTooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + name; + if (normalSegment === null && chapterSegment === null) { + this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); + } else { + this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS); + if (noYoutubeChapters && normalSegment !== null && chapterSegment !== null) { + this.categoryTooltipContainer.classList.add("sponsorTwoTooltips"); } else { - this.categoryTooltip.textContent = name; + this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips"); } - // Use the class if the timestamp text uses it to prevent overlapping + this.setTooltipTitle(normalSegment, this.categoryTooltip); + this.setTooltipTitle(chapterSegment, this.chapterTooltip); + + // Used to prevent overlapping this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters); + this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters); } }); @@ -154,6 +149,21 @@ class PreviewBar { }); } + private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void { + if (segment) { + const name = segment.description || utils.shortCategoryName(segment.category); + if (segment.unsubmitted) { + tooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + name; + } else { + tooltip.textContent = name; + } + + tooltip.style.removeProperty("display"); + } else { + tooltip.style.display = "none"; + } + } + createElement(parent: HTMLElement): void { this.parent = parent; @@ -495,9 +505,9 @@ class PreviewBar { this.categoryTooltip = undefined; } - if (this.tooltipContainer) { - this.tooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); - this.tooltipContainer = undefined; + if (this.categoryTooltipContainer) { + this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); + this.categoryTooltipContainer = undefined; } } @@ -524,6 +534,27 @@ class PreviewBar { getMinimumSize(showLarger = false): number { return this.videoDuration * (showLarger ? 0.006 : 0.003); } + + private getSmallestSegment(timeInSeconds: number, segments: PreviewBarSegment[]): PreviewBarSegment | null { + let segment: PreviewBarSegment | null = null; + let currentSegmentLength = Infinity; + + for (const seg of segments) { // + const segmentLength = seg.segment[1] - seg.segment[0]; + const minSize = this.getMinimumSize(seg.showLarger); + + const startTime = segmentLength !== 0 ? seg.segment[0] : Math.floor(seg.segment[0]); + const endTime = segmentLength > minSize ? seg.segment[1] : Math.ceil(seg.segment[0] + minSize); + if (startTime <= timeInSeconds && endTime >= timeInSeconds) { + if (segmentLength < currentSegmentLength) { + currentSegmentLength = segmentLength; + segment = seg; + } + } + } + + return segment; + } } export default PreviewBar; diff --git a/src/utils/arrayUtils.ts b/src/utils/arrayUtils.ts new file mode 100644 index 00000000..b19b376d --- /dev/null +++ b/src/utils/arrayUtils.ts @@ -0,0 +1,6 @@ +export function partition(array: T[], filter: (element: T) => boolean): [T[], T[]] { + const pass = [], fail = []; + array.forEach((element) => (filter(element) ? pass : fail).push(element)); + + return [pass, fail]; +} \ No newline at end of file From 4a19fececf4be6cad6b7b5c3b4c8a9faeb0aa850 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 26 Dec 2021 00:17:49 -0500 Subject: [PATCH 034/101] Always set segment source --- src/content.ts | 8 ++++++-- src/types.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/content.ts b/src/content.ts index e2057499..c0045fb7 100644 --- a/src/content.ts +++ b/src/content.ts @@ -708,7 +708,11 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) { if (response?.ok) { const recievedSegments: SponsorTime[] = JSON.parse(response.responseText) ?.filter((video) => video.videoID === id) - ?.map((video) => video.segments)[0]; + ?.map((video) => video.segments)?.[0] + ?.map((segment) => ({ + ...segment, + source: SponsorSourceType.Server + })); if (!recievedSegments || !recievedSegments.length) { // return if no video found retryFetch(); @@ -1666,7 +1670,7 @@ function vote(type: number, UUID: SegmentUUID, category?: Category, skipNotice?: const sponsorIndex = utils.getSponsorIndexFromUUID(sponsorTimes, UUID); // Don't vote for preview sponsors - if (sponsorIndex == -1 || sponsorTimes[sponsorIndex].source === SponsorSourceType.Local) return; + if (sponsorIndex == -1 || sponsorTimes[sponsorIndex].source !== SponsorSourceType.Server) return; // See if the local time saved count and skip count should be saved if (type === 0 && sponsorSkipped[sponsorIndex] || type === 1 && !sponsorSkipped[sponsorIndex]) { diff --git a/src/types.ts b/src/types.ts index 0c1a7d87..7409e3ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -87,7 +87,7 @@ export interface SponsorTime extends SegmentContainer { description?: string; hidden?: SponsorHideType; - source?: SponsorSourceType; + source: SponsorSourceType; videoDuration?: number; } From 1a6a07744ec92acd2ecc3bbdca11c6a4289d660d Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 26 Dec 2021 01:18:55 -0500 Subject: [PATCH 035/101] Don't break chapter bar when existing chapters are there --- src/js-components/previewBar.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index dc7c7d0b..7d58f861 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -255,7 +255,8 @@ class PreviewBar { const newChapterBar = chapterBar.cloneNode(true) as HTMLElement; newChapterBar.classList.add("sponsorBlockChapterBar"); newChapterBar.style.removeProperty("display"); - const originalSection = newChapterBar.querySelector(".ytp-chapter-hover-container"); + const originalSections = newChapterBar.querySelectorAll(".ytp-chapter-hover-container"); + const originalSection = originalSections[0]; this.customChaptersBar = newChapterBar; this.chaptersBarSegments = segments; @@ -273,7 +274,7 @@ class PreviewBar { // Hide old bar chapterBar.style.display = "none"; - originalSection.remove(); + originalSections.forEach((section) => section.remove()); if (this.container?.parentElement === progressBar) { progressBar.insertBefore(newChapterBar, this.container.nextSibling); } else { From 22e85f715de28dc9ce20745b1cb19af71d216091 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 29 Dec 2021 02:16:49 -0500 Subject: [PATCH 036/101] Add initial code to support drawing when there are existing chapters --- src/js-components/previewBar.ts | 94 +++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 7d58f861..db204556 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -267,7 +267,7 @@ class PreviewBar { const duration = chapter[1] - chapter[0]; const newSection = originalSection.cloneNode(true) as HTMLElement; - this.setupChapterSection(newSection, duration); + this.setupChapterSection(newSection, duration, i !== chaptersToRender.length - 1); newChapterBar.appendChild(newSection); } @@ -361,9 +361,13 @@ class PreviewBar { return result; } - private setupChapterSection(section: HTMLElement, duration: number): void { - section.style.marginRight = "2px"; - section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; + private setupChapterSection(section: HTMLElement, duration: number, addMargin: boolean): void { + if (addMargin) { + section.style.marginRight = "2px"; + section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; + } else { + section.style.width = this.timeToPercentage(duration); + } section.setAttribute("decimal-width", String(this.timeToDecimal(duration))); section.addEventListener("mouseenter", () => { @@ -419,20 +423,22 @@ class PreviewBar { const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width")); for (const className in changes) { - const customChangedElement = section.querySelector(`.${className}`) as HTMLElement; + const selector = `.${className}` + const customChangedElement = section.querySelector(selector) as HTMLElement; if (customChangedElement) { const changedElement = changes[className]; + const changedData = this.findLeftAndScale(selector, changedElement); - const left = parseFloat(changedElement.style.left.replace("px", "")) / progressBar.clientWidth; + const left = (changedData.left) / progressBar.clientWidth; const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)); if (!isNaN(left) && !isNaN(calculatedLeft)) { customChangedElement.style.left = `${calculatedLeft * 100}%`; customChangedElement.style.removeProperty("display"); } - const transformMatch = changedElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); - if (transformMatch) { - const transformScale = parseFloat(transformMatch[1]) + left; + if (changedData.scale) { + const transformScale = (changedData.scale * changedData.scaleWidth + changedData.scalePosition) / progressBar.clientWidth; + customChangedElement.style.transform = `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, (transformScale - cursor) / sectionWidthDecimal - calculatedLeft))}`; @@ -453,6 +459,76 @@ class PreviewBar { } } + private findLeftAndScale(selector: string, element: HTMLElement): + { left: number, leftPosition: number, scale: number, scalePosition: number, scaleWidth: number } { + const section = element.parentElement.parentElement; + let currentWidth = 0; + + // Walk left and find lowest left + let left = 0; + let leftPositionOffset = 0; + let leftSection = null; + let checkSection = section; + // If true, keep walking to find width, but don't set the left + let foundEarly = false; + do { + if (checkSection) { + if (checkSection !== section) { + currentWidth += this.getPartialChapterSectionStyle(checkSection, "width") + + this.getPartialChapterSectionStyle(checkSection, "marginRight"); + } + + const checkElement = checkSection.querySelector(selector) as HTMLElement; + const checkLeft = parseFloat(checkElement.style.left.replace("px", "")); + if (!foundEarly) { + left = checkLeft; + leftPositionOffset = currentWidth; + leftSection = checkSection; + } + + if (checkLeft !== 0) { + foundEarly = true; + } + } + } while ((checkSection = checkSection.previousElementSibling as HTMLElement) !== null); + const leftPosition = currentWidth - leftPositionOffset; + + // Then walk right and find the first with a scale below 1 + let scale = null; + let scalePosition = 0; + let scaleWidth = 0; + checkSection = section; + do { + if (checkSection) { + const checkSectionWidth = this.getPartialChapterSectionStyle(checkSection, "width") + + this.getPartialChapterSectionStyle(checkSection, "marginRight"); + const checkElement = checkSection.querySelector(selector) as HTMLElement; + const transformMatch = checkElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); + if (transformMatch) { + const transformScale = parseFloat(transformMatch[1]); + if (transformScale < 1) { + scale = transformScale; + scaleWidth = checkSectionWidth; + scalePosition = currentWidth; + if (checkSection === leftSection) { + scalePosition += left; + } + + break; + } + } + + currentWidth += checkSectionWidth; + } + } while ((checkSection = checkSection.nextElementSibling as HTMLElement) !== null); + + return { left: left + leftPosition, leftPosition, scale, scalePosition, scaleWidth }; + } + + private getPartialChapterSectionStyle(element: HTMLElement, param: string): number { + return parseInt(element.style[param].match(/\d+/g)?.[0]) || 0; + } + updateChapterText(segments: SponsorTime[], currentTime: number): void { if (!segments) return; From 496ef87a286824843957749d98531a0e41b3f650 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Dec 2021 00:22:13 -0500 Subject: [PATCH 037/101] Reduce issues in rendering over existing chapters by replacing walking method with direct loop --- src/js-components/previewBar.ts | 84 ++++++++++++++------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index db204556..e64f2795 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -459,68 +459,54 @@ class PreviewBar { } } - private findLeftAndScale(selector: string, element: HTMLElement): + private findLeftAndScale(selector: string, currentElement: HTMLElement): { left: number, leftPosition: number, scale: number, scalePosition: number, scaleWidth: number } { - const section = element.parentElement.parentElement; + const sections = currentElement.parentElement.parentElement.parentElement.children; let currentWidth = 0; - // Walk left and find lowest left let left = 0; - let leftPositionOffset = 0; - let leftSection = null; - let checkSection = section; - // If true, keep walking to find width, but don't set the left - let foundEarly = false; - do { - if (checkSection) { - if (checkSection !== section) { - currentWidth += this.getPartialChapterSectionStyle(checkSection, "width") - + this.getPartialChapterSectionStyle(checkSection, "marginRight"); - } + let leftPosition = 0; - const checkElement = checkSection.querySelector(selector) as HTMLElement; - const checkLeft = parseFloat(checkElement.style.left.replace("px", "")); - if (!foundEarly) { - left = checkLeft; - leftPositionOffset = currentWidth; - leftSection = checkSection; - } - - if (checkLeft !== 0) { - foundEarly = true; - } - } - } while ((checkSection = checkSection.previousElementSibling as HTMLElement) !== null); - const leftPosition = currentWidth - leftPositionOffset; - - // Then walk right and find the first with a scale below 1 let scale = null; let scalePosition = 0; let scaleWidth = 0; - checkSection = section; - do { - if (checkSection) { - const checkSectionWidth = this.getPartialChapterSectionStyle(checkSection, "width") - + this.getPartialChapterSectionStyle(checkSection, "marginRight"); - const checkElement = checkSection.querySelector(selector) as HTMLElement; - const transformMatch = checkElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); - if (transformMatch) { - const transformScale = parseFloat(transformMatch[1]); - if (transformScale < 1) { - scale = transformScale; - scaleWidth = checkSectionWidth; - scalePosition = currentWidth; - if (checkSection === leftSection) { - scalePosition += left; - } + for (const sectionElement of sections) { + const section = sectionElement as HTMLElement; + const checkElement = section.querySelector(selector) as HTMLElement; + const currentSectionWidthNoMargin = this.getPartialChapterSectionStyle(section, "width"); + const currentSectionWidth = currentSectionWidthNoMargin + + this.getPartialChapterSectionStyle(section, "marginRight"); + + // First check for left + const checkLeft = parseFloat(checkElement.style.left.replace("px", "")); + if (checkLeft !== 0) { + left = checkLeft; + leftPosition = currentWidth; + } + + // Then check for scale + const transformMatch = checkElement.style.transform.match(/scaleX\(([0-9.]+?)\)/); + if (transformMatch) { + const transformScale = parseFloat(transformMatch[1]); + if (transformScale < 1 && transformScale + checkLeft / currentSectionWidthNoMargin < 0.99999) { + scale = transformScale; + scaleWidth = currentSectionWidth; + scalePosition = currentWidth; + if (checkLeft !== 0) { + scalePosition += left; + } + + if (transformScale > 0) { + // reached the end of this section for sure, since the scale is now between 0 and 1 + // if the scale is always zero, then it will go through all sections but still return 0 break; } } - - currentWidth += checkSectionWidth; } - } while ((checkSection = checkSection.nextElementSibling as HTMLElement) !== null); + + currentWidth += currentSectionWidth; + } return { left: left + leftPosition, leftPosition, scale, scalePosition, scaleWidth }; } From 68c1f780d5948a6ebe693a00904baa8a4a19169f Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Dec 2021 00:35:39 -0500 Subject: [PATCH 038/101] Fix sometimes not rendering chapters when no existing chapters --- src/js-components/previewBar.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index e64f2795..2cf9e618 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -427,7 +427,7 @@ class PreviewBar { const customChangedElement = section.querySelector(selector) as HTMLElement; if (customChangedElement) { const changedElement = changes[className]; - const changedData = this.findLeftAndScale(selector, changedElement); + const changedData = this.findLeftAndScale(selector, changedElement, progressBar); const left = (changedData.left) / progressBar.clientWidth; const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / sectionWidthDecimal)); @@ -459,7 +459,7 @@ class PreviewBar { } } - private findLeftAndScale(selector: string, currentElement: HTMLElement): + private findLeftAndScale(selector: string, currentElement: HTMLElement, progressBar: HTMLElement): { left: number, leftPosition: number, scale: number, scalePosition: number, scaleWidth: number } { const sections = currentElement.parentElement.parentElement.parentElement.children; let currentWidth = 0; @@ -474,10 +474,10 @@ class PreviewBar { for (const sectionElement of sections) { const section = sectionElement as HTMLElement; const checkElement = section.querySelector(selector) as HTMLElement; - const currentSectionWidthNoMargin = this.getPartialChapterSectionStyle(section, "width"); + const currentSectionWidthNoMargin = this.getPartialChapterSectionStyle(section, "width") || progressBar.clientWidth; const currentSectionWidth = currentSectionWidthNoMargin + this.getPartialChapterSectionStyle(section, "marginRight"); - + // First check for left const checkLeft = parseFloat(checkElement.style.left.replace("px", "")); if (checkLeft !== 0) { @@ -512,7 +512,12 @@ class PreviewBar { } private getPartialChapterSectionStyle(element: HTMLElement, param: string): number { - return parseInt(element.style[param].match(/\d+/g)?.[0]) || 0; + const data = element.style[param]; + if (data?.includes("100%")) { + return 0; + } else { + return parseInt(element.style[param].match(/\d+/g)?.[0]) || 0; + } } updateChapterText(segments: SponsorTime[], currentTime: number): void { From c3933a4eee0248f722a6828ef1d7b45e223afba0 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Dec 2021 00:39:03 -0500 Subject: [PATCH 039/101] reduce returned variables --- src/js-components/previewBar.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 2cf9e618..0a99febc 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -437,7 +437,7 @@ class PreviewBar { } if (changedData.scale) { - const transformScale = (changedData.scale * changedData.scaleWidth + changedData.scalePosition) / progressBar.clientWidth; + const transformScale = (changedData.scale) / progressBar.clientWidth; customChangedElement.style.transform = `scaleX(${Math.max(0, Math.min(1 - calculatedLeft, @@ -460,7 +460,7 @@ class PreviewBar { } private findLeftAndScale(selector: string, currentElement: HTMLElement, progressBar: HTMLElement): - { left: number, leftPosition: number, scale: number, scalePosition: number, scaleWidth: number } { + { left: number, scale: number } { const sections = currentElement.parentElement.parentElement.parentElement.children; let currentWidth = 0; @@ -508,7 +508,10 @@ class PreviewBar { currentWidth += currentSectionWidth; } - return { left: left + leftPosition, leftPosition, scale, scalePosition, scaleWidth }; + return { + left: left + leftPosition, + scale: scale * scaleWidth + scalePosition + }; } private getPartialChapterSectionStyle(element: HTMLElement, param: string): number { From 02bc554b0ea0a682c9b2fabe8f1352285f12e347 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Dec 2021 01:05:59 -0500 Subject: [PATCH 040/101] Fix progress not starting at full --- src/js-components/previewBar.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 0a99febc..a98800ba 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -436,7 +436,7 @@ class PreviewBar { customChangedElement.style.removeProperty("display"); } - if (changedData.scale) { + if (changedData.scale !== null) { const transformScale = (changedData.scale) / progressBar.clientWidth; customChangedElement.style.transform = @@ -492,14 +492,15 @@ class PreviewBar { if (transformScale < 1 && transformScale + checkLeft / currentSectionWidthNoMargin < 0.99999) { scale = transformScale; scaleWidth = currentSectionWidth; - scalePosition = currentWidth; - if (checkLeft !== 0) { - scalePosition += left; - } if (transformScale > 0) { // reached the end of this section for sure, since the scale is now between 0 and 1 // if the scale is always zero, then it will go through all sections but still return 0 + + scalePosition = currentWidth; + if (checkLeft !== 0) { + scalePosition += left; + } break; } } @@ -510,7 +511,7 @@ class PreviewBar { return { left: left + leftPosition, - scale: scale * scaleWidth + scalePosition + scale: scale !== null ? scale * scaleWidth + scalePosition : null }; } From 63f6702f8666835b6b6f378fd7ab67d1e8aae98e Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Dec 2021 01:08:54 -0500 Subject: [PATCH 041/101] Fix chapter bar breaking when adding new unsubmitted segments --- src/js-components/previewBar.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index a98800ba..94d8a176 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -366,6 +366,7 @@ class PreviewBar { section.style.marginRight = "2px"; section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; } else { + section.style.marginRight = "0"; section.style.width = this.timeToPercentage(duration); } section.setAttribute("decimal-width", String(this.timeToDecimal(duration))); From 7895b9d2c1c61cc20f932daabe872f00ca336212 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 2 Jan 2022 22:44:15 -0500 Subject: [PATCH 042/101] Fix crash from preview bar not being defined yet --- src/content.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content.ts b/src/content.ts index c0045fb7..6836cb40 100644 --- a/src/content.ts +++ b/src/content.ts @@ -449,7 +449,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?: if (!video) return; if (currentTime === undefined || currentTime === null) currentTime = video.currentTime; - previewBar.updateChapterText(sponsorTimes, currentTime); + previewBar?.updateChapterText(sponsorTimes, currentTime); if (video.paused) return; if (videoMuted && !inMuteSegment(currentTime)) { @@ -630,7 +630,7 @@ function setupVideoListeners() { startSponsorSchedule(); } else { - previewBar.updateChapterText(sponsorTimes, video.currentTime); + previewBar?.updateChapterText(sponsorTimes, video.currentTime); } }); video.addEventListener('ratechange', () => startSponsorSchedule()); From 46983bec24284222f483d64afa65784134b96b97 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 21 Jan 2022 20:18:42 -0500 Subject: [PATCH 043/101] Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters --- config.json.example | 2 +- manifest/manifest.json | 2 +- public/_locales/bg/messages.json | 3 + public/_locales/cs/messages.json | 3 + public/_locales/de/messages.json | 7 +- public/_locales/es/messages.json | 16 +++ public/_locales/et/messages.json | 16 +++ public/_locales/fa/messages.json | 134 +++++++++++++++++++- public/_locales/fi/messages.json | 16 +++ public/_locales/hu/messages.json | 7 +- public/_locales/it/messages.json | 22 +++- public/_locales/nl/messages.json | 16 +++ public/_locales/pt_BR/messages.json | 23 ++++ public/_locales/ru/messages.json | 7 +- public/_locales/sk/messages.json | 13 ++ public/_locales/sv/messages.json | 3 + public/_locales/tr/messages.json | 20 ++- public/_locales/uk/messages.json | 3 + public/_locales/vi/messages.json | 3 + src/components/SkipNoticeComponent.tsx | 10 +- src/components/SponsorTimeEditComponent.tsx | 83 ++++++++---- src/content.ts | 56 +++----- src/js-components/previewBar.ts | 5 +- src/popup.ts | 5 +- src/types.ts | 8 +- src/utils.ts | 43 ++++++- src/utils/categoryUtils.ts | 54 ++++---- 27 files changed, 450 insertions(+), 130 deletions(-) diff --git a/config.json.example b/config.json.example index cbcd6cad..c81273bd 100644 --- a/config.json.example +++ b/config.json.example @@ -13,7 +13,7 @@ "preview": ["skip", "mute"], "filler": ["skip", "mute"], "music_offtopic": ["skip"], - "poi_highlight": ["skip"], + "poi_highlight": ["poi"], "chapter": ["chapter"] }, "wikiLinks": { diff --git a/manifest/manifest.json b/manifest/manifest.json index 912a526a..82e20719 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_fullName__", "short_name": "SponsorBlock", - "version": "4.0.3", + "version": "4.0.5", "default_locale": "en", "description": "__MSG_Description__", "homepage_url": "https://sponsor.ajay.app", diff --git a/public/_locales/bg/messages.json b/public/_locales/bg/messages.json index 2e4f8ec3..26428ad7 100644 --- a/public/_locales/bg/messages.json +++ b/public/_locales/bg/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Следното предложение е по-кратко от опцията за минимална продължителност. Това може да означава, че вече е изпратено и просто е игнорирано поради тази опция. Наистина ли искате да го изпратите?" }, + "liveOrPremiere": { + "message": "Не е разрешено изпращането по време на активен поток на живо или премиера. Моля, изчакайте, докато приключи, след което опреснете страницата и проверете дали сегментите са все още валидни." + }, "showUploadButton": { "message": "Показване на бутона за качване" }, diff --git a/public/_locales/cs/messages.json b/public/_locales/cs/messages.json index 878683e7..8bd1e767 100644 --- a/public/_locales/cs/messages.json +++ b/public/_locales/cs/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Váš příspěvek je kratší než vaše možnost nejkratší doby trvání. To by mohlo znamenat, že někdo segment již odeslal, a je jenom ignorován kvůli této možnosti. Opravdu chcete odeslat váš příspěvek?" }, + "liveOrPremiere": { + "message": "Odesílání na aktivním streamu nebo premiéře. Počkejte prosím, než skončí, poté obnovte stránku a ověřte, zda jsou segmenty stále platné." + }, "showUploadButton": { "message": "Zobrazit tlačítko Nahrát" }, diff --git a/public/_locales/de/messages.json b/public/_locales/de/messages.json index e7bb65d9..9a45ba84 100644 --- a/public/_locales/de/messages.json +++ b/public/_locales/de/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Die folgende Einreichung ist kürzer als deine Mindestdauer. Das könnte bedeuten, dass dieses Videosegment bereits eingereicht wurde und aufgrund dieser Option einfach ignoriert wird. Bist du dir sicher, dass du es übermitteln möchtest?" }, + "liveOrPremiere": { + "message": "Das Einreichen eines aktiven Livestreams oder Premiere ist nicht erlaubt. Bitte warte bis es zu ende ist, lade die Seite neu und überprüfe, dass die Segmente noch immer korrekt sind." + }, "showUploadButton": { "message": "Upload-Knopf anzeigen" }, @@ -544,7 +547,7 @@ "message": "Nur für das Kennzeichnen ganzer Videos. Wird verwendet wenn ein Video ein Produkt, eine Dienstleistung oder einen Ort präsentiert, zu welchem sie freien oder subventionierten Zugriff erhalten haben." }, "category_exclusive_access_pill": { - "message": "Dieses Video präsentiert ein Produkt, eine Dienstleistung oder einen Ort, zu welchem sie freien oder subventionierten Zigriff erhalten haben", + "message": "Dieses Video präsentiert ein Produkt, eine Dienstleistung oder einen Ort, zu welchem sie freien oder subventionierten Zugriff erhalten haben", "description": "Short description for this category" }, "category_interaction": { @@ -638,7 +641,7 @@ "message": "Segmente zulassen, die den Ton ausschalten anstatt zu überspringen" }, "fullVideoSegments": { - "message": "Zeige an, wenn das gesamte Video Werbung ist", + "message": "Zeige ein Icon, wenn ein ganzes Video Werbung ist", "description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo" }, "colorFormatIncorrect": { diff --git a/public/_locales/es/messages.json b/public/_locales/es/messages.json index 3e0ed5b2..d4688c6c 100644 --- a/public/_locales/es/messages.json +++ b/public/_locales/es/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "El siguiente envío es más corto que su opción de duración mínima. Esto podría significar que esto ya se ha enviado y que simplemente se ha ignorado debido a esta opción. ¿Está seguro de que desea enviar?" }, + "liveOrPremiere": { + "message": "No se permite enviar en una transmisión en vivo activa o estreno. Espere hasta que finalice, luego actualice la página y verifique que los segmentos aún sean válidos." + }, "showUploadButton": { "message": "Mostrar botón de subida" }, @@ -537,6 +540,16 @@ "category_selfpromo_description": { "message": "Similar a \"sponsor\", excepto que para la promoción propia o no remunerada. Esto incluye secciones sobre mercancía, donaciones o información sobre con quiénes colaboraron." }, + "category_exclusive_access": { + "message": "Acceso Exclusivo" + }, + "category_exclusive_access_description": { + "message": "Solo para etiquetar videos completos. Utilizado cuando un video exhibe un producto, servicio o ubicación al que han recibido acceso gratuito o subsidiado." + }, + "category_exclusive_access_pill": { + "message": "Este video exhibe un producto, servicio o ubicación al que han recibido acceso gratuito o subsidiado", + "description": "Short description for this category" + }, "category_interaction": { "message": "Recordatorio de interacción (subscribir)" }, @@ -618,6 +631,9 @@ "showOverlay_POI": { "message": "Mostrar en la barra de búsqueda" }, + "showOverlay_full": { + "message": "Mostrar Etiqueta" + }, "autoSkipOnMusicVideos": { "message": "Omitir automáticamente todos los segmentos cuando hay un segmento sin música" }, diff --git a/public/_locales/et/messages.json b/public/_locales/et/messages.json index 54bbe191..3177e65d 100644 --- a/public/_locales/et/messages.json +++ b/public/_locales/et/messages.json @@ -537,6 +537,16 @@ "category_selfpromo_description": { "message": "Sarnaneb \"sponsorile\", ent on mõeldud tasumata või enesepromo jaoks. Selle alla kuuluvad jaotised oma müüdava kauba, annetuste ja koostööpartnerite kohta." }, + "category_exclusive_access": { + "message": "Eksklusiivne ligipääs" + }, + "category_exclusive_access_description": { + "message": "Ainult tervete videote märkimiseks. Kasutatakse, kui video esitleb toodet, teenust või asukohta, millele isik on tasuta või toetusega ligipääsu saanud." + }, + "category_exclusive_access_pill": { + "message": "See video esitleb toodet, teenust või asukohta, millele isik on tasuta või toetusega ligipääsu saanud", + "description": "Short description for this category" + }, "category_interaction": { "message": "Tegutsemise meeldetuletus (kanali tellimine)" }, @@ -618,6 +628,9 @@ "showOverlay_POI": { "message": "Kuva mängija ajaribal" }, + "showOverlay_full": { + "message": "Kuva silt" + }, "autoSkipOnMusicVideos": { "message": "Jäta automaatselt kõik segmendid vahele, kui eksisteerib mitte-muusika segment" }, @@ -745,6 +758,9 @@ "message": "Sain aru", "description": "Used as the button to dismiss a tooltip" }, + "categoryPillTitleText": { + "message": "See terve video on selle kategooriaga sildistatud ning on liiga tihedalt integreeritud, et eraldada saaks" + }, "experiementOptOut": { "message": "Keeldu kõigist tulevikus tehtavatatest eksperimentidest", "description": "This is used in a popup about a new experiment to get a list of unlisted videos to back up since all unlisted videos uploaded before 2017 will be set to private." diff --git a/public/_locales/fa/messages.json b/public/_locales/fa/messages.json index aa875708..5af02d6f 100644 --- a/public/_locales/fa/messages.json +++ b/public/_locales/fa/messages.json @@ -3,11 +3,15 @@ "message": "اسپانسربلاک برای یوتیوب - اسپانسر ها را رد کنید", "description": "Name of the extension." }, + "Description": { + "message": "بخش های اسپانسر شده، درخواست ساب اسکرایب و خیلی چیز های دیگر در ویدیو های یوتیوب را رد کنید. قسمت های اسپانسری ویدیو هایی که میبینید را گزارش کنید تا در وقت دیگران صرفه جویی شود.", + "description": "Description of the extension." + }, "400": { "message": "سرور گفت که این درخواست نامعتبر است" }, "429": { - "message": "شما برای این یک ویدیو تعداد بیش از حدی زمان اسپانسر ثبت کرده‌اید، آیا مطمئن هستید که به این تعداد وجود دارد؟" + "message": "شما برای این ویدیو قسمت های اسپانسری خیلی زیادی ثبت کرده اید، آیا مطمئنید که به این تعداد وجوددارد؟" }, "409": { "message": "این قبلاً ثبت شده است" @@ -127,7 +131,7 @@ "message": "شما دیگران را نجات دادید از " }, "viewLeaderboard": { - "message": "لیست سرنشینان" + "message": "نفرات برتر" }, "recordTimesDescription": { "message": "ثبت" @@ -144,6 +148,9 @@ "setUsername": { "message": "تنظیم نام کاربری" }, + "discordAdvert": { + "message": "به سرور رسمی دیسکورد بپیوندید تا پیشنهادات و بازخورد‌های خود را ارائه دهید!" + }, "hideThis": { "message": "مخفی‌سازی" }, @@ -156,6 +163,9 @@ "hideButtons": { "message": "مخفی‌سازی کلید ها در پخش‌کننده یوتیوب" }, + "showNotice": { + "message": "نمایش مجدد اطلاعیه" + }, "longDescription": { "message": "افزونه اسپانسر بلاک به شما امکان رد کردن بخش‌های تبلیغاتی (اسپانسر شده)، قسمت‌های شروع و پایان ویدیو، درخواست ساب‌اسکرایب و سایر قسمت‌های آزار دهنده یوتیوب را می‌دهد. اسپانسر بلاک یک افزونه مرورگر است که به هر کسی امکان ثبت زمان شروع و پایان بخش های اسپانسر شده و سایر بخش های ویدیو های یوتیوب را می‌دهد. پس از اینکه هر کاربر این اطلاعات را ثبت کرده و گزارش دهد، بقیه کاربرانی که از این افزونه استفاده می‌کنند مستقیماً بخش اسپانسر شده ویدیو را رد خواهند کرد. شما همچنین می‌توانید در ویدیو های نماهنگ (موزیک ویدیو)، قسمت‌های غیر موسیقی ویدیو را رد کنید.", "description": "Full description of the extension on the store pages." @@ -172,6 +182,9 @@ "message": "اگر همچنان این را نمی‌پسندید، گزینه هرگز نمایش نده را انتخاب کنید.", "description": "The second line of the message displayed after the notice was upgraded." }, + "keybindDescription": { + "message": "یک کلید را با تایپ نمودن انتخاب نمایید" + }, "disableSkipping": { "message": "ردکردن فعال است" }, @@ -194,6 +207,20 @@ "mute": { "message": "بی‌صدا" }, + "skip_category": { + "message": "{0} رد شود؟" + }, + "mute_category": { + "message": "{0} بی‌صدا شود؟" + }, + "skipped": { + "message": "{0} رد شد", + "description": "Example: Sponsor Skipped" + }, + "muted": { + "message": "{0} بی‌صدا شد", + "description": "Example: Sponsor Muted" + }, "minLower": { "message": "دقیقه" }, @@ -203,6 +230,12 @@ "createdBy": { "message": "ایجاد شده توسط" }, + "keybindCurrentlySet": { + "message": ". در حال حاضر تنظیم شده است به:" + }, + "supportedSites": { + "message": "وب‌سایت‌های پشتیبانی شده: " + }, "add": { "message": "افزودن" }, @@ -221,6 +254,9 @@ "setOptions": { "message": "تنظیم گزینه‌ها" }, + "confirmNoticeTitle": { + "message": "ثبت بخش" + }, "submit": { "message": "ثبت" }, @@ -242,6 +278,9 @@ "edit": { "message": "ویرایش" }, + "theKey": { + "message": "کلید" + }, "to": { "message": "به", "description": "Used between segments. Example: 1:20 to 1:30" @@ -249,6 +288,12 @@ "category_sponsor": { "message": "اسپانسر" }, + "category_exclusive_access": { + "message": "دسترسی اختصاصی" + }, + "category_filler_short": { + "message": "پر کننده" + }, "category_music_offtopic_short": { "message": "غیر موسیقی" }, @@ -261,12 +306,43 @@ "manualSkip": { "message": "ردکردن دستی" }, + "showOverlay": { + "message": "نمایش در نوار پیشرفت" + }, + "disable": { + "message": "غیرفعال کردن" + }, + "showOverlay_POI": { + "message": "نمایش در نوار پیشرفت" + }, + "showOverlay_full": { + "message": "نمایش نام" + }, + "category": { + "message": "دسته بندی" + }, "bracketNow": { "message": "(اکنون)" }, + "moreCategories": { + "message": "نمایش دسته‌بندی‌ها" + }, "bracketEnd": { "message": "(پایان)" }, + "acceptPermission": { + "message": "تأیید دسترسی" + }, + "incorrectCategory": { + "message": "تغییر دسته بندی" + }, + "guidelines": { + "message": "دستورالعمل‌ها" + }, + "readTheGuidelines": { + "message": "دستورالعمل‌ها را بخوانید!!", + "description": "Show the first time they submit or if they are \"high risk\"" + }, "help": { "message": "راهنما" }, @@ -274,7 +350,61 @@ "message": "فهمیدم", "description": "Used as the button to dismiss a tooltip" }, + "hideForever": { + "message": "مخفی‌سازی برای همیشه" + }, + "warningChatInfo": { + "message": "شما یک اخطار دریافت کرده اید و موقتاً نمیتوانید بخشی را ثبت کنید. این یعنی ما متوجه شدیم که شما یک سری اشتباهات متداول داشتید اما قصد و نیت خرابکاری نداشتید، لطفاً فقط تایید کنید که شما از قوانین آگاهی دارید و سپس ما این اخطار را حذف خواهیم کرد. شما میتوانید به کمک discord.gg/SponsorBlock یا matrix.to/#/#sponsor:ajay.app عضو گروه ما بشوید." + }, + "voteRejectedWarning": { + "message": "رای شما رد شد چون شما یک اخطار دارید. برای حل این مشکل یک کلیک کنید تا گروه چت ما باز بشود، یا اینکه بعداً هر موقع وقت داشتید این کار را انجام بدهید.", + "description": "This is an integrated chat panel that will appearing allowing them to talk to the Discord/Matrix chat without leaving their browser." + }, "Donate": { "message": "کمک مالی" + }, + "hideDonationLink": { + "message": "پنهان کردن لینک کمک مالی" + }, + "helpPageThanksForInstalling": { + "message": "ازینکه افزونه SponserBlock را نصب کردید سپاسگزاریم." + }, + "helpPageReviewOptions": { + "message": "لطفاً تنظیمات زیر را بررسی کنید" + }, + "helpPageHowSkippingWorks": { + "message": "چگونه ردکردن کار می‌کند" + }, + "Submitting": { + "message": "ثبت نمودن" + }, + "Editing": { + "message": "ویرایش" + }, + "helpPageCopyOfDatabase": { + "message": "آیا می‌توان یک کپی از پایگاه داده را دریافت کرد؟ اگر شما یک روز ناپدید شدید چه می‌شود؟" + }, + "helpPageCopyOfDatabase1": { + "message": "پایگاه داده به‌صورت عمومی در دسترس است در" + }, + "helpPageCopyOfDatabase2": { + "message": "سورس کد به‌صورت رایگان ارائه شده است. بنابراین، اگر اتفاقی برای من بیفتد، ارسالات شما از بین نمی‌روند." + }, + "helpPageSourceCode": { + "message": "از کجا سورس کد را بگیرم؟" + }, + "Credits": { + "message": "سازندگان" + }, + "LearnMore": { + "message": "بیشتر بدانید" + }, + "dayAbbreviation": { + "message": "روز", + "description": "100d" + }, + "hourAbbreviation": { + "message": "ساعت", + "description": "100h" } } diff --git a/public/_locales/fi/messages.json b/public/_locales/fi/messages.json index 0ceb236e..eaa7a59c 100644 --- a/public/_locales/fi/messages.json +++ b/public/_locales/fi/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Seuraava lähetys on lyhyempi kuin vähimmäiskeston asetuksesi. Tämä voi tarkoittaa sitä, että tämä on jo lähetetty, ja sitä ei vain oteta huomioon tämän asetuksen vuoksi. Oletko varma, että haluat lähettää?" }, + "liveOrPremiere": { + "message": "Aktiivisen livestriimin tai ensiesityksen aikana lähettäminen ei ole sallittua. Odota kunnes se loppuu, sitten päivitä sivu ja varmista, että segmentit ovat yhä oikein." + }, "showUploadButton": { "message": "Näytä lähetä-painike" }, @@ -537,6 +540,16 @@ "category_selfpromo_description": { "message": "Samankaltainen \"sponsorin\" kanssa, mutta maksamattomalle tai itsensä mainostukselle. Tämä sisältää osioita kauppatavarasta, lahjoituksista tai tietoa siitä, kenen kanssa he ovat tehneet yhteistyötä." }, + "category_exclusive_access": { + "message": "Yksinoikeudellinen ensikatsaus" + }, + "category_exclusive_access_description": { + "message": "Vain kokonaisten videoiden merkitsemiseen. Käytetään kun videossa esitellään tuote, palvelu tai sijainti, johon he ovat saaneet ilmaisen tai tuetun käyttöoikeuden." + }, + "category_exclusive_access_pill": { + "message": "Tämä video esittelee tuotteen, palvelun tai sijainnin, johon he ovat saaneet ilmaisen tai tuetun käyttöoikeuden", + "description": "Short description for this category" + }, "category_interaction": { "message": "Vuorovaikutusmuistutus (tilaaminen)" }, @@ -618,6 +631,9 @@ "showOverlay_POI": { "message": "Näytä liukusäätimessä" }, + "showOverlay_full": { + "message": "Näytä merkki" + }, "autoSkipOnMusicVideos": { "message": "Ohita kaikki segmentit automaattisesti, kun videossa on \"Musiikiton\" segmentti" }, diff --git a/public/_locales/hu/messages.json b/public/_locales/hu/messages.json index 5583086c..2256041f 100644 --- a/public/_locales/hu/messages.json +++ b/public/_locales/hu/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "A következő szegmens rövidebb, mint az általad beállított minimális időtartam. Ez azt jelentheti, hogy már beküldhették, csak emiatt az opció miatt nálad nem jelenik meg. Biztosan beküldöd?" }, + "liveOrPremiere": { + "message": "Aktív élő közvetítés vagy premier közben nem lehet szegmenseket beküldeni. Kérjük várd meg a végét, majd frissítsd az oldalt és ellenőrizd, hogy a szegmensek nem csúsztak-e el." + }, "showUploadButton": { "message": "Feltöltés gomb megjelenítése" }, @@ -541,10 +544,10 @@ "message": "Exkluzív hozzáférés" }, "category_exclusive_access_description": { - "message": "Csak teljes videók megjelölésére. Akkor használt, amikor egy videó egy olyan terméket, szolgáltatást vagy helyszínt mutat be, amihez ingyen vagy kedvezményes hozzáférést kaptak." + "message": "Csak teljes videók megjelölésére. Akkor használt, amikor egy videó egy olyan terméket, szolgáltatást vagy helyszínt mutat be, amihez ingyenes vagy kedvezményes hozzáférést kaptak." }, "category_exclusive_access_pill": { - "message": "Ez a videó olyan terméket, szolgáltatást vagy helyszínt mutat be, amihez ingyen vagy kedvezményes hozzáférést kaptak", + "message": "Ez a videó olyan terméket, szolgáltatást vagy helyszínt mutat be, amihez ingyenes vagy kedvezményes hozzáférést kaptak", "description": "Short description for this category" }, "category_interaction": { diff --git a/public/_locales/it/messages.json b/public/_locales/it/messages.json index c6b49709..a0cf744d 100644 --- a/public/_locales/it/messages.json +++ b/public/_locales/it/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Il seguente contributo è più breve della tua opzione di durata minima. Ciò potrebbe significare che questo è già stato inviato e viene semplicemente ignorato a causa di questa opzione. Sei sicuro di voler inviare?" }, + "liveOrPremiere": { + "message": "Inviare segmenti per una live o premiere non è consentito. Per favore aspetta che finisca, poi ricarica la pagina e verifica che i segmenti siano ancora validi." + }, "showUploadButton": { "message": "Mostra Pulsante di Caricamento" }, @@ -537,6 +540,16 @@ "category_selfpromo_description": { "message": "Simile alle \"sponsorizzazioni\" tranne che per promozioni non pagate o autopromozioni. Ciò include sezioni riguardanti vendita di merce, donazioni o informazioni in merito a collaboratori." }, + "category_exclusive_access": { + "message": "Accesso Esclusivo" + }, + "category_exclusive_access_description": { + "message": "Solo per etichettare interi video. Usato quando un video mostra un prodotto, un servizio o un posto che hanno ricevuto gratuitamente o a cui hanno avuto un accesso sovvenzionato." + }, + "category_exclusive_access_pill": { + "message": "Questo video mostra un prodotto, un servizio o un posto che hanno ricevuto gratuitamente o a cui hanno avuto un accesso sovvenzionato", + "description": "Short description for this category" + }, "category_interaction": { "message": "Promemoria di Interazione (Sottoscrizione)" }, @@ -568,10 +581,10 @@ "message": "Riepilogo rapido degli episodi precedenti, o un'anteprima di ciò che sta arrivando più tardi nel video attuale. Inteso per clip, non per riassunti a voce." }, "category_filler": { - "message": "Tangente di Riempimento" + "message": "Tangente riempitiva" }, "category_filler_description": { - "message": "Le scene tangenziali aggiunte solo per riempire o per umorismo che non sono richieste per comprendere il contenuto principale del video. Questo non dovrebbe includere segmenti che forniscono contesto o dettagli di sfondo." + "message": "Le scene riempitive sono aggiunte solo per riempire o per umorismo che non sono richieste per comprendere il contenuto principale del video. Questo non dovrebbe includere segmenti che forniscono contesto o dettagli di sfondo." }, "category_filler_short": { "message": "Riempimento" @@ -618,6 +631,9 @@ "showOverlay_POI": { "message": "Mostra Nella Barra di Ricerca" }, + "showOverlay_full": { + "message": "Mostra Etichetta" + }, "autoSkipOnMusicVideos": { "message": "Salta automaticamente tutti i segmenti quando c'è un segmento non musicale" }, @@ -832,7 +848,7 @@ "message": "Scopri di Più" }, "CopyDownvoteButtonInfo": { - "message": "Vota negativamente e crea una copia locale per reinviare" + "message": "Vota negativamente e crea una copia locale da reinviare" }, "OpenCategoryWikiPage": { "message": "Apri la pagina della wiki di questa categoria." diff --git a/public/_locales/nl/messages.json b/public/_locales/nl/messages.json index f824c98c..e9590974 100644 --- a/public/_locales/nl/messages.json +++ b/public/_locales/nl/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "De volgende inzending is korter dan uw \"minimale tijdsduur\"-instelling. Dit kan betekenen dat dit al is ingediend en genegeerd wordt door deze optie. Weet u zeker dat u dit wilt indienen?" }, + "liveOrPremiere": { + "message": "Indienen op een actieve livesteam of première is niet toegestaan. Wacht tot hij klaar is, vernieuw dan de pagina en controleer of de segmenten nog geldig zijn." + }, "showUploadButton": { "message": "Uploaden-knop weergeven" }, @@ -537,6 +540,16 @@ "category_selfpromo_description": { "message": "Vergelijkbaar met \"sponsor\", behalve voor onbetaalde of zelfpromotie. Dit is inclusief secties over koopwaar, donaties of informatie over met wie ze hebben samengewerkt." }, + "category_exclusive_access": { + "message": "Exclusieve toegang" + }, + "category_exclusive_access_description": { + "message": "Alleen voor het labelen van volledige video's. Wordt gebruikt wanneer een video een product, dienst of locatie laat zien waartoe men gratis of gesubsidieerd toegang heeft gekregen." + }, + "category_exclusive_access_pill": { + "message": "Deze video toont een product, dienst of locatie waartoe men gratis of gesubsidieerd toegang heeft gekregen", + "description": "Short description for this category" + }, "category_interaction": { "message": "Interactieherinnering (abonneren)" }, @@ -618,6 +631,9 @@ "showOverlay_POI": { "message": "Weergeven in tijdbalk" }, + "showOverlay_full": { + "message": "Label weergeven" + }, "autoSkipOnMusicVideos": { "message": "Automatisch alle segmenten overslaan wanneer er een niet-muziek-segment is" }, diff --git a/public/_locales/pt_BR/messages.json b/public/_locales/pt_BR/messages.json index 4b7c5927..4f62af26 100644 --- a/public/_locales/pt_BR/messages.json +++ b/public/_locales/pt_BR/messages.json @@ -302,6 +302,10 @@ "mute": { "message": "Silenciar" }, + "full": { + "message": "Vídeo completo", + "description": "Used for the name of the option to label an entire video as sponsor or self promotion." + }, "skip_category": { "message": "Pular {0}?" }, @@ -533,6 +537,9 @@ "category_selfpromo_description": { "message": "Similar a \"patrocinador\", mas para auto promoções e segmentos não-pagos. Isso inclui seções sobre vendas, doações ou informações sobre com quem colaboraram." }, + "category_exclusive_access": { + "message": "Acesso Exclusivo" + }, "category_interaction": { "message": "Lembrete de interação (inscrever-se)" }, @@ -614,12 +621,19 @@ "showOverlay_POI": { "message": "Mostrar na barra de progresso" }, + "showOverlay_full": { + "message": "Mostrar Rótulo" + }, "autoSkipOnMusicVideos": { "message": "Pular automaticamente todos os segmentos quando há um segmento que não é música" }, "muteSegments": { "message": "Permitir segmentos que silenciem o áudio ao invés de pular" }, + "fullVideoSegments": { + "message": "Mostrar um ícone quando um vídeo é inteiramente um anúncio", + "description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo" + }, "colorFormatIncorrect": { "message": "Sua cor está formatada incorretamente. Deve ser um código hexadecimal de 3 ou 6 dígitos com uma cerquilha (hashtag) no início." }, @@ -737,6 +751,12 @@ "message": "Entendi", "description": "Used as the button to dismiss a tooltip" }, + "fullVideoTooltipWarning": { + "message": "Este segmento é grande. Se o vídeo inteiro for sobre um tópico, altere de \"Pular\" para \"Vídeo completo\". Consulte as diretrizes para obter mais informações." + }, + "categoryPillTitleText": { + "message": "Este vídeo inteiro está rotulado como esta categoria e está muito integrado para poder ser separado" + }, "experiementOptOut": { "message": "Optar por sair de todos os experimentos futuros", "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." @@ -835,6 +855,9 @@ "SponsorTimeEditScrollNewFeature": { "message": "Use a roda do mouse enquanto mantêm o cursor sobre a caixa de edição para ajustar o tempo rapidamente. Combinações das teclas ctrl e shift podem ser usadas para refinar as mudanças." }, + "categoryPillNewFeature": { + "message": "Novo! Veja quando um vídeo é inteiramente patrocinado ou de autopromoção" + }, "dayAbbreviation": { "message": "d", "description": "100d" diff --git a/public/_locales/ru/messages.json b/public/_locales/ru/messages.json index f85aa8f4..792b6648 100644 --- a/public/_locales/ru/messages.json +++ b/public/_locales/ru/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Следующий диапазон времени короче, чем Ваша настройка минимальной длительности. Это может означать, что он уже был отправлен, и просто игнорируется из-за этой настройки. Вы действительно хотите отправить?" }, + "liveOrPremiere": { + "message": "Отправка сегментов на стримах или премьерах не допускается. Пожалуйста, дождитесь окончания видео, затем обновите страницу и убедитесь, что сегменты всё ещё верные." + }, "showUploadButton": { "message": "Показывать кнопку отправки" }, @@ -541,7 +544,7 @@ "message": "Эксклюзивный доступ" }, "category_exclusive_access_description": { - "message": "Категория для всего видео. Используется когда видео демонстрирует продукт, сервис или местоположение, к которому автор получил бесплатный или проспонсированный доступ." + "message": "Категория для всего видео. Используется, когда видео демонстрирует продукт, сервис или местоположение, к которому автор получил бесплатный или проспонсированный доступ." }, "category_exclusive_access_pill": { "message": "Это видео демонстрирует продукт, сервис или местоположение, к которому автор получил бесплатный или проспонсированный доступ", @@ -629,7 +632,7 @@ "message": "Показывать в полосе прокрутки" }, "showOverlay_full": { - "message": "Отображать название" + "message": "Показывать категорию" }, "autoSkipOnMusicVideos": { "message": "Пропускать все сегменты автоматически при наличии сегмента без музыки" diff --git a/public/_locales/sk/messages.json b/public/_locales/sk/messages.json index 57423a01..bb194c72 100644 --- a/public/_locales/sk/messages.json +++ b/public/_locales/sk/messages.json @@ -537,6 +537,16 @@ "category_selfpromo_description": { "message": "Podobné ako sponzor, okrem neplatenej alebo vlastnej propagácie. Patria sem sekcie týkajúce sa merchu, donatov alebo informácií o tom, s kým spolupracovali." }, + "category_exclusive_access": { + "message": "Exkluzívny Prístup" + }, + "category_exclusive_access_description": { + "message": "Iba pre označovanie celých videí. Používa sa, keď video predstavuje produkt, službu alebo miesto, ku ktorým získali bezplatný alebo dotovaný prístup." + }, + "category_exclusive_access_pill": { + "message": "Toto video predstavuje produkt, službu alebo miesto, ku ktorým získali bezplatný alebo dotovaný prístup", + "description": "Short description for this category" + }, "category_interaction": { "message": "Pripomienka interakcie (Prihlásiť sa na odber)" }, @@ -618,6 +628,9 @@ "showOverlay_POI": { "message": "Zobraziť v časovej lište" }, + "showOverlay_full": { + "message": "Ukázať Označenie" + }, "autoSkipOnMusicVideos": { "message": "Automaticky preskočiť všetky segmenty ak neexistuje segment bez hudby" }, diff --git a/public/_locales/sv/messages.json b/public/_locales/sv/messages.json index cb7f622b..c0181ab5 100644 --- a/public/_locales/sv/messages.json +++ b/public/_locales/sv/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Följande rapport är kortare än ditt minstavärde i inställningarna. Det skulle kunna betyda att det redan är rapporterat och bara ignorerat på grund av denna inställning. Är du säker på att du vill rapportera?" }, + "liveOrPremiere": { + "message": "Att skicka in på en aktiv liveström eller premiär är inte tillåtet. Vänta tills den är färdig, uppdatera sedan sidan och kontrollera att segmenten fortfarande är giltiga." + }, "showUploadButton": { "message": "Visa uppladdningsknapp" }, diff --git a/public/_locales/tr/messages.json b/public/_locales/tr/messages.json index 32a0c829..2b8d5156 100644 --- a/public/_locales/tr/messages.json +++ b/public/_locales/tr/messages.json @@ -71,7 +71,7 @@ "message": "Sponsor sürelerini yollarken bir sorun oluştur, lütfen tekrar deneyin." }, "sponsorFound": { - "message": "Bu videonun kısımları veri tabanımızda mevut" + "message": "Bu videonun kısımları veri tabanımızda mevcut" }, "sponsor404": { "message": "Kısım bulunamadı" @@ -440,6 +440,9 @@ "shortCheck": { "message": "Sıradaki öneri belirlediğiniz minimum süre ayarından daha kısa. Bu zaten yollandığı ve bu ayardan dolayı yok sayıldığı anlamına gelebilir. Göndermek istediğinizden emin misiniz?" }, + "liveOrPremiere": { + "message": "Bir canlı yayın veya ön gösterim sırasında kısım gönderilemez. Yayının bitmesini bekleyin, sonra sayfayı tazeleyip kısımların geçerli olduğunu kontrol edin." + }, "showUploadButton": { "message": "Karşıya Yükleme Butonunu Göster" }, @@ -537,6 +540,16 @@ "category_selfpromo_description": { "message": "\"Sponsor\" seçeneğinden farkı para karşılığı olmaması veya kendi reklamını yapmasıdır. Buna kendi markalı ürünlerini satmak, bağış toplamak ve videoda işbirliği yaptığı kimselerden bahsetmek dahildir." }, + "category_exclusive_access": { + "message": "Özel Erişim" + }, + "category_exclusive_access_description": { + "message": "Yalnızca bütün videoyu etiketlemek için kullanın. Video; ücretli veya ücretsiz elde edilen bir ürünün, hizmetin veya bir yerin reklamını yapıyorsa kullanılır." + }, + "category_exclusive_access_pill": { + "message": "Bu video; yayıncıya özel ücretle veya ücretsiz sunulan bir ürünün, hizmetin veya bir yerin reklamını yapıyor", + "description": "Short description for this category" + }, "category_interaction": { "message": "Etkileşim Hatırlatıcısı (Abonelik)" }, @@ -618,6 +631,9 @@ "showOverlay_POI": { "message": "Arama Çubuğunda Göster" }, + "showOverlay_full": { + "message": "Etiketi Göster" + }, "autoSkipOnMusicVideos": { "message": "Müzik olmayan kısım varsa tüm kısımları otomatik atla" }, @@ -850,7 +866,7 @@ "message": "Zaman aralığını hızlı bir şekilde ayarlamak için düzenleme kutusunun üzerinde fare tekerini kullanın. Değişikliklere ince ayar yapmak için ctrl veya shift tuşunun kombinasyonları kullanılabilir." }, "categoryPillNewFeature": { - "message": "Yeni! Videonun bütünü sponsor veya kendi reklamıysa öngörün" + "message": "Yeni! Videonun bütünü sponsor veya kendi reklamıysa bu uyarıyı görün" }, "dayAbbreviation": { "message": "d", diff --git a/public/_locales/uk/messages.json b/public/_locales/uk/messages.json index 831ad003..ef11899d 100644 --- a/public/_locales/uk/messages.json +++ b/public/_locales/uk/messages.json @@ -440,6 +440,9 @@ "shortCheck": { "message": "Наступний діапазон часу коротше, ніж Ваше налаштування мінімальної тривалості. Це може означати, що він вже був надісланий, і просто ігнорується через це налаштування. Ви дійсно хочете надіслати?" }, + "liveOrPremiere": { + "message": "Надсилання під час прямого ефіру чи прем'єри не дозволено. Будь ласка, зачекайте до завершення, потім оновіть сторінку і переконайтеся, що сегменти все ще дійсні." + }, "showUploadButton": { "message": "Показувати кнопку надсилання" }, diff --git a/public/_locales/vi/messages.json b/public/_locales/vi/messages.json index 3fdf5304..c053c1a0 100644 --- a/public/_locales/vi/messages.json +++ b/public/_locales/vi/messages.json @@ -537,6 +537,9 @@ "category_selfpromo_description": { "message": "Tương tự như 'nhà tài trợ' ngoại trừ việc quảng cáo không được trả tiền hay tự quảng cáo. Điều này bao gồm các phần hàng hóa, đóng góp, hoặc thông tin về người mà họ hợp tác cùng." }, + "category_exclusive_access": { + "message": "Truy cập riêng" + }, "category_interaction": { "message": "Nhắc tương tác (Đăng ký)" }, diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 49dac9ab..588405fe 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -1,13 +1,13 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config" -import { Category, ContentContainer, CategoryActionType, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types"; +import { Category, ContentContainer, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types"; import NoticeComponent from "./NoticeComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; import Utils from "../utils"; const utils = new Utils(); -import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils"; +import { getSkippingText } from "../utils/categoryUtils"; import ThumbsUpSvg from "../svg-icons/thumbs_up_svg"; import ThumbsDownSvg from "../svg-icons/thumbs_down_svg"; @@ -326,7 +326,7 @@ class SkipNoticeComponent extends React.Component 1 - || getCategoryActionType(this.segments[0].category) !== CategoryActionType.POI + || this.segments[0].actionType !== ActionType.Poi || this.props.unskipTime)) { const style: React.CSSProperties = { @@ -547,7 +547,7 @@ class SkipNoticeComponent extends React.Component getCategoryActionType(cat as Category) === CategoryActionType.Skippable))) as Category[]; + const categories = (CompileConfig.categoryList.filter((cat => CompileConfig.categorySupport[cat].includes(ActionType.Skip)))) as Category[]; for (const category of categories) { elements.push(