From 233da8c449016a887b3b3a348f96f736a79d151c Mon Sep 17 00:00:00 2001 From: gosha305 Date: Sun, 14 Sep 2025 14:24:55 +0200 Subject: [PATCH 01/16] Added better UI for nested chapters --- public/popup.css | 25 +- src/popup/SegmentListComponent.tsx | 374 ++++++++++++++++++----------- 2 files changed, 254 insertions(+), 145 deletions(-) diff --git a/public/popup.css b/public/popup.css index aa3fd10855..c82f69a4f2 100644 --- a/public/popup.css +++ b/public/popup.css @@ -197,9 +197,7 @@ *
wrapper around each segment */ .votingButtons { - font-family: Arial, Helvetica, sans-serif; border-radius: 8px; - margin: 4px 16px; } .votingButtons[open] { padding-bottom: 5px; @@ -208,6 +206,29 @@ background-color: var(--sb-grey-bg-color); } +/* + * Nested chapters + */ +.innerChapterList { + border-radius: 8px; +} + +.innerChapterList > summary { + font-weight: bold; + padding: 4px; + cursor: pointer; +} + +.segmentWrapper:has(> .innerChapterList > summary:hover) { + background-color: var(--sb-grey-bg-color); +} + +.segmentWrapper{ + font-family: Arial, Helvetica, sans-serif; + border-radius: 8px; + margin: 4px 16px; +} + /* * Individual segments summaries (clickable ) */ diff --git a/src/popup/SegmentListComponent.tsx b/src/popup/SegmentListComponent.tsx index f415cf25b3..a2acf0073c 100644 --- a/src/popup/SegmentListComponent.tsx +++ b/src/popup/SegmentListComponent.tsx @@ -27,6 +27,10 @@ enum SegmentListTab { Chapter } +interface segmentWithNesting extends SponsorTime { + innerChapters?: (segmentWithNesting|SponsorTime)[]; +} + export const SegmentListComponent = (props: SegmentListComponentProps) => { const [tab, setTab] = React.useState(SegmentListTab.Segments); const [isVip, setIsVip] = React.useState(Config.config?.isVip ?? false); @@ -53,6 +57,36 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => { } }; + const segmentsWithNesting: segmentWithNesting[] = [] + let nbTrailingNonChapters = 0 + function nestChapters(segments: segmentWithNesting[], seg: SponsorTime, topLevel?:boolean){ + if (seg.actionType === ActionType.Chapter + && segments.length) + { + // trailing non-chapters can only exist at top level + const lastElement = segments[segments.length - (topLevel ? nbTrailingNonChapters + 1 : 1)] + + if (lastElement.actionType === ActionType.Chapter + && lastElement.segment[0] <= seg.segment[0] + && lastElement.segment[1] >= seg.segment[1]) { + if (lastElement.innerChapters){ + nestChapters(lastElement.innerChapters, seg); + } else { + lastElement.innerChapters = [seg]; + } + } + else { + if (topLevel){nbTrailingNonChapters = 0} + segments.push(seg); + } + } else { + if (seg.actionType !== ActionType.Chapter){nbTrailingNonChapters++} + + segments.push(seg); + } + } + props.segments.forEach((seg) => {nestChapters(segmentsWithNesting, {...seg}, true)}) + return (
s.actionType === ActionType.Chapter) ? "" : "hidden"}> @@ -73,14 +107,14 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => { sendMessage: props.sendMessage })}> { - props.segments.map((segment) => ( + segmentsWithNesting.map((segment) => ( { ); }; -function SegmentListItem({ segment, videoID, currentTime, isVip, startingLooped, tabFilter, sendMessage }: { - segment: SponsorTime; +function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter, tabFilter, sendMessage }: { + segment: segmentWithNesting; videoID: VideoID; currentTime: number; isVip: boolean; - startingLooped: boolean; + loopedChapter: SegmentUUID; tabFilter: (segment: SponsorTime) => boolean; sendMessage: (request: Message) => Promise; }) { const [voteMessage, setVoteMessage] = React.useState(null); const [hidden, setHidden] = React.useState(segment.hidden || SponsorHideType.Visible); - const [isLooped, setIsLooped] = React.useState(startingLooped); + const [isLooped, setIsLooped] = React.useState(loopedChapter === segment.UUID); let extraInfo = ""; if (segment.hidden === SponsorHideType.Downvoted) { @@ -124,165 +158,219 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, startingLooped, } return ( -
skipSegment({ - segment, - sendMessage - })} - onMouseEnter={() => { - selectSegment({ +
+ { +
skipSegment({ segment, sendMessage - }); - }} - className={"votingButtons " + (!tabFilter(segment) ? "hidden" : "")}> - = segment.segment[0] ? ( - currentTime < segment.segment[1] ? "segmentActive" : "segmentPassed" - ) : "" - )}> -
- { - segment.actionType !== ActionType.Chapter && - - } - {(segment.description || shortCategoryName(segment.category)) + extraInfo} -
- -
- { - segment.actionType === ActionType.Full ? chrome.i18n.getMessage("full") : - (getFormattedTime(segment.segment[0], true) + - (segment.actionType !== ActionType.Poi - ? " " + chrome.i18n.getMessage("to") + " " + getFormattedTime(segment.segment[1], true) - : "")) - } -
-
- -
- { - vote({ - type: 1, - UUID: segment.UUID, - setVoteMessage: setVoteMessage, - sendMessage - }); - }}/> - { - vote({ - type: 0, - UUID: segment.UUID, - setVoteMessage: setVoteMessage, + })} + onMouseEnter={() => { + selectSegment({ + segment, sendMessage }); - }}/> - { - const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.3); + }} + className={"votingButtons"} + > + = segment.segment[0] ? ( + currentTime < segment.segment[1] ? "segmentActive" : "segmentPassed" + ) : "" + )}> +
+ { + segment.actionType !== ActionType.Chapter && + + } + {(segment.description || shortCategoryName(segment.category)) + extraInfo} +
+ +
+ { + segment.actionType === ActionType.Full ? chrome.i18n.getMessage("full") : + (getFormattedTime(segment.segment[0], true) + + (segment.actionType !== ActionType.Poi + ? " " + chrome.i18n.getMessage("to") + " " + getFormattedTime(segment.segment[1], true) + : "")) + } +
+
- if (segment.UUID.length > 60) { - copyToClipboardPopup(segment.UUID, sendMessage); - } else { - const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", { +
+ { + vote({ + type: 1, UUID: segment.UUID, - videoID: videoID + setVoteMessage: setVoteMessage, + sendMessage }); - - if (segmentIDData.ok && segmentIDData.responseText) { - copyToClipboardPopup(segmentIDData.responseText, sendMessage); - } - } - - stopAnimation(); - }}/> - { - segment.actionType === ActionType.Chapter && + }}/> { - if (isLooped) { - loopChapter({ - segment: null, - element: e.currentTarget, - sendMessage - }); - } else { - loopChapter({ - segment, - element: e.currentTarget, - sendMessage - }); - } - - setIsLooped(!isLooped); + title="Downvote" + src={segment.locked && isVip ? chrome.runtime.getURL("icons/thumbs_down_locked.svg") : chrome.runtime.getURL("icons/thumbs_down.svg")} + onClick={() => { + vote({ + type: 0, + UUID: segment.UUID, + setVoteMessage: setVoteMessage, + sendMessage + }); }}/> - } - { - (segment.actionType === ActionType.Skip || segment.actionType === ActionType.Mute - || segment.actionType === ActionType.Poi - && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(segment.hidden)) && { - const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.4); - stopAnimation(); + title="Copy Segment ID" + src={chrome.runtime.getURL("icons/clipboard.svg")} + onClick={async (e) => { + const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.3); - if (segment.hidden === SponsorHideType.Hidden) { - segment.hidden = SponsorHideType.Visible; - setHidden(SponsorHideType.Visible); + if (segment.UUID.length > 60) { + copyToClipboardPopup(segment.UUID, sendMessage); } else { - segment.hidden = SponsorHideType.Hidden; - setHidden(SponsorHideType.Hidden); + const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", { + UUID: segment.UUID, + videoID: videoID + }); + + if (segmentIDData.ok && segmentIDData.responseText) { + copyToClipboardPopup(segmentIDData.responseText, sendMessage); + } } - sendMessage({ - message: "hideSegment", - type: segment.hidden, - UUID: segment.UUID - }); - }}/> - } - { - segment.actionType !== ActionType.Full && - { - skipSegment({ - segment, - element: e.currentTarget, - sendMessage - }); + stopAnimation(); }}/> - } -
+ { + segment.actionType === ActionType.Chapter && + { + if (isLooped) { + loopChapter({ + segment: null, + element: e.currentTarget, + sendMessage + }); + } else { + loopChapter({ + segment, + element: e.currentTarget, + sendMessage + }); + } + + setIsLooped(!isLooped); + }}/> + } + { + (segment.actionType === ActionType.Skip || segment.actionType === ActionType.Mute + || segment.actionType === ActionType.Poi + && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(segment.hidden)) && + { + const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.4); + stopAnimation(); + + if (segment.hidden === SponsorHideType.Hidden) { + segment.hidden = SponsorHideType.Visible; + setHidden(SponsorHideType.Visible); + } else { + segment.hidden = SponsorHideType.Hidden; + setHidden(SponsorHideType.Hidden); + } + + sendMessage({ + message: "hideSegment", + type: segment.hidden, + UUID: segment.UUID + }); + }}/> + } + { + segment.actionType !== ActionType.Full && + { + skipSegment({ + segment, + element: e.currentTarget, + sendMessage + }); + }}/> + } +
-
-
- {voteMessage} +
+
+ {voteMessage} +
-
-
+
+ } + { + segment.innerChapters + && + } +
); } +function InnerChapterList({ chapters, videoID, currentTime, isVip, loopedChapter, tabFilter, sendMessage }: { + chapters: (segmentWithNesting)[]; + videoID: VideoID; + currentTime: number; + isVip: boolean; + loopedChapter: SegmentUUID; + + tabFilter: (segment: SponsorTime) => boolean; + sendMessage: (request: Message) => Promise; +}) { + return
+ { + e.currentTarget.firstChild.textContent = (e.currentTarget.parentElement as HTMLDetailsElement).open ? chrome.i18n.getMessage("expandChapters").replace("{0}", String(chapters.length)) : chrome.i18n.getMessage("collapseChapters"); + }}> + {chrome.i18n.getMessage("collapseChapters")} + +
+ { + chapters.map((chapter) => { + return + }) + } +
+
+} + async function vote(props: { type: number; UUID: SegmentUUID; From 299560ec9a8329abde8414515d3f7e11723b4201 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 18 Sep 2025 03:19:00 -0400 Subject: [PATCH 02/16] Formatting fixes and add locales --- public/_locales | 2 +- src/popup/SegmentListComponent.tsx | 32 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/public/_locales b/public/_locales index 0285b87951..89f31bcf2d 160000 --- a/public/_locales +++ b/public/_locales @@ -1 +1 @@ -Subproject commit 0285b87951827abe87cd5f7fe4032a1389b0954a +Subproject commit 89f31bcf2db2b906cf16dec760bb4de52b46aaca diff --git a/src/popup/SegmentListComponent.tsx b/src/popup/SegmentListComponent.tsx index 1194f42211..4bf438b6ea 100644 --- a/src/popup/SegmentListComponent.tsx +++ b/src/popup/SegmentListComponent.tsx @@ -58,35 +58,37 @@ export const SegmentListComponent = (props: SegmentListComponentProps) => { } }; - const segmentsWithNesting: segmentWithNesting[] = [] - let nbTrailingNonChapters = 0 - function nestChapters(segments: segmentWithNesting[], seg: SponsorTime, topLevel?:boolean){ - if (seg.actionType === ActionType.Chapter - && segments.length) - { + const segmentsWithNesting: segmentWithNesting[] = []; + let nbTrailingNonChapters = 0; + function nestChapters(segments: segmentWithNesting[], seg: SponsorTime, topLevel?: boolean) { + if (seg.actionType === ActionType.Chapter && segments.length) { // trailing non-chapters can only exist at top level const lastElement = segments[segments.length - (topLevel ? nbTrailingNonChapters + 1 : 1)] if (lastElement.actionType === ActionType.Chapter - && lastElement.segment[0] <= seg.segment[0] - && lastElement.segment[1] >= seg.segment[1]) { + && lastElement.segment[0] <= seg.segment[0] + && lastElement.segment[1] >= seg.segment[1]) { if (lastElement.innerChapters){ nestChapters(lastElement.innerChapters, seg); } else { lastElement.innerChapters = [seg]; } - } - else { - if (topLevel){nbTrailingNonChapters = 0} + } else { + if (topLevel) { + nbTrailingNonChapters = 0; + } + segments.push(seg); } } else { - if (seg.actionType !== ActionType.Chapter){nbTrailingNonChapters++} + if (seg.actionType !== ActionType.Chapter) { + nbTrailingNonChapters++; + } segments.push(seg); } } - props.segments.forEach((seg) => {nestChapters(segmentsWithNesting, {...seg}, true)}) + props.segments.forEach((seg) => nestChapters(segmentsWithNesting, {...seg}, true)); return (
@@ -160,7 +162,6 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter, return (
- {
skipSegment({ segment, @@ -250,7 +251,6 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter, stopAnimation(); } - stopAnimation(); }}/> { segment.actionType === ActionType.Chapter && @@ -325,7 +325,7 @@ function SegmentListItem({ segment, videoID, currentTime, isVip, loopedChapter,
- } + { segment.innerChapters && Date: Thu, 18 Sep 2025 03:19:51 -0400 Subject: [PATCH 03/16] bump version --- manifest/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest/manifest.json b/manifest/manifest.json index 3073b6e5d5..95b51223ab 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_fullName__", "short_name": "SponsorBlock", - "version": "5.14", + "version": "6.0", "default_locale": "en", "description": "__MSG_Description__", "homepage_url": "https://sponsor.ajay.app", From f533921252a41f83ddaf4f8e87abfd8208abf0c5 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 18 Sep 2025 03:20:21 -0400 Subject: [PATCH 04/16] update translations --- public/_locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/_locales b/public/_locales index 89f31bcf2d..4cd536fa4a 160000 --- a/public/_locales +++ b/public/_locales @@ -1 +1 @@ -Subproject commit 89f31bcf2db2b906cf16dec760bb4de52b46aaca +Subproject commit 4cd536fa4a2efa7ee19f22268df800c3a80ea2b8 From 4d74516adc4cdd58426074c3d843cb210c1a0fa7 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 18 Sep 2025 03:22:00 -0400 Subject: [PATCH 05/16] Request unlimited storage for new local data --- manifest/firefox-manifest-extra.json | 5 +---- manifest/manifest.json | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/manifest/firefox-manifest-extra.json b/manifest/firefox-manifest-extra.json index 3c41591674..e89e5953db 100644 --- a/manifest/firefox-manifest-extra.json +++ b/manifest/firefox-manifest-extra.json @@ -13,8 +13,5 @@ }, "browser_action": { "default_area": "navbar" - }, - "permissions": [ - "unlimitedStorage" - ] + } } diff --git a/manifest/manifest.json b/manifest/manifest.json index 95b51223ab..5f01711b00 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -16,7 +16,8 @@ }, "permissions": [ "storage", - "scripting" + "scripting", + "unlimitedStorage" ], "options_ui": { "page": "options/options.html", From d4995a6357d7dda07b20415f921547d4e7097205 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 18 Sep 2025 03:28:14 -0400 Subject: [PATCH 06/16] Don't show skip profile button when not on a video --- src/popup/PopupComponent.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/popup/PopupComponent.tsx b/src/popup/PopupComponent.tsx index 6475bc5ce8..68907b8cd2 100644 --- a/src/popup/PopupComponent.tsx +++ b/src/popup/PopupComponent.tsx @@ -163,10 +163,13 @@ export const PopupComponent = () => { {/* Toggle Box */}
- + { + videoID && + + }