Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters

This commit is contained in:
Ajay
2022-06-03 02:20:30 -04:00
48 changed files with 4931 additions and 3459 deletions

View File

@@ -1,34 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
jasmine: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {
// TODO: Remove warn rules when not needed anymore
"no-self-assign": "off",
"@typescript-eslint/no-empty-interface": "off",
"react/prop-types": [2, { ignore: ['children'] }]
},
settings: {
react: {
version: "detect",
},
},
};

33
.eslintrc.json Normal file
View File

@@ -0,0 +1,33 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"no-self-assign": "off",
"@typescript-eslint/no-empty-interface": "off",
"react/prop-types": [2, { "ignore": ["children"] }]
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@@ -5,4 +5,5 @@ module.exports = {
"transform": { "transform": {
"^.+\\.ts$": "ts-jest" "^.+\\.ts$": "ts-jest"
}, },
"reporters": ["github-actions"]
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "__MSG_fullName__", "name": "__MSG_fullName__",
"short_name": "SponsorBlock", "short_name": "SponsorBlock",
"version": "4.4", "version": "4.4.5",
"default_locale": "en", "default_locale": "en",
"description": "__MSG_Description__", "description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app", "homepage_url": "https://sponsor.ajay.app",
@@ -34,6 +34,7 @@
"icons/settings.svg", "icons/settings.svg",
"icons/pencil.svg", "icons/pencil.svg",
"icons/check.svg", "icons/check.svg",
"icons/check-smaller.svg",
"icons/upvote.png", "icons/upvote.png",
"icons/downvote.png", "icons/downvote.png",
"icons/thumbs_down.svg", "icons/thumbs_down.svg",
@@ -52,6 +53,16 @@
"icons/visible.svg", "icons/visible.svg",
"icons/not_visible.svg", "icons/not_visible.svg",
"icons/sort.svg", "icons/sort.svg",
"icons/money.svg",
"icons/segway.png",
"icons/close-smaller.svg",
"icons/right-arrow.svg",
"icons/campaign.svg",
"icons/star.svg",
"icons/lightbulb.svg",
"icons/bolt.svg",
"icons/stopwatch.svg",
"icons/music-note.svg",
"icons/PlayerInfoIconSponsorBlocker.svg", "icons/PlayerInfoIconSponsorBlocker.svg",
"icons/PlayerDeleteIconSponsorBlocker.svg", "icons/PlayerDeleteIconSponsorBlocker.svg",
"popup.html", "popup.html",
@@ -66,7 +77,35 @@
], ],
"browser_action": { "browser_action": {
"default_title": "SponsorBlock", "default_title": "SponsorBlock",
"default_popup": "popup.html" "default_popup": "popup.html",
"default_icon": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/LogoSponsorBlocker64px.png",
"128": "icons/LogoSponsorBlocker128px.png"
},
"theme_icons": [
{
"light": "icons/IconSponsorBlocker16px.png",
"dark": "icons/IconSponsorBlocker16px.png",
"size": 16
},
{
"light": "icons/IconSponsorBlocker32px.png",
"dark": "icons/IconSponsorBlocker32px.png",
"size": 32
},
{
"light": "icons/LogoSponsorBlocker64px.png",
"dark": "icons/LogoSponsorBlocker64px.png",
"size": 64
},
{
"light": "icons/LogoSponsorBlocker128px.png",
"dark": "icons/LogoSponsorBlocker128px.png",
"size": 128
}
]
}, },
"background": { "background": {
"scripts":[ "scripts":[

6152
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,39 +3,40 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "background.js", "main": "background.js",
"type": "module",
"dependencies": { "dependencies": {
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.180", "@types/chrome": "^0.0.188",
"@types/firefox-webext-browser": "^94.0.1", "@types/firefox-webext-browser": "^94.0.1",
"@types/jest": "^27.4.1", "@types/jest": "^27.5.1",
"@types/react": "^17.0.43", "@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14", "@types/react-dom": "^17.0.14",
"@types/selenium-webdriver": "^4.0.18", "@types/selenium-webdriver": "^4.1.0",
"@types/wicg-mediasession": "^1.1.3", "@types/wicg-mediasession": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^5.17.0", "@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.17.0", "@typescript-eslint/parser": "^5.26.0",
"chromedriver": "^100.0.0", "chromedriver": "^101.0.0",
"concurrently": "^7.0.0", "concurrently": "^7.2.1",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^11.0.0",
"eslint": "^8.12.0", "eslint": "^8.16.0",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.30.0",
"fork-ts-checker-webpack-plugin": "^7.2.1", "fork-ts-checker-webpack-plugin": "^7.2.11",
"jest": "^27.5.1", "jest": "^28.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"schema-utils": "^4.0.0", "schema-utils": "^4.0.0",
"selenium-webdriver": "^4.1.1", "selenium-webdriver": "^4.1.2",
"speed-measure-webpack-plugin": "^1.5.0", "speed-measure-webpack-plugin": "^1.5.0",
"ts-jest": "^27.1.4", "ts-jest": "^28.0.3",
"ts-loader": "^9.2.8", "ts-loader": "^9.3.0",
"ts-node": "^10.7.0", "ts-node": "^10.8.0",
"typescript": "4.6", "typescript": "4.7",
"web-ext": "^6.8.0", "web-ext": "^6.8.0",
"webpack": "^5.64.4", "webpack": "^5.72.1",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-merge": "^4.2.2" "webpack-merge": "^5.8.0"
}, },
"scripts": { "scripts": {
"web-run": "npm run web-run:chrome", "web-run": "npm run web-run:chrome",
@@ -65,7 +66,7 @@
"lint:fix": "eslint src --fix" "lint:fix": "eslint src --fix"
}, },
"engines": { "engines": {
"node": ">=12.20.0" "node": ">=16"
}, },
"funding": [ "funding": [
{ {

View File

@@ -111,7 +111,7 @@
"message": "It seems the server is down. Contact the dev immediately." "message": "It seems the server is down. Contact the dev immediately."
}, },
"connectionError": { "connectionError": {
"message": "A connection error has occured. Error code: " "message": "A connection error has occurred. Error code: "
}, },
"clearTimes": { "clearTimes": {
"message": "Clear Segments" "message": "Clear Segments"
@@ -249,6 +249,9 @@
"showSkipNotice": { "showSkipNotice": {
"message": "Show Notice After A Segment Is Skipped" "message": "Show Notice After A Segment Is Skipped"
}, },
"showCategoryGuidelines": {
"message": "Show Category Help"
},
"noticeVisibilityMode0": { "noticeVisibilityMode0": {
"message": "Full Size Skip Notices" "message": "Full Size Skip Notices"
}, },
@@ -366,7 +369,7 @@
"message": "Show Time With Skips Removed" "message": "Show Time With Skips Removed"
}, },
"showTimeWithSkipsDescription": { "showTimeWithSkipsDescription": {
"message": "This time appears in brackets next to the current time on below the seekbar. This shows the total video duration minus any segments. This includes segments marked as only \"Show In Seekbar\"." "message": "This time appears in brackets next to the current time on below the Seek Bar. This shows the total video duration minus any segments. This includes segments marked as only \"Show In Seek Bar\"."
}, },
"youHaveSkipped": { "youHaveSkipped": {
"message": "You've skipped " "message": "You've skipped "
@@ -420,7 +423,7 @@
"message": "Supported Sites: " "message": "Supported Sites: "
}, },
"optionsInfo": { "optionsInfo": {
"message": "Enable Invidious support, disable autoskip, hide buttons and more." "message": "Enable Invidious support, disable auto skip, hide buttons and more."
}, },
"addInvidiousInstance": { "addInvidiousInstance": {
"message": "Add 3rd-Party Client Instance" "message": "Add 3rd-Party Client Instance"
@@ -509,7 +512,7 @@
"incorrectlyFormattedOptions": { "incorrectlyFormattedOptions": {
"message": "This JSON is not formatted correctly. Your options have not been changed." "message": "This JSON is not formatted correctly. Your options have not been changed."
}, },
"confirmNoticeTitle" : { "confirmNoticeTitle": {
"message": "Submit Segment" "message": "Submit Segment"
}, },
"submit": { "submit": {
@@ -552,18 +555,39 @@
"message": "to", "message": "to",
"description": "Used between segments. Example: 1:20 to 1:30" "description": "Used between segments. Example: 1:20 to 1:30"
}, },
"generic_guideline1": {
"message": "Include segue transitions"
},
"generic_guideline2": {
"message": "Plays as if nothing was skipped"
},
"category_sponsor": { "category_sponsor": {
"message": "Sponsor" "message": "Sponsor"
}, },
"category_sponsor_description": { "category_sponsor_description": {
"message": "Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shoutouts to causes/creators/websites/products they like." "message": "Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shoutouts to causes/creators/websites/products they like."
}, },
"category_sponsor_guideline1": {
"message": "Paid promotions"
},
"category_sponsor_guideline2": {
"message": "Not for donations or custom merch"
},
"category_selfpromo": { "category_selfpromo": {
"message": "Unpaid/Self Promotion" "message": "Unpaid/Self Promotion"
}, },
"category_selfpromo_description": { "category_selfpromo_description": {
"message": "Similar to \"sponsor\" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with." "message": "Similar to \"sponsor\" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with."
}, },
"category_selfpromo_guideline1": {
"message": "Donations, memberships and custom merch"
},
"category_selfpromo_guideline2": {
"message": "Free shoutouts that don't add to the video"
},
"category_selfpromo_guideline3": {
"message": "Not for corporate designed products and merch"
},
"category_exclusive_access": { "category_exclusive_access": {
"message": "Exclusive Access" "message": "Exclusive Access"
}, },
@@ -574,12 +598,24 @@
"message": "This video showcases a product, service or location that they've received free or subsidized access to", "message": "This video showcases a product, service or location that they've received free or subsidized access to",
"description": "Short description for this category" "description": "Short description for this category"
}, },
"category_exclusive_access_guideline1": {
"message": "Entire video showcases something with free or subsidized access"
},
"category_interaction": { "category_interaction": {
"message": "Interaction Reminder (Subscribe)" "message": "Interaction Reminder (Subscribe)"
}, },
"category_interaction_description": { "category_interaction_description": {
"message": "When there is a short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should be under self promotion instead." "message": "When there is a short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should be under self promotion instead."
}, },
"category_interaction_guideline1": {
"message": "Short reminders to like, subscribe or follow"
},
"category_interaction_guideline2": {
"message": "Includes indirect reminders to comment"
},
"category_interaction_guideline3": {
"message": "Not for general promotion, only calls to action"
},
"category_interaction_short": { "category_interaction_short": {
"message": "Interaction Reminder" "message": "Interaction Reminder"
}, },
@@ -592,18 +628,36 @@
"category_intro_short": { "category_intro_short": {
"message": "Intermission" "message": "Intermission"
}, },
"category_intro_guideline1": {
"message": "Interval without actual content"
},
"category_intro_guideline2": {
"message": "Not for transitions with information"
},
"category_outro": { "category_outro": {
"message": "Endcards/Credits" "message": "Endcards/Credits"
}, },
"category_outro_description": { "category_outro_description": {
"message": "Credits or when the YouTube endcards appear. Not for conclusions with information." "message": "Credits or when the YouTube endcards appear. Not for conclusions with information."
}, },
"category_outro_guideline1": {
"message": "Don't include content, even if endcards are on screen"
},
"category_preview": { "category_preview": {
"message": "Preview/Recap" "message": "Preview/Recap"
}, },
"category_preview_description": { "category_preview_description": {
"message": "Quick recap of previous episodes, or a preview of what's coming up later in the current video. Meant for edited together clips, not for spoken summaries." "message": "Quick recap of previous episodes, or a preview of what's coming up later in the current video. Meant for edited together clips, not for spoken summaries."
}, },
"category_preview_guideline1": {
"message": "Clips that appear later, or in a future video"
},
"category_preview_guideline2": {
"message": "Recap of a previous video"
},
"category_preview_guideline3": {
"message": "Not for sections that add additional content"
},
"category_filler": { "category_filler": {
"message": "Filler Tangent/Jokes" "message": "Filler Tangent/Jokes"
}, },
@@ -613,6 +667,15 @@
"category_filler_short": { "category_filler_short": {
"message": "Filler" "message": "Filler"
}, },
"category_filler_guideline1": {
"message": "Tangential scenes only for filler or humor"
},
"category_filler_guideline2": {
"message": "Distractions, bloopers, replays"
},
"category_filler_guideline3": {
"message": "Not for scenes required to understand the topic"
},
"category_music_offtopic": { "category_music_offtopic": {
"message": "Music: Non-Music Section" "message": "Music: Non-Music Section"
}, },
@@ -622,12 +685,27 @@
"category_music_offtopic_short": { "category_music_offtopic_short": {
"message": "Non-Music" "message": "Non-Music"
}, },
"category_music_offtopic_guideline1": {
"message": "Sections not in official releases"
},
"category_music_offtopic_guideline2": {
"message": "Non-music in a live performance"
},
"category_poi_highlight": { "category_poi_highlight": {
"message": "Highlight" "message": "Highlight"
}, },
"category_poi_highlight_description": { "category_poi_highlight_description": {
"message": "The part of the video that most people are looking for. Similar to \"Video starts at x\" comments." "message": "The part of the video that most people are looking for. Similar to \"Video starts at x\" comments."
}, },
"category_poi_highlight_guideline1": {
"message": "Section most people are looking for"
},
"category_poi_highlight_guideline2": {
"message": "Can skip context"
},
"category_poi_highlight_guideline3": {
"message": "Can skip to the title or thumbnail"
},
"category_chapter": { "category_chapter": {
"message": "Chapter" "message": "Chapter"
}, },
@@ -883,7 +961,10 @@
"LearnMore": { "LearnMore": {
"message": "Learn More" "message": "Learn More"
}, },
"CopyDownvoteButtonInfo": { "FullDetails": {
"message": "Full Details"
},
"CopyDownvoteButtonInfo": {
"message": "Downvotes and creates a local copy for you to resubmit" "message": "Downvotes and creates a local copy for you to resubmit"
}, },
"OpenCategoryWikiPage": { "OpenCategoryWikiPage": {
@@ -963,5 +1044,11 @@
}, },
"openOptionsPage": { "openOptionsPage": {
"message": "Open options page" "message": "Open options page"
},
"resetToDefault": {
"message": "Reset settings to default"
},
"confirmResetToDefault": {
"message": "Are you sure you want to reset all settings to their default values? This cannot be undone."
} }
} }

View File

@@ -186,7 +186,7 @@
"message": "Detta döljer knapparna på YouTube-spelaren som du kan skicka in segment med som ska hoppas över." "message": "Detta döljer knapparna på YouTube-spelaren som du kan skicka in segment med som ska hoppas över."
}, },
"showSkipButton": { "showSkipButton": {
"message": "Behåll knappen hoppa till markerat på spelaren" "message": "Behåll knappen hoppa till höjdpunkt på spelaren"
}, },
"showInfoButton": { "showInfoButton": {
"message": "Visa Infoknapp På YouTube-spelaren" "message": "Visa Infoknapp På YouTube-spelaren"
@@ -613,7 +613,7 @@
"message": "Icke-musik" "message": "Icke-musik"
}, },
"category_poi_highlight": { "category_poi_highlight": {
"message": "Markera" "message": "Höjdpunkt"
}, },
"category_poi_highlight_description": { "category_poi_highlight_description": {
"message": "Den del av videon som de flesta letar efter. Liknande kommentarer \"Video börjar på x\"." "message": "Den del av videon som de flesta letar efter. Liknande kommentarer \"Video börjar på x\"."

View File

@@ -190,9 +190,6 @@ div:hover > .sponsorBlockChapterBar {
} }
.sponsorSkipNoticeParent, .sponsorSkipNotice { .sponsorSkipNoticeParent, .sponsorSkipNotice {
min-width: 350px;
max-width: 50%;
border-spacing: var(--skip-notice-border-horizontal) var(--skip-notice-border-vertical); border-spacing: var(--skip-notice-border-horizontal) var(--skip-notice-border-vertical);
padding-left: var(--skip-notice-padding); padding-left: var(--skip-notice-padding);
padding-right: var(--skip-notice-padding); padding-right: var(--skip-notice-padding);
@@ -200,6 +197,15 @@ div:hover > .sponsorBlockChapterBar {
border-collapse: unset; border-collapse: unset;
} }
.sponsorSkipNoticeParent {
min-width: 350px;
max-width: 50%;
}
.sponsorSkipNotice {
width: 100%;
}
.sponsorSkipNoticeTableContainer { .sponsorSkipNoticeTableContainer {
background-color: rgba(28, 28, 28, 0.9); background-color: rgba(28, 28, 28, 0.9);
border-radius: 5px; border-radius: 5px;
@@ -211,7 +217,7 @@ div:hover > .sponsorBlockChapterBar {
} }
.sponsorSkipNoticeLimitWidth { .sponsorSkipNoticeLimitWidth {
min-width: calc(100% - 50px); max-width: calc(100% - 50px);
} }
.sponsorSkipNotice .hidden { .sponsorSkipNotice .hidden {
@@ -386,6 +392,22 @@ div:hover > .sponsorBlockChapterBar {
color: rgb(235, 235, 235); color: rgb(235, 235, 235);
} }
.sb-guidelines-notice .sponsorTimesInfoMessage td {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
font-size: 15px;
display: flex;
align-items: center;
}
.sponsorTimesInfoIcon {
width: 30px;
padding-right: 10px;
padding-left: 10px;
}
.segmentSummary { .segmentSummary {
outline: none !important; outline: none !important;
} }

37
public/icons/bolt.svg Normal file
View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="bolt.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M19.95 42 22 27.9H14.7Q14.15 27.9 13.9 27.4Q13.65 26.9 13.9 26.45L26.15 6H28.2L26.15 20.05H33.35Q33.9 20.05 34.175 20.55Q34.45 21.05 34.2 21.5L22 42Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

37
public/icons/campaign.svg Normal file
View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="campaign.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M36.5 25.5V22.5H44V25.5ZM39 40 32.95 35.5 34.75 33.1 40.8 37.6ZM34.9 14.85 33.1 12.45 39 8 40.8 10.4ZM10.5 38V30H7Q5.75 30 4.875 29.125Q4 28.25 4 27V21Q4 19.75 4.875 18.875Q5.75 18 7 18H16L26 12V36L16 30H13.5V38ZM28 30.7V17.3Q29.35 18.5 30.175 20.225Q31 21.95 31 24Q31 26.05 30.175 27.775Q29.35 29.5 28 30.7Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
version="1.1"
id="svg4"
sodipodi:docname="check-smaller.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="26.25"
inkscape:cx="12.038095"
inkscape:cy="12"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
fill="#ffffff"
d="M 17.69347,4.9833775 9.9421192,12.940517 6.3065298,9.5107153 3.7684768,12.048769 9.9421192,18.016623 20.231523,7.5214304 Z"
id="path2"
style="stroke-width:0.68596" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="close-smaller.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1366"
inkscape:window-height="731"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M12.45 37.65 10.35 35.55 21.9 24 10.35 12.45 12.45 10.35 24 21.9 35.55 10.35 37.65 12.45 26.1 24 37.65 35.55 35.55 37.65 24 26.1Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="lightbulb.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M24 44Q22.3 44 21.125 42.825Q19.95 41.65 19.95 39.95H28.05Q28.05 41.65 26.875 42.825Q25.7 44 24 44ZM15.9 36.85V33.85H32.1V36.85ZM16.15 30.8Q12.85 28.65 10.925 25.425Q9 22.2 9 18.15Q9 12.05 13.45 7.6Q17.9 3.15 24 3.15Q30.1 3.15 34.55 7.6Q39 12.05 39 18.15Q39 22.2 37.1 25.425Q35.2 28.65 31.85 30.8Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

37
public/icons/money.svg Normal file
View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="money.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="6.5625"
inkscape:cx="37.942857"
inkscape:cy="29.714286"
inkscape:window-width="1366"
inkscape:window-height="731"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M 22.070204,47.757552 V 42.214123 Q 18.308592,41.554191 15.89984,39.343419 13.491088,37.132647 12.435197,33.766994 l 3.695619,-1.517844 q 1.121884,3.167674 3.233667,4.718514 2.111782,1.55084 5.081476,1.55084 3.167674,0 5.213463,-1.583837 2.045789,-1.583837 2.045789,-4.355551 0,-2.903701 -1.814813,-4.487538 -1.814813,-1.583836 -6.830296,-3.233666 -4.75151,-1.517844 -7.094269,-4.025586 -2.342759,-2.507741 -2.342759,-6.269354 0,-3.629626 2.342759,-6.0713741 2.342759,-2.4417484 6.104371,-2.7717144 V 0.24244792 h 3.959592 V 5.7198835 q 2.969694,0.329966 5.114473,1.9467994 2.144779,1.6168335 3.266663,4.1245751 l -3.695619,1.583837 q -0.923905,-2.111783 -2.474745,-3.068684 -1.55084,-0.9569014 -4.058582,-0.9569014 -3.035687,0 -4.817503,1.3858574 -1.781817,1.385857 -1.781817,3.761612 0,2.507742 1.979796,4.058582 1.979796,1.55084 7.325246,3.20067 4.487537,1.385857 6.632316,3.992589 2.144779,2.606731 2.144779,6.566323 0,4.157572 -2.441748,6.69831 -2.441749,2.540738 -7.193259,3.266663 v 5.477436 z"
id="path2"
style="fill:#ffffff;stroke-width:1.31986" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="music-note.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M19.65 42Q16.5 42 14.325 39.825Q12.15 37.65 12.15 34.5Q12.15 31.35 14.325 29.175Q16.5 27 19.65 27Q21.05 27 22.175 27.4Q23.3 27.8 24.15 28.5V6H35.85V12.75H27.15V34.5Q27.15 37.65 24.975 39.825Q22.8 42 19.65 42Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="right-arrow.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24.07619"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M 17.039265,39.62264 14.838095,37.382164 28.320259,23.9 14.838095,10.417836 17.039265,8.1773601 32.761905,23.9 Z"
id="path2"
style="fill:#ffffff;stroke-width:0.786132" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/icons/segway.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

37
public/icons/star.svg Normal file
View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="star.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M11.65 44 16.3 28.8 4 20H19.2L24 4L28.8 20H44L31.7 28.8L36.35 44L24 34.6Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="48"
width="48"
version="1.1"
id="svg4"
sodipodi:docname="stopwatch.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="13.125"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M14.45 34Q16.3 35.95 18.8 36.975Q21.3 38 24 38Q29.85 38 33.925 33.925Q38 29.85 38 24Q38 18.15 33.925 14.075Q29.85 10 24 10V24ZM24 44Q19.75 44 16.1 42.475Q12.45 40.95 9.75 38.25Q7.05 35.55 5.525 31.9Q4 28.25 4 24Q4 19.8 5.525 16.15Q7.05 12.5 9.75 9.8Q12.45 7.1 16.1 5.55Q19.75 4 24 4Q28.2 4 31.85 5.55Q35.5 7.1 38.2 9.8Q40.9 12.5 42.45 16.15Q44 19.8 44 24Q44 28.25 42.45 31.9Q40.9 35.55 38.2 38.25Q35.5 40.95 31.85 42.475Q28.2 44 24 44Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -187,6 +187,18 @@
<option value="4">__MSG_noticeVisibilityMode4__</option> <option value="4">__MSG_noticeVisibilityMode4__</option>
</select> </select>
</div> </div>
<div data-type="toggle" data-sync="showCategoryGuidelines">
<div class="switch-container">
<label class="switch">
<input id="showCategoryGuidelines" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="showCategoryGuidelines">
__MSG_showCategoryGuidelines__
</label>
</div>
</div>
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideVideoPlayerControls"> <div data-type="toggle" data-toggle-type="reverse" data-sync="hideVideoPlayerControls">
<div class="switch-container"> <div class="switch-container">
@@ -380,6 +392,12 @@
</div> </div>
</div> </div>
<div data-type="button-press" data-sync="resetToDefault" data-confirm-message="confirmResetToDefault">
<div class="option-button trigger-button">
__MSG_resetToDefault__
</div>
</div>
</div> </div>
<div id="advanced" class="option-group hidden"> <div id="advanced" class="option-group hidden">

View File

@@ -1,40 +1,52 @@
:root { :root {
--sb-main-font-family: "Source Sans Pro", sans-serif; --sb-main-font-family: "Source Sans Pro", sans-serif;
--sb-main-bg-color: #222626; --sb-main-bg-color: #222;
--sb-main-fg-color: #fff; --sb-main-fg-color: #fff;
--sb-grey-bg-color: #333; --sb-grey-bg-color: #333;
--sb-grey-fg-color: #999;
--sb-red-bg-color: #cc1717; --sb-red-bg-color: #cc1717;
} }
/* /*
* Container when popup displayed in-page * Generic utilities
*/ */
.grey-text {
#sponsorBlockPopupContainer { color: var(--sb-grey-fg-color);
}
.white-text {
color: var(--sb-main-fg-color);
}
.sbHeader {
font-size: 20px;
font-weight: bold;
text-align: left;
margin: 0;
}
#sponsorBlockPopupBody .u-mZ {
margin: 0 !important;
position: relative; position: relative;
margin-bottom: 16px; }
#sponsorBlockPopupBody .hidden {
display: none !important;
} }
/* /*
* Disable popup max height when displayed in-page * <button> elements that have icons
*/ */
#setUsernameButton,
#sponsorBlockPopupContainer #sponsorBlockPopupHTML { #copyUserID,
max-height: none; #submitUsername {
} color: var(--sb-main-fg-color);
background: transparent;
/* width: fit-content;
* Disable fixed popup width when displayed in-page padding: none;
*/ border: none;
}
#sponsorBlockPopupContainer #sponsorBlockPopupBody {
width: auto;
}
/* /*
* Main containers * Main containers
*/ */
#sponsorBlockPopupHTML { #sponsorBlockPopupHTML {
color-scheme: dark; color-scheme: dark;
max-height: 600px; max-height: 600px;
@@ -45,8 +57,8 @@
margin: 0; margin: 0;
width: 374px; width: 374px;
max-width: 100%; /* NOTE: Ensures content doesn't exceed restricted popup widths in Firefox */ max-width: 100%; /* NOTE: Ensures content doesn't exceed restricted popup widths in Firefox */
font-family: var(--sb-main-font-family);
font-size: 14px; font-size: 14px;
font-family: var(--sb-main-font-family);
background-color: var(--sb-main-bg-color); background-color: var(--sb-main-bg-color);
color: var(--sb-main-fg-color); color: var(--sb-main-fg-color);
color-scheme: dark; color-scheme: dark;
@@ -64,15 +76,47 @@
/* /*
* Disable transition on all elements until the extension has loaded * Disable transition on all elements until the extension has loaded
*/ */
.sb-preload * { .sb-preload * {
transition: none !important; transition: none !important;
} }
/* /*
* Close popup button when displayed in-page * Alert indicating that Beta server is enabled
*/ */
#sbBetaServerWarning {
padding: 8px;
font-size: 1em;
font-weight: 700;
color: var(--sb-main-fg-color);
background-color: var(--sb-red-bg-color);
cursor: pointer;
}
/*
* Container when popup displayed in-page (content.ts)
*/
#sponsorBlockPopupContainer {
position: relative;
margin-bottom: 16px;
}
/*
* Disable popup max height when displayed in-page (content.ts)
*/
#sponsorBlockPopupContainer #sponsorBlockPopupHTML {
max-height: none;
}
/*
* Disable fixed popup width when displayed in-page (content.ts)
*/
#sponsorBlockPopupContainer #sponsorBlockPopupBody {
width: auto;
}
/*
* Close popup button when displayed in-page (top-right corner)
*/
.sbCloseButton { .sbCloseButton {
background: transparent; background: transparent;
border: 0; border: 0;
@@ -88,51 +132,34 @@
opacity: 1; opacity: 1;
} }
/*
* Alert indicating that Beta server is enabled
*/
#sbBetaServerWarning {
padding: 8px;
font-size: 1em;
font-weight: 700;
color: var(--sb-main-fg-color);
background-color: var(--sb-red-bg-color);
cursor: pointer;
}
/* /*
* Header logo * Header logo
*/ */
.sbPopupLogo { .sbPopupLogo {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 32px;
font-weight: bold;
justify-content: center; justify-content: center;
font-weight: bold;
user-select: none; user-select: none;
padding: 20px 0 10px; padding: 10px 0px 0px;
font-size: 32px;
} }
.sbPopupLogo img { .sbPopupLogo img {
margin-right: 8px; margin: 8px;
} }
/* /*
* Refresh segments button * Refresh segments button
*/ */
#refreshSegmentsButton { #refreshSegmentsButton {
background: transparent;
border: 0;
border-radius: 50%;
display: flex; display: flex;
padding: 5px;
margin: 5px auto;
align-items: center; align-items: center;
background: transparent;
border-radius: 50%;
margin: 5px auto;
border: none;
padding: 5px;
} }
#refreshSegmentsButton:hover { #refreshSegmentsButton:hover {
background-color: var(--sb-grey-bg-color); background-color: var(--sb-grey-bg-color);
} }
@@ -140,15 +167,14 @@
/* /*
* <details> wrapper around each segment * <details> wrapper around each segment
*/ */
.votingButtons { .votingButtons {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
border-radius: 8px;
margin: 4px 16px;
} }
.votingButtons[open] { .votingButtons[open] {
padding-bottom: 5px; padding-bottom: 5px;
} }
.votingButtons:hover { .votingButtons:hover {
background-color: var(--sb-grey-bg-color); background-color: var(--sb-grey-bg-color);
} }
@@ -156,34 +182,46 @@
/* /*
* Individual segments summaries (clickable <summary>) * Individual segments summaries (clickable <summary>)
*/ */
.segmentSummary { .segmentSummary {
cursor: pointer; display: flex;
font-weight: bold; align-items: center;
padding: 7px; justify-content: space-between;
list-style: none;
white-space: nowrap; white-space: nowrap;
font-weight: bold;
list-style: none;
cursor: pointer;
padding: 4px 8px;
}
.segmentSummary > div {
text-align: left;
} }
/* /*
* Category dot in segment * Category dot in segment
*/ */
.sponsorTimesCategoryColorCircle { .sponsorTimesCategoryColorCircle {
margin: 0 8px; margin-right: 8px;
} }
.dot { .dot {
width: 10px; width: 10px;
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
display: inline-block; display: inline-block;
} }
/*
* Category name in segment
*/
.summaryLabel {
overflow-wrap: break-word;
white-space: normal;
}
.sbVoteButtonsContainer {
text-align: right;
}
/* /*
* Buttons that appear under a segment on click * Buttons that appear under a segment on click
*/ */
.voteButton { .voteButton {
height: 20px; height: 20px;
padding: 0 5px; padding: 0 5px;
@@ -197,7 +235,6 @@
/* /*
* "Voted!" text that appears after voting on a segment * "Voted!" text that appears after voting on a segment
*/ */
.sponsorTimesThanksForVotingText { .sponsorTimesThanksForVotingText {
font-size: large; font-size: large;
} }
@@ -205,111 +242,44 @@
/* /*
* Main controls menu * Main controls menu
*/ */
.sbControlsMenu { .sbControlsMenu {
margin: 16px;
margin-top: 6px;
border-radius: 8px;
background-color: var(--sb-grey-bg-color); background-color: var(--sb-grey-bg-color);
display: flex;
justify-content: space-evenly; justify-content: space-evenly;
margin-top: 10px; overflow: hidden;
}
.sbControlsMenu-item {
background: transparent;
border: 0;
cursor: pointer;
user-select: none;
padding: 10px 15px;
flex: 1;
display: flex; display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
} }
.sbControlsMenu-item {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
background: transparent;
user-select: none;
cursor: pointer;
border: none;
flex: 1;
padding: 10px 15px;
trasition: background-color 0.2s ease-in-out;
}
.sbControlsMenu-item:hover { .sbControlsMenu-item:hover {
background-color: #444; background-color: #444;
} }
.sbControlsMenu-itemIcon { .sbControlsMenu-itemIcon {
margin-bottom: 6px; margin-bottom: 6px;
} }
/*
* "Skipping is enabled" toggle
*/
.toggleSwitchContainer {
display: flex;
flex-direction: column;
align-items: center;
}
.toggleSwitchContainer-switch {
display: flex;
margin-bottom: 6px;
}
.switchBg {
display: block;
width: 50px;
height: 23px;
border-radius: 18.5px;
}
.switchBg.shadow {
box-shadow: 0.75px 0.75px 10px 0px rgba(50, 50, 50, 0.5);
opacity: 1;
}
.switchBg.white {
position: absolute;
background-color: #ccc;
opacity: 1;
}
.switchBg.green {
position: absolute;
background-color: #00a205;
opacity: 0;
transition: opacity 0.2s ease-out;
}
.switchDot {
background-color: var(--sb-main-fg-color);
border-radius: 50%;
width: 15px;
height: 15px;
margin: 4px;
position: absolute;
box-shadow: 0.75px 0.75px 3.8px 0px rgba(50, 50, 50, 0.45);
transition: transform 0.2s ease-out;
}
#toggleSwitch:checked ~ .switchDot {
transform: translateX(27px);
}
#toggleSwitch:checked ~ .switchBg.green {
opacity: 1;
}
#toggleSwitch:checked ~ .switchBg.white {
opacity: 0;
transition: opacity 0.2s step-end;
}
/* /*
* Whitelist add/remove icon * Whitelist add/remove icon
*/ */
.SBWhitelistIcon > path { .SBWhitelistIcon > path {
fill: var(--sb-main-fg-color); fill: var(--sb-main-fg-color);
} }
.SBWhitelistIcon.rotated { .SBWhitelistIcon.rotated {
transform: rotate(45deg); transform: rotate(45deg);
} }
@keyframes rotate { @keyframes rotate {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
@@ -319,239 +289,255 @@
} }
} }
/*
* "Skipping is enabled" toggle
*/
.toggleSwitchContainer {
display: flex;
align-items: center;
flex-direction: column;
}
.toggleSwitchContainer-switch {
display: flex;
margin-bottom: 6px;
}
.switchBg {
width: 50px;
height: 23px;
display: block;
border-radius: 18.5px;
}
.switchBg.shadow {
box-shadow: 0.75px 0.75px 10px 0px rgba(50, 50, 50, 0.5);
opacity: 1;
}
.switchBg.white {
opacity: 1;
position: absolute;
background-color: #ccc;
}
.switchBg.green {
opacity: 0;
position: absolute;
background-color: #00a205;
transition: opacity 0.2s ease-out;
}
.switchDot {
width: 15px;
margin: 4px;
height: 15px;
border-radius: 50%;
position: absolute;
transition: transform 0.2s ease-out;
background-color: var(--sb-main-fg-color);
box-shadow: 0.75px 0.75px 3.8px 0px rgba(50, 50, 50, 0.45);
}
#toggleSwitch:checked ~ .switchDot {
transform: translateX(27px);
}
#toggleSwitch:checked ~ .switchBg.green {
opacity: 1;
}
#toggleSwitch:checked ~ .switchBg.white {
transition: opacity 0.2s step-end;
opacity: 0;
}
/* /*
* Notice that appears when whitelisting a channel, that recommends * Notice that appears when whitelisting a channel, that recommends
* enabling the "Force Channel Check Before Skipping" option * enabling the "Force Channel Check Before Skipping" option
*/ */
#whitelistForceCheck { #whitelistForceCheck {
background-color: #fff3cd; background-color: #fff3cd;
color: #664d03;
display: block;
padding: 10px 15px; padding: 10px 15px;
display: block;
color: #664d03;
} }
#whitelistForceCheck:hover { #whitelistForceCheck:hover {
background-color: #f2e4b7; background-color: #f2e4b7;
} }
/* /*
* Container around the "Segment Starts Now" and "Submit Times" buttons * Submit box
*/ */
#mainControls { #mainControls {
margin-bottom: 12px; margin: 16px;
padding: 8px 12px;
text-align: left;
border-radius: 8px;
border: 2px solid var(--sb-grey-bg-color);
} }
.sponsorStartHint { .sponsorStartHint {
display: block; display: block;
padding: 0 10px 12px; text-align: left;
} }
/* /*
* Generic buttons used for "Segment Starts Now" and "Submit Times" * Generic buttons used for "Segment Starts Now" and "Submit Times"
*/ */
.sbMediumButton { .sbMediumButton {
background-color: var(--sb-red-bg-color); border: none;
border: 0; font-size: 16px;
-moz-border-radius: 28px; padding: 8px 16px;
-webkit-border-radius: 28px;
border-radius: 28px; border-radius: 28px;
display: inline-block; display: inline-block;
-moz-border-radius: 28px;
-webkit-border-radius: 28px;
color: var(--sb-main-fg-color); color: var(--sb-main-fg-color);
font-size: 16px;
padding: 8px 37px;
font-family: var(--sb-main-font-family);
transition: 0.01s background-color; transition: 0.01s background-color;
font-family: var(--sb-main-font-family);
background-color: var(--sb-red-bg-color);
} }
.sbMediumButton:hover, .sbMediumButton:hover,
.sbMediumButton:focus { .sbMediumButton:focus {
outline: none;
background-color: #ec1c1c; background-color: #ec1c1c;
outline: none;
} }
.sbMediumButton:active { .sbMediumButton:active {
position: relative; position: relative;
top: 1px; top: 1px;
} }
/* /*
* "Submit Times" button * "Submit Times" button
*/ */
#submitTimes { #submitTimes {
margin-top: 12px; margin-top: 12px;
} }
/* /*
* Heading utility class * Your Work box
*/ */
.sbHeader {
font-size: 20px;
font-weight: bold;
margin: 10px 0 5px;
}
/*
* Side-by-side section of "Your Work"
*/
.sbYourWorkCols { .sbYourWorkCols {
display: flex; margin: 16px;
margin: 0 20px 12px; margin-bottom: 8px;
border-radius: 8px;
border: 2px solid var(--sb-grey-bg-color);
} }
.sbYourWorkCols > div { .sbYourWorkCols > div {
display: flex; display: flex;
align-items: center; border-top: 2px solid var(--sb-grey-bg-color);
flex-basis: 50%; border-bottom: 2px solid var(--sb-grey-bg-color);
justify-content: center;
} }
/* .sbStatsSentence {
* <button> elements that have icons padding-top: 5px;
*/ padding-bottom: 5px;
#setUsernameButton,
#copyUserID,
#submitUsername {
background: transparent;
border: 0;
padding: 0;
color: var(--sb-main-fg-color);
width: fit-content;
}
/*
* Prevent username from wrapping
*/
#setUsernameButton {
flex: 0 1;
margin-right: 5px;
}
/*
* Set username button
*/
#submitUsername {
padding-left: 7px;
} }
/* /*
* Increase font size of username input and display * Increase font size of username input and display
*/ */
#usernameValue, #usernameValue,
#usernameInput, #usernameInput,
#sponsorTimesContributionsDisplay { #sponsorTimesContributionsDisplay {
font-size: 16px; font-size: 16px;
flex: 1 0; flex: 1 0;
} }
/*
/* * Improve alignment of username and submissions
* Left align "Username" and "Submissions" labels
*/ */
#usernameElement,
#usernameElement > div > p, #sponsorTimesContributionsContainer {
display: flex;
flex-direction: column;
justify-content: start;
}
#usernameElement > span,
#sponsorTimesContributionsContainer { #sponsorTimesContributionsContainer {
text-align: start; text-align: start;
} }
/* #sponsorTimesContributionsContainer {
* Enable flexbox for buttons with SVG icon margin-left: 8px;
*/ padding-left: 8px;
border-left: 2px solid var(--sb-grey-bg-color);
#setUsernameContainer > button {
display: flex;
} }
/* /*
* Improve position of "Copy User ID" button * Username
*/ */
#usernameElement {
padding: 8px;
}
#setUsernameContainer {
display: flex;
}
#setUsernameContainer > button {
display: flex;
}
#setUsernameButton {
margin-right: 5px;
flex: 0 1;
}
#submitUsername {
padding-left: 16px;
}
#copyUserID { #copyUserID {
width: 100%; width: 100%;
flex: 0 1; flex: 0 1;
} }
/*
* Container around username display and edit
*/
#setUsernameContainer {
display: flex;
}
/*
* Improve alignment of username and submissions
*/
#usernameElement > div,
#sponsorTimesContributionsContainer > div {
display: flex;
flex-flow: column nowrap;
align-items: flex-start;
}
/* /*
* Truncate username display * Truncate username display
*/ */
#usernameValue { #usernameValue {
overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 130px; white-space: nowrap;
overflow: hidden;
margin: 0 8px 0 0; margin: 0 8px 0 0;
max-width: 130px;
} }
/* /*
* Set username form container with "expanded" state * Set username form container with "expanded" state
*/ */
#setUsername {
display: flex;
}
#setUsername.SBExpanded { #setUsername.SBExpanded {
width: 200%; width: calc(200% - 130px);
text-align: left;
} }
/* /*
* Set username input * Set username input
*/ */
#usernameInput { #usernameInput {
background: transparent; border: none;
padding: 2px; padding: 4px 8px;
border: var(--sb-main-fg-color) 1px solid; border-radius: 4px;
color: var(--sb-main-fg-color); width: calc(100% - 68px);
width: calc(100% - 24px);
text-overflow: ellipsis; text-overflow: ellipsis;
color: var(--sb-main-fg-color);
background: var(--sb-grey-bg-color);
}
/*
* Submissions
*/
#sponsorTimesContributionsContainer {
padding: 8px;
} }
/* /*
* Footer * Footer
*/ */
#sbFooter { #sbFooter {
margin-top: 10px; padding: 8px 0;
padding-bottom: 20px;
} }
#sbFooter a { #sbFooter a {
transition: background 0.3s ease !important;
color: var(--sb-main-fg-color); color: var(--sb-main-fg-color);
display: inline-block;
text-decoration: none; text-decoration: none;
border-radius: 4px;
background: #333;
cursor: pointer;
padding: 4px 8px;
font-weight: 500;
margin: 2px 1px;
}
#sbFooter a:hover {
background: #444;
} }
/* /*
* "Show Notice Again" button * "Show Notice Again" button
*/ */
#showNoticeAgain { #showNoticeAgain {
background: transparent; background: transparent;
border: 1px solid #fff; border: 1px solid #fff;
@@ -561,11 +547,7 @@
padding: 5px; padding: 5px;
} }
/* #sponsorBlockPopupBody .u-mZ {
* Generic utilities
*/
#sponsorBlockPopupBody .u-mZ {
margin: 0 !important; margin: 0 !important;
} }

View File

@@ -8,9 +8,13 @@
<link id="sponsorBlockStyleSheet" href="popup.css" rel="stylesheet"> <link id="sponsorBlockStyleSheet" href="popup.css" rel="stylesheet">
</head> </head>
<body id="sponsorBlockPopupBody"> <body id="sponsorBlockPopupBody" style="visibility: hidden">
<div id="sponsorblockPopup" class="sponsorBlockPageBody sb-preload"> <div id="sponsorblockPopup" class="sponsorBlockPageBody sb-preload">
<button id="sbCloseButton" title="__MSG_closePopup__" class="sbCloseButton hidden">
<img src="icons/close.png" width="15" height="15">
</button>
<div id="sbBetaServerWarning" class="hidden" title="__MSG_openOptionsPage__"> <div id="sbBetaServerWarning" class="hidden" title="__MSG_openOptionsPage__">
__MSG_betaServerWarning__ __MSG_betaServerWarning__
</div> </div>
@@ -19,15 +23,16 @@
<img src="icons/IconSponsorBlocker256px.png" alt="SponsorBlock" width="40" height="40" id="sponsorBlockPopupLogo"> <img src="icons/IconSponsorBlocker256px.png" alt="SponsorBlock" width="40" height="40" id="sponsorBlockPopupLogo">
<p class="u-mZ">SponsorBlock</p> <p class="u-mZ">SponsorBlock</p>
</header> </header>
<div id="videoInfo"> <div id="videoInfo">
<!-- Loading text --> <!-- Loading text -->
<p id="loadingIndicator" class="u-mZ">__MSG_noVideoID__</p> <p id="loadingIndicator" class="u-mZ grey-text">__MSG_noVideoID__</p>
<!-- If the video was found in the database --> <!-- If the video was found in the database -->
<p id="videoFound" class="u-mZ"></p> <p id="videoFound" class="u-mZ grey-text"></p>
<button id="refreshSegmentsButton" title="__MSG_refreshSegments__"> <button id="refreshSegmentsButton" title="__MSG_refreshSegments__">
<img src="/icons/refresh.svg" alt="Refresh icon" id="refreshSegments" /> <img src="/icons/refresh.svg" alt="Refresh icon" id="refreshSegments" />
</button> </button>
<!-- Video Segments -->
<div id="issueReporterContainer"> <div id="issueReporterContainer">
<div id="issueReporterTabs" class="hidden"> <div id="issueReporterTabs" class="hidden">
<span id="issueReporterTabSegments" class="sbSelected"> <span id="issueReporterTabSegments" class="sbSelected">
@@ -40,7 +45,8 @@
<div id="issueReporterTimeButtons"></div> <div id="issueReporterTimeButtons"></div>
</div> </div>
</div> </div>
<!-- Toggle Box -->
<div class="sbControlsMenu"> <div class="sbControlsMenu">
<label id="whitelistButton" for="whitelistToggle" class="hidden sbControlsMenu-item"> <label id="whitelistButton" for="whitelistToggle" class="hidden sbControlsMenu-item">
<input type="checkbox" style="display:none;" id="whitelistToggle"> <input type="checkbox" style="display:none;" id="whitelistToggle">
@@ -72,25 +78,31 @@
__MSG_forceChannelCheckPopup__ __MSG_forceChannelCheckPopup__
</a> </a>
<!-- Submit box -->
<div id="mainControls" style="display: none"> <div id="mainControls" style="display: none">
<p class="sbHeader"> <p class="sbHeader">
__MSG_recordTimesDescription__ __MSG_recordTimesDescription__
</p> </p>
<sub class="sponsorStartHint">__MSG_popupHint__</sub> <sub class="sponsorStartHint grey-text">__MSG_popupHint__</sub>
<div> <div align="center" style="margin: 8px 0;">
<button id="sponsorStart" class="sbMediumButton">__MSG_sponsorStart__</button> <button id="sponsorStart" class="sbMediumButton" style="margin-right: 8px">__MSG_sponsorStart__</button>
</div> <button id="submitTimes" class="sbMediumButton" style="display: none; margin: 0 !important;">__MSG_submitTimesButton__</button>
<div id="submissionSection" style="display: none">
<b style="display: block; margin-top: 12px;">__MSG_submissionEditHint__</b>
<button id="submitTimes" class="sbMediumButton">__MSG_submitTimesButton__</button>
</div> </div>
<span id="submissionHint" style="display: none;">__MSG_submissionEditHint__</span>
</div> </div>
<h1 class="recordingSubtitle sbHeader">__MSG_yourWork__</h1> <!-- Your Work box -->
<div class="sbYourWorkCols"> <div class="sbYourWorkCols">
<div id="usernameElement"> <p class="sbHeader" style="padding: 8px 16px; cursor: pointer;">
<div> __MSG_yourWork__
<p class="u-mZ">__MSG_Username__:</p> </p>
<div>
<!-- Username -->
<div id="usernameElement">
<span class="u-mZ grey-text">__MSG_Username__:
<!-- loading/errors -->
<span id="setUsernameStatus" class="u-mZ white-text" style="display: none"></span>
</span>
<div id="setUsernameContainer"> <div id="setUsernameContainer">
<p id="usernameValue"></p> <p id="usernameValue"></p>
<button id="setUsernameButton" title="__MSG_setUsername__"> <button id="setUsernameButton" title="__MSG_setUsername__">
@@ -101,48 +113,48 @@
</button> </button>
</div> </div>
<div id="setUsername" style="display: none"> <div id="setUsername" style="display: none">
<div id="setUsernameStatusContainer" style="display: none">
<p id="setUsernameStatus" class="u-mZ"></p>
</div>
<input id="usernameInput" placeholder="Username"> <input id="usernameInput" placeholder="Username">
<button id="submitUsername"> <button id="submitUsername">
<img src="/icons/check.svg" alt="__MSG_setUsername__" width="16" height="16" id="sbPopupIconCheck"> <img src="/icons/check.svg" alt="__MSG_setUsername__" width="16" height="16" id="sbPopupIconCheck">
</button> </button>
</div> </div>
</div> </div>
</div> <!-- Submissions -->
<div id="sponsorTimesContributionsContainer" class="hidden"> <div id="sponsorTimesContributionsContainer" class="hidden">
<div> <p class="u-mZ grey-text">__MSG_Submissions__:</p>
<p class="u-mZ">__MSG_Submissions__:</p> <span id="sponsorTimesContributionsDisplay">0</span>
<span id="sponsorTimesContributionsDisplay">
0
</span>
</div> </div>
</div> </div>
</div>
<p id="sponsorTimesViewsContainer" style="display: none" class="u-mZ"> <p id="sponsorTimesViewsContainer" style="display: none" class="u-mZ sbStatsSentence">
__MSG_savedPeopleFrom__ __MSG_savedPeopleFrom__
<b><span id="sponsorTimesViewsDisplay"> <b>
0 <span id="sponsorTimesViewsDisplay">0</span>
</span></b> </b>
<span id="sponsorTimesViewsDisplayEndWord">__MSG_Segments__</span> <span id="sponsorTimesViewsDisplayEndWord">__MSG_Segments__</span>
<br> <br />
(<b><span id="sponsorTimesOthersTimeSavedDisplay">0</span> (
<span id="sponsorTimesOthersTimeSavedEndWord">__MSG_minsLower__</span></b> <b>
<span>__MSG_youHaveSavedTimeEnd__</span>). <span id="sponsorTimesOthersTimeSavedDisplay">0</span>
</p> <span id="sponsorTimesOthersTimeSavedEndWord">__MSG_minsLower__</span>
<p id="sponsorTimesSkipsDoneContainer" style="display: none" class="u-mZ"> </b>
__MSG_youHaveSkipped__ <span>__MSG_youHaveSavedTimeEnd__</span>
<b><span id="sponsorTimesSkipsDoneDisplay"> )
0 </p>
</span></b> <p id="sponsorTimesSkipsDoneContainer" style="display: none" class="u-mZ sbStatsSentence">
<span id="sponsorTimesSkipsDoneEndWord">__MSG_Segments__</span> __MSG_youHaveSkipped__
(<b><span id="sponsorTimeSavedDisplay"> <b>
0 <span id="sponsorTimesSkipsDoneDisplay">0</span>
</span> </b>
<span id="sponsorTimeSavedEndWord">__MSG_minsLower__</span></b>). <span id="sponsorTimesSkipsDoneEndWord">__MSG_Segments__</span>
</p> (
<b>
<span id="sponsorTimeSavedDisplay">0</span>
<span id="sponsorTimeSavedEndWord">__MSG_minsLower__</span>
</b>
)
</p>
</div>
<footer id="sbFooter"> <footer id="sbFooter">
<div id="sponsorTimesDonateContainer" style="display: none; align-items: center; justify-content: center;"> <div id="sponsorTimesDonateContainer" style="display: none; align-items: center; justify-content: center;">
@@ -153,13 +165,13 @@
<img id="sbCloseDonate" src="/icons/close.png" alt="Close icon" height="8" style="padding-left: 5px; cursor: pointer;" /> <img id="sbCloseDonate" src="/icons/close.png" alt="Close icon" height="8" style="padding-left: 5px; cursor: pointer;" />
</div> </div>
<a href="https://sponsor.ajay.app" target="_blank" rel="noopener">__MSG_website__</a> | <a id="helpButton">__MSG_help__</a>
<a href="https://sponsor.ajay.app/stats" target="_blank" rel="noopener">__MSG_viewLeaderboard__</a> | <a href="https://sponsor.ajay.app" target="_blank" rel="noopener">__MSG_website__</a>
<a href="https://sponsor.ajay.app/stats" target="_blank" rel="noopener">__MSG_viewLeaderboard__</a>
<br />
<a href="https://github.com/ajayyy/SponsorBlock" target="_blank" rel="noopener">GitHub</a> <a href="https://github.com/ajayyy/SponsorBlock" target="_blank" rel="noopener">GitHub</a>
<br/> <a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a>
<a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a> | <a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a>
<a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a> |
<a id="helpButton">__MSG_help__</a> |
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">$</a> <a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">$</a>
</footer> </footer>

View File

@@ -24,14 +24,37 @@ if (utils.isFirefox()) {
}); });
} }
chrome.tabs.onUpdated.addListener(function(tabId) { function onTabUpdatedListener(tabId: number) {
chrome.tabs.sendMessage(tabId, { chrome.tabs.sendMessage(tabId, {
message: 'update', message: 'update',
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox }, () => void chrome.runtime.lastError ); // Suppress error on Firefox
}
function onNavigationApiAvailableChange(changes: {[key: string]: chrome.storage.StorageChange}) {
if (changes.navigationApiAvailable) {
if (changes.navigationApiAvailable.newValue) {
chrome.tabs.onUpdated.removeListener(onTabUpdatedListener);
} else {
chrome.tabs.onUpdated.addListener(onTabUpdatedListener);
}
}
}
// If Navigation API is not supported, then background has to inform content script about video change.
// This happens on Safari, Firefox, and Chromium 101 (inclusive) and below.
chrome.tabs.onUpdated.addListener(onTabUpdatedListener);
utils.wait(() => Config.local !== null).then(() => {
if (Config.local.navigationApiAvailable) {
chrome.tabs.onUpdated.removeListener(onTabUpdatedListener);
}
}); });
chrome.runtime.onMessage.addListener(function (request, sender, callback) { if (!Config.configSyncListeners.includes(onNavigationApiAvailableChange)) {
switch(request.message) { Config.configSyncListeners.push(onNavigationApiAvailableChange);
}
chrome.runtime.onMessage.addListener(function (request, _, callback) {
switch(request.message) {
case "openConfig": case "openConfig":
chrome.tabs.create({url: chrome.runtime.getURL('options/options.html' + (request.hash ? '#' + request.hash : ''))}); chrome.tabs.create({url: chrome.runtime.getURL('options/options.html' + (request.hash ? '#' + request.hash : ''))});
return; return;
@@ -62,6 +85,19 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
case "unregisterContentScript": case "unregisterContentScript":
unregisterFirefoxContentScript(request.id) unregisterFirefoxContentScript(request.id)
return false; return false;
case "tabs":
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id,
request.data,
(response) => callback(response)
);
}
);
return true;
} }
}); });

View File

@@ -18,6 +18,7 @@ export interface CategorySkipOptionsState {
} }
class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> { class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> {
setBarColorTimeout: NodeJS.Timeout;
constructor(props: CategorySkipOptionsProps) { constructor(props: CategorySkipOptionsProps) {
super(props); super(props);
@@ -175,6 +176,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
} }
setColorState(event: React.FormEvent<HTMLInputElement>, preview: boolean): void { setColorState(event: React.FormEvent<HTMLInputElement>, preview: boolean): void {
clearTimeout(this.setBarColorTimeout);
if (preview) { if (preview) {
this.setState({ this.setState({
previewColor: event.currentTarget.value previewColor: event.currentTarget.value
@@ -191,7 +194,9 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
} }
// Make listener get called // Make listener get called
Config.config.barTypes = Config.config.barTypes; this.setBarColorTimeout = setTimeout(() => {
Config.config.barTypes = Config.config.barTypes;
}, 50);
} }
} }

View File

@@ -24,6 +24,7 @@ export interface NoticeProps {
smaller?: boolean, smaller?: boolean,
limitWidth?: boolean, limitWidth?: boolean,
extraClass?: string,
// Callback for when this is closed // Callback for when this is closed
closeListener: () => void, closeListener: () => void,
@@ -35,8 +36,6 @@ export interface NoticeProps {
} }
export interface NoticeState { export interface NoticeState {
noticeTitle: string,
maxCountdownTime: () => number, maxCountdownTime: () => number,
countdownTime: number, countdownTime: number,
@@ -54,9 +53,13 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
amountOfPreviousNotices: number; amountOfPreviousNotices: number;
parentRef: React.RefObject<HTMLDivElement>;
constructor(props: NoticeProps) { constructor(props: NoticeProps) {
super(props); super(props);
this.parentRef = React.createRef();
const maxCountdownTime = () => { const maxCountdownTime = () => {
if (this.props.maxCountdownTime) return this.props.maxCountdownTime(); if (this.props.maxCountdownTime) return this.props.maxCountdownTime();
else return Config.config.skipNoticeDuration; else return Config.config.skipNoticeDuration;
@@ -71,8 +74,6 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
// Setup state // Setup state
this.state = { this.state = {
noticeTitle: props.noticeTitle,
maxCountdownTime, maxCountdownTime,
//the countdown until this notice closes //the countdown until this notice closes
@@ -97,9 +98,11 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
return ( return (
<div id={"sponsorSkipNotice" + this.idSuffix} <div id={"sponsorSkipNotice" + this.idSuffix}
className={"sponsorSkipObject sponsorSkipNoticeParent" className={"sponsorSkipObject sponsorSkipNoticeParent"
+ (this.props.showInSecondSlot ? " secondSkipNotice" : "")} + (this.props.showInSecondSlot ? " secondSkipNotice" : "")
+ (this.props.extraClass ? ` ${this.props.extraClass}` : "")}
onMouseEnter={(e) => this.onMouseEnter(e) } onMouseEnter={(e) => this.onMouseEnter(e) }
onMouseLeave={() => this.timerMouseLeave()} onMouseLeave={() => this.timerMouseLeave()}
ref={this.parentRef}
style={noticeStyle} > style={noticeStyle} >
<div className={"sponsorSkipNoticeTableContainer" <div className={"sponsorSkipNoticeTableContainer"
+ (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "") + (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")
@@ -123,7 +126,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
style={{float: "left"}} style={{float: "left"}}
className="sponsorSkipMessage sponsorSkipObject"> className="sponsorSkipMessage sponsorSkipObject">
{this.state.noticeTitle} {this.props.noticeTitle}
</span> </span>
{this.props.firstColumn} {this.props.firstColumn}
@@ -344,12 +347,6 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
if (!silent) this.props.closeListener(); if (!silent) this.props.closeListener();
} }
changeNoticeTitle(title: string): void {
this.setState({
noticeTitle: title
});
}
addNoticeInfoMessage(message: string, message2 = ""): void { addNoticeInfoMessage(message: string, message2 = ""): void {
//TODO: Replace //TODO: Replace
@@ -384,6 +381,10 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
document.querySelector("#sponsorSkipNotice" + this.idSuffix + " > tbody").insertBefore(thanksForVotingText2, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix)); document.querySelector("#sponsorSkipNotice" + this.idSuffix + " > tbody").insertBefore(thanksForVotingText2, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
} }
} }
getElement(): React.RefObject<HTMLDivElement> {
return this.parentRef;
}
} }
export default NoticeComponent; export default NoticeComponent;

View File

@@ -1,6 +1,7 @@
import * as React from "react"; import * as React from "react";
export interface NoticeTextSelectionProps { export interface NoticeTextSelectionProps {
icon?: string,
text: string, text: string,
idSuffix: string, idSuffix: string,
onClick?: (event: React.MouseEvent) => unknown onClick?: (event: React.MouseEvent) => unknown
@@ -24,12 +25,21 @@ class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionPr
} }
return ( return (
<p id={"sponsorTimesInfoMessage" + this.props.idSuffix} <tr id={"sponsorTimesInfoMessage" + this.props.idSuffix}
onClick={this.props.onClick} onClick={this.props.onClick}
style={style} style={style}
className="sponsorTimesInfoMessage"> className="sponsorTimesInfoMessage">
{this.props.text}
</p> <td>
{this.props.icon ?
<img src={chrome.runtime.getURL(this.props.icon)} className="sponsorTimesInfoIcon" />
: null}
<span>
{this.props.text}
</span>
</td>
</tr>
); );
} }
} }

View File

@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import * as CompileConfig from "../../config.json"; import * as CompileConfig from "../../config.json";
import Config from "../config" import Config from "../config"
import { Category, ContentContainer, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types"; import { Category, ContentContainer, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
import NoticeComponent from "./NoticeComponent"; import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import Utils from "../utils"; import Utils from "../utils";
@@ -73,7 +73,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
amountOfPreviousNotices: number; amountOfPreviousNotices: number;
showInSecondSlot: boolean; showInSecondSlot: boolean;
idSuffix: string; idSuffix: string;
noticeRef: React.MutableRefObject<NoticeComponent>; noticeRef: React.MutableRefObject<NoticeComponent>;
@@ -106,7 +106,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
if (this.segments.length > 1) { if (this.segments.length > 1) {
this.segments.sort((a, b) => a.segment[0] - b.segment[0]); this.segments.sort((a, b) => a.segment[0] - b.segment[0]);
} }
// This is the suffix added at the end of every id // This is the suffix added at the end of every id
for (const segment of this.segments) { for (const segment of this.segments) {
this.idSuffix += segment.UUID; this.idSuffix += segment.UUID;
@@ -169,7 +169,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
noticeStyle.transform = "scale(0.8) translate(10%, 10%)"; noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
} }
// If it started out as smaller, always keep the // If it started out as smaller, always keep the
// skip button there // skip button there
const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute; const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute;
const firstColumn = showFirstSkipButton ? ( const firstColumn = showFirstSkipButton ? (
@@ -182,7 +182,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
showInSecondSlot={this.showInSecondSlot} showInSecondSlot={this.showInSecondSlot}
idSuffix={this.idSuffix} idSuffix={this.idSuffix}
fadeIn={true} fadeIn={true}
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)} || (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
timed={true} timed={true}
maxCountdownTime={this.state.maxCountdownTime} maxCountdownTime={this.state.maxCountdownTime}
@@ -206,7 +206,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
key={0}> key={0}>
{/* Vote Button Container */} {/* Vote Button Container */}
{!this.state.thanksForVotingText ? {!this.state.thanksForVotingText ?
<td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix} <td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix}
className="sponsorTimesVoteButtonsContainer"> className="sponsorTimesVoteButtonsContainer">
@@ -269,7 +269,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
? this.getSkipButton(1) : null} ? this.getSkipButton(1) : null}
{/* Never show button */} {/* Never show button */}
{!this.autoSkip || this.props.startReskip ? "" : {!this.autoSkip || this.props.startReskip ? "" :
<td className="sponsorSkipNoticeRightSection" <td className="sponsorSkipNoticeRightSection"
key={1}> key={1}>
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton" <button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
@@ -344,7 +344,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
} }
getSkipButton(buttonIndex: number): JSX.Element { getSkipButton(buttonIndex: number): JSX.Element {
if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1 if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1
|| this.segments[0].actionType !== ActionType.Poi || this.segments[0].actionType !== ActionType.Poi
|| this.props.unskipTime)) { || this.props.unskipTime)) {
@@ -366,8 +366,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
className="sponsorSkipObject sponsorSkipNoticeButton" className="sponsorSkipObject sponsorSkipNoticeButton"
style={style} style={style}
onClick={() => this.prepAction(buttonIndex === 1 ? SkipNoticeAction.Unskip1 : SkipNoticeAction.Unskip0)}> onClick={() => this.prepAction(buttonIndex === 1 ? SkipNoticeAction.Unskip1 : SkipNoticeAction.Unskip0)}>
{this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null) {this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null)
+ (!forceSeek && this.state.showKeybindHint + (!forceSeek && this.state.showKeybindHint
? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")} ? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")}
</button> </button>
</span> </span>
@@ -380,7 +380,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
for (let i = 0; i < this.segments.length; i++) { for (let i = 0; i < this.segments.length; i++) {
elements.push( elements.push(
<button className="sponsorSkipObject sponsorSkipNoticeButton" <button className="sponsorSkipObject sponsorSkipNoticeButton"
style={{opacity: this.getSubmissionChooserOpacity(i), style={{opacity: this.getSubmissionChooserOpacity(i),
color: this.getSubmissionChooserColor(i)}} color: this.getSubmissionChooserColor(i)}}
onClick={() => this.performAction(i)} onClick={() => this.performAction(i)}
key={"submission" + i + this.segments[i].category + this.idSuffix}> key={"submission" + i + this.segments[i].category + this.idSuffix}>
@@ -405,7 +405,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
getSubmissionChooserColor(index: number): string { getSubmissionChooserColor(index: number): string {
const isDownvote = this.state.actionState == SkipNoticeAction.Downvote; const isDownvote = this.state.actionState == SkipNoticeAction.Downvote;
const isCopyDownvote = this.state.actionState == SkipNoticeAction.CopyDownvote; const isCopyDownvote = this.state.actionState == SkipNoticeAction.CopyDownvote;
const shouldWarnUser = Config.config.isVip && (isDownvote || isCopyDownvote) const shouldWarnUser = Config.config.isVip && (isDownvote || isCopyDownvote)
&& this.segments[index].locked === 1; && this.segments[index].locked === 1;
return shouldWarnUser ? this.lockedColor : this.unselectedColor; return shouldWarnUser ? this.lockedColor : this.unselectedColor;
@@ -481,8 +481,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
/** /**
* Performs the action from the current state * Performs the action from the current state
* *
* @param index * @param index
*/ */
performAction(index: number, action?: SkipNoticeAction): void { performAction(index: number, action?: SkipNoticeAction): void {
switch (action ?? this.state.actionState) { switch (action ?? this.state.actionState) {
@@ -618,11 +618,6 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
countdownTime: Config.config.skipNoticeDuration countdownTime: Config.config.skipNoticeDuration
}; };
// See if the title should be changed
if (!this.autoSkip) {
newState.noticeTitle = chrome.i18n.getMessage("noticeTitle");
}
//reset countdown //reset countdown
this.setState(newState, () => { this.setState(newState, () => {
this.noticeRef.current.resetCountdown(); this.noticeRef.current.resetCountdown();
@@ -724,7 +719,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
messages messages
}); });
} }
addVoteButtonInfo(message: string): void { addVoteButtonInfo(message: string): void {
this.setState({ this.setState({
thanksForVotingText: message thanksForVotingText: message
@@ -787,7 +782,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case ActionType.Mute: { case ActionType.Mute: {
return chrome.i18n.getMessage("unmute"); return chrome.i18n.getMessage("unmute");
} }
case ActionType.Skip: case ActionType.Skip:
default: { default: {
return chrome.i18n.getMessage("unskip"); return chrome.i18n.getMessage("unskip");
} }
@@ -800,7 +795,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case ActionType.Mute: { case ActionType.Mute: {
return chrome.i18n.getMessage("mute"); return chrome.i18n.getMessage("mute");
} }
case ActionType.Skip: case ActionType.Skip:
default: { default: {
return chrome.i18n.getMessage("reskip"); return chrome.i18n.getMessage("reskip");
} }
@@ -813,7 +808,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case ActionType.Mute: { case ActionType.Mute: {
return chrome.i18n.getMessage("mute"); return chrome.i18n.getMessage("mute");
} }
case ActionType.Skip: case ActionType.Skip:
default: { default: {
return chrome.i18n.getMessage("skip"); return chrome.i18n.getMessage("skip");
} }

View File

@@ -20,6 +20,7 @@ export interface SponsorTimeEditProps {
submissionNotice: SubmissionNoticeComponent; submissionNotice: SubmissionNoticeComponent;
categoryList?: Category[]; categoryList?: Category[];
categoryChangeListener?: (index: number, category: Category) => void;
} }
export interface SponsorTimeEditState { export interface SponsorTimeEditState {
@@ -419,6 +420,10 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index]; const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
this.handleReplacingLostTimes(chosenCategory, sponsorTime.actionType, sponsorTime); this.handleReplacingLostTimes(chosenCategory, sponsorTime.actionType, sponsorTime);
this.saveEditTimes(); this.saveEditTimes();
if (this.props.categoryChangeListener) {
this.props.categoryChangeListener(this.props.index, chosenCategory);
}
} }
actionTypeSelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void { actionTypeSelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {

View File

@@ -1,10 +1,13 @@
import * as React from "react"; import * as React from "react";
import Config from "../config" import Config from "../config"
import { ContentContainer } from "../types"; import GenericNotice from "../render/GenericNotice";
import { Category, ContentContainer } from "../types";
import * as CompileConfig from "../../config.json";
import NoticeComponent from "./NoticeComponent"; import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import SponsorTimeEditComponent from "./SponsorTimeEditComponent"; import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
import { getGuidelineInfo } from "../utils/constants";
export interface SubmissionNoticeProps { export interface SubmissionNoticeProps {
// Contains functions and variables from the content script needed by the skip notice // Contains functions and variables from the content script needed by the skip notice
@@ -32,6 +35,8 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
videoObserver: MutationObserver; videoObserver: MutationObserver;
guidelinesReminder: GenericNotice;
constructor(props: SubmissionNoticeProps) { constructor(props: SubmissionNoticeProps) {
super(props); super(props);
this.noticeRef = React.createRef(); this.noticeRef = React.createRef();
@@ -135,6 +140,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
index={i} index={i}
contentContainer={this.props.contentContainer} contentContainer={this.props.contentContainer}
submissionNotice={this} submissionNotice={this}
categoryChangeListener={this.categoryChangeListener.bind(this)}
ref={timeRef}> ref={timeRef}>
</SponsorTimeEditComponent> </SponsorTimeEditComponent>
); );
@@ -161,6 +167,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
} }
cancel(): void { cancel(): void {
this.guidelinesReminder?.close();
this.noticeRef.current.close(true); this.noticeRef.current.close(true);
this.contentContainer().resetSponsorSubmissionNotice(); this.contentContainer().resetSponsorSubmissionNotice();
@@ -207,6 +214,45 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
this.forceUpdate(); this.forceUpdate();
} }
categoryChangeListener(index: number, category: Category): void {
const dialogWidth = this.noticeRef?.current?.getElement()?.current?.offsetWidth;
if (category !== "chooseACategory" && Config.config.showCategoryGuidelines
&& this.contentContainer().v.offsetWidth > dialogWidth * 2) {
const options = {
title: chrome.i18n.getMessage(`category_${category}`),
textBoxes: getGuidelineInfo(category),
buttons: [{
name: chrome.i18n.getMessage("FullDetails"),
listener: () => window.open(CompileConfig.wikiLinks[category])
},
{
name: chrome.i18n.getMessage("Hide"),
listener: () => {
Config.config.showCategoryGuidelines = false;
this.guidelinesReminder?.close();
this.guidelinesReminder = null;
}
}],
timed: false,
style: {
right: `${dialogWidth + 10}px`,
},
extraClass: "sb-guidelines-notice"
};
if (options.textBoxes) {
if (this.guidelinesReminder) {
this.guidelinesReminder.update(options);
} else {
this.guidelinesReminder = new GenericNotice(null, "GuidelinesReminder", options);
}
} else {
this.guidelinesReminder?.close();
this.guidelinesReminder = null;
}
}
}
} }
export default SubmissionNoticeComponent; export default SubmissionNoticeComponent;

View File

@@ -1,6 +1,4 @@
import * as React from "react"; import * as React from "react";
import Config from "../config";
import { Category, SegmentUUID, SponsorTime } from "../types";
export interface TooltipProps { export interface TooltipProps {
text: string; text: string;

View File

@@ -1,6 +1,6 @@
import * as CompileConfig from "../config.json"; import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json"; import * as invidiousList from "../ci/invidiouslist.json";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind, HashedValue, VideoID, SponsorHideType } from "./types"; import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
import { keybindEquals } from "./utils/configUtils"; import { keybindEquals } from "./utils/configUtils";
interface SBConfig { interface SBConfig {
@@ -56,6 +56,7 @@ interface SBConfig {
scrollToEditTimeUpdate: boolean, scrollToEditTimeUpdate: boolean,
categoryPillUpdate: boolean, categoryPillUpdate: boolean,
darkMode: boolean, darkMode: boolean,
showCategoryGuidelines: boolean,
// Used to cache calculated text color info // Used to cache calculated text color info
categoryPillColors: { categoryPillColors: {
@@ -102,9 +103,11 @@ export type VideoDownvotes = { segments: { uuid: HashedValue, hidden: SponsorHid
interface SBStorage { interface SBStorage {
/* VideoID prefixes to UUID prefixes */ /* VideoID prefixes to UUID prefixes */
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>, downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>,
navigationApiAvailable: boolean,
} }
export interface SBObject { export interface SBObject {
configLocalListeners: Array<(changes: StorageChangesObject) => unknown>;
configSyncListeners: Array<(changes: StorageChangesObject) => unknown>; configSyncListeners: Array<(changes: StorageChangesObject) => unknown>;
syncDefaults: SBConfig; syncDefaults: SBConfig;
localDefaults: SBStorage; localDefaults: SBStorage;
@@ -114,12 +117,14 @@ export interface SBObject {
local: SBStorage; local: SBStorage;
forceSyncUpdate(prop: string): void; forceSyncUpdate(prop: string): void;
forceLocalUpdate(prop: string): void; forceLocalUpdate(prop: string): void;
resetToDefault(): void;
} }
const Config: SBObject = { const Config: SBObject = {
/** /**
* Callback function when an option is updated * Callback function when an option is updated
*/ */
configLocalListeners: [],
configSyncListeners: [], configSyncListeners: [],
syncDefaults: { syncDefaults: {
userID: null, userID: null,
@@ -168,6 +173,7 @@ const Config: SBObject = {
scrollToEditTimeUpdate: false, // false means the tooltip will be shown scrollToEditTimeUpdate: false, // false means the tooltip will be shown
categoryPillUpdate: false, categoryPillUpdate: false,
darkMode: true, darkMode: true,
showCategoryGuidelines: true,
categoryPillColors: {}, categoryPillColors: {},
@@ -284,14 +290,16 @@ const Config: SBObject = {
} }
}, },
localDefaults: { localDefaults: {
downvotedSegments: {} downvotedSegments: {},
navigationApiAvailable: null
}, },
cachedSyncConfig: null, cachedSyncConfig: null,
cachedLocalStorage: null, cachedLocalStorage: null,
config: null, config: null,
local: null, local: null,
forceSyncUpdate, forceSyncUpdate,
forceLocalUpdate forceLocalUpdate,
resetToDefault
}; };
// Function setup // Function setup
@@ -302,7 +310,7 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
for (const key in changes) { for (const key in changes) {
Config.cachedSyncConfig[key] = changes[key].newValue; Config.cachedSyncConfig[key] = changes[key].newValue;
} }
for (const callback of Config.configSyncListeners) { for (const callback of Config.configSyncListeners) {
callback(changes); callback(changes);
} }
@@ -310,9 +318,13 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
for (const key in changes) { for (const key in changes) {
Config.cachedLocalStorage[key] = changes[key].newValue; Config.cachedLocalStorage[key] = changes[key].newValue;
} }
for (const callback of Config.configLocalListeners) {
callback(changes);
}
} }
}); });
const syncHandler: ProxyHandler<SBConfig> = { const syncHandler: ProxyHandler<SBConfig> = {
set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) { set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) {
Config.cachedSyncConfig[prop] = value; Config.cachedSyncConfig[prop] = value;
@@ -329,10 +341,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data; return obj[prop] || data;
}, },
deleteProperty(obj: SBConfig, prop: keyof SBConfig) { deleteProperty(obj: SBConfig, prop: keyof SBConfig) {
chrome.storage.sync.remove(<string> prop); chrome.storage.sync.remove(<string> prop);
return true; return true;
} }
@@ -354,10 +366,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data; return obj[prop] || data;
}, },
deleteProperty(obj: SBStorage, prop: keyof SBStorage) { deleteProperty(obj: SBStorage, prop: keyof SBStorage) {
chrome.storage.local.remove(<string> prop); chrome.storage.local.remove(<string> prop);
return true; return true;
} }
@@ -370,8 +382,20 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
} }
function forceSyncUpdate(prop: string): void { function forceSyncUpdate(prop: string): void {
const value = Config.cachedSyncConfig[prop];
if (prop === "unsubmittedSegments") {
// Early to be safe
if (JSON.stringify(value).length + prop.length > 8000) {
for (const key in value) {
if (!value[key] || value[key].length <= 0) {
delete value[key];
}
}
}
}
chrome.storage.sync.set({ chrome.storage.sync.set({
[prop]: Config.cachedSyncConfig[prop] [prop]: value
}); });
} }
@@ -381,7 +405,7 @@ function forceLocalUpdate(prop: string): void {
}); });
} }
async function fetchConfig(): Promise<void> { async function fetchConfig(): Promise<void> {
await Promise.all([new Promise<void>((resolve) => { await Promise.all([new Promise<void>((resolve) => {
chrome.storage.sync.get(null, function(items) { chrome.storage.sync.get(null, function(items) {
Config.cachedSyncConfig = <SBConfig> <unknown> items; Config.cachedSyncConfig = <SBConfig> <unknown> items;
@@ -389,7 +413,7 @@ async function fetchConfig(): Promise<void> {
}); });
}), new Promise<void>((resolve) => { }), new Promise<void>((resolve) => {
chrome.storage.local.get(null, function(items) { chrome.storage.local.get(null, function(items) {
Config.cachedLocalStorage = <SBStorage> <unknown> items; Config.cachedLocalStorage = <SBStorage> <unknown> items;
resolve(); resolve();
}); });
})]); })]);
@@ -433,9 +457,9 @@ function migrateOldSyncFormats(config: SBConfig) {
if (!config["autoSkipOnMusicVideosUpdate"]) { if (!config["autoSkipOnMusicVideosUpdate"]) {
config["autoSkipOnMusicVideosUpdate"] = true; config["autoSkipOnMusicVideosUpdate"] = true;
for (const selection of config.categorySelections) { for (const selection of config.categorySelections) {
if (selection.name === "music_offtopic" if (selection.name === "music_offtopic"
&& selection.option === CategorySkipOption.AutoSkip) { && selection.option === CategorySkipOption.AutoSkip) {
config.autoSkipOnMusicVideos = true; config.autoSkipOnMusicVideos = true;
break; break;
} }
@@ -526,6 +550,16 @@ function addDefaults() {
} }
} }
function resetToDefault() {
chrome.storage.sync.set({
...Config.syncDefaults,
userID: Config.config.userID,
minutesSaved: Config.config.minutesSaved,
skipCount: Config.config.skipCount,
sponsorTimesContributed: Config.config.sponsorTimesContributed
});
}
// Sync config // Sync config
setupConfig(); setupConfig();

View File

@@ -5,8 +5,6 @@ import { ContentContainer, Keybind } from "./types";
import Utils from "./utils"; import Utils from "./utils";
const utils = new Utils(); const utils = new Utils();
import runThePopup from "./popup";
import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar"; import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice"; import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SkipNoticeComponent from "./components/SkipNoticeComponent";
@@ -20,6 +18,7 @@ import { isSafari, keybindEquals } from "./utils/configUtils";
import { CategoryPill } from "./render/CategoryPill"; import { CategoryPill } from "./render/CategoryPill";
import { AnimationUtils } from "./utils/animationUtils"; import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
import { logDebug } from "./utils/logger";
// Hack to get the CSS loaded on permission-based sites (Invidious) // Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
@@ -226,6 +225,10 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
utils.addHiddenSegment(sponsorVideoID, request.UUID, request.type); utils.addHiddenSegment(sponsorVideoID, request.UUID, request.type);
updatePreviewBar(); updatePreviewBar();
break; break;
case "closePopup":
closeInfoMenu();
break;
} }
} }
@@ -280,6 +283,7 @@ function resetValues() {
switchingVideos = false; switchingVideos = false;
} else { } else {
switchingVideos = true; switchingVideos = true;
logDebug("Setting switching videos to true (reset data)");
} }
firstEvent = true; firstEvent = true;
@@ -324,9 +328,6 @@ async function videoIDChange(id) {
} }
} }
// Get new video info
// getVideoInfo(); // Seems to have been replaced
// Update whitelist data when the video data is loaded // Update whitelist data when the video data is loaded
whitelistCheck(); whitelistCheck();
@@ -453,6 +454,8 @@ function videoOnReadyListener(): void {
} }
function cancelSponsorSchedule(): void { function cancelSponsorSchedule(): void {
logDebug("Pausing skipping");
if (currentSkipSchedule !== null) { if (currentSkipSchedule !== null) {
clearTimeout(currentSkipSchedule); clearTimeout(currentSkipSchedule);
currentSkipSchedule = null; currentSkipSchedule = null;
@@ -475,21 +478,15 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
// Reset lastCheckVideoTime // Reset lastCheckVideoTime
lastCheckVideoTime = -1; lastCheckVideoTime = -1;
lastCheckTime = 0; lastCheckTime = 0;
console.warn("[SB] Ad playing, pausing skipping"); logDebug("[SB] Ad playing, pausing skipping");
return; return;
} }
logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`);
if (!video) return; if (!video) return;
if (currentTime === undefined || currentTime === null) { if (currentTime === undefined || currentTime === null) {
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ? currentTime = getVirtualTime();
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
if ((lastTimeFromWaitingEvent || !utils.isFirefox())
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6){
currentTime = virtualTime;
} else {
currentTime = video.currentTime;
}
} }
lastTimeFromWaitingEvent = null; lastTimeFromWaitingEvent = null;
@@ -514,6 +511,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments); const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
logDebug(`Ready to start skipping: ${skipInfo.index} at ${currentTime}`);
if (skipInfo.index === -1) return; if (skipInfo.index === -1) return;
const currentSkip = skipInfo.array[skipInfo.index]; const currentSkip = skipInfo.array[skipInfo.index];
@@ -540,7 +538,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
let forcedIncludeNonIntersectingSegments = true; let forcedIncludeNonIntersectingSegments = true;
if (incorrectVideoCheck(videoID, currentSkip)) return; if (incorrectVideoCheck(videoID, currentSkip)) return;
forceVideoTime ||= video.currentTime; forceVideoTime ||= Math.max(video.currentTime, getVirtualTime());
if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment))) { if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment))) {
if (forceVideoTime >= skipTime[0] && forceVideoTime < skipTime[1]) { if (forceVideoTime >= skipTime[0] && forceVideoTime < skipTime[1]) {
@@ -575,6 +573,8 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
// Use interval instead of timeout near the end to combat imprecise video time // Use interval instead of timeout near the end to combat imprecise video time
const startIntervalTime = performance.now(); const startIntervalTime = performance.now();
const startVideoTime = Math.max(currentTime, video.currentTime); const startVideoTime = Math.max(currentTime, video.currentTime);
logDebug(`Starting setInterval skipping ${video.currentTime} to skip at ${skipTime[0]}`);
currentSkipInterval = setInterval(() => { currentSkipInterval = setInterval(() => {
const intervalDuration = performance.now() - startIntervalTime; const intervalDuration = performance.now() - startIntervalTime;
if (intervalDuration >= delayTime || video.currentTime >= skipTime[0]) { if (intervalDuration >= delayTime || video.currentTime >= skipTime[0]) {
@@ -589,12 +589,26 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
} }
}, 1); }, 1);
} else { } else {
logDebug(`Starting timeout to skip ${video.currentTime} to skip at ${skipTime[0]}`);
// Schedule for right before to be more precise than normal timeout // Schedule for right before to be more precise than normal timeout
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 100)); currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 150));
} }
} }
} }
function getVirtualTime(): number {
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ?
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
if ((lastTimeFromWaitingEvent || !utils.isFirefox())
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6) {
return virtualTime;
} else {
return video.currentTime;
}
}
function inMuteSegment(currentTime: number): boolean { function inMuteSegment(currentTime: number): boolean {
const checkFunction = (segment) => segment.actionType === ActionType.Mute && segment.segment[0] <= currentTime && segment.segment[1] > currentTime; const checkFunction = (segment) => segment.actionType === ActionType.Mute && segment.segment[0] <= currentTime && segment.segment[1] > currentTime;
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction); return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
@@ -665,6 +679,8 @@ function setupVideoListeners() {
if (!Config.config.disableSkipping) { if (!Config.config.disableSkipping) {
switchingVideos = false; switchingVideos = false;
let startedWaiting = false;
video.addEventListener('play', () => { video.addEventListener('play', () => {
// If it is not the first event, then the only way to get to 0 is if there is a seek event // If it is not the first event, then the only way to get to 0 is if there is a seek event
// This check makes sure that changing the video resolution doesn't cause the extension to think it // This check makes sure that changing the video resolution doesn't cause the extension to think it
@@ -676,6 +692,8 @@ function setupVideoListeners() {
if (switchingVideos) { if (switchingVideos) {
switchingVideos = false; switchingVideos = false;
logDebug("Setting switching videos to false");
// If already segments loaded before video, retry to skip starting segments // If already segments loaded before video, retry to skip starting segments
if (sponsorTimes) startSkipScheduleCheckingForStartSponsors(); if (sponsorTimes) startSkipScheduleCheckingForStartSponsors();
} }
@@ -695,6 +713,12 @@ function setupVideoListeners() {
}); });
video.addEventListener('playing', () => { video.addEventListener('playing', () => {
updateVirtualTime(); updateVirtualTime();
if (startedWaiting) {
startedWaiting = false;
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)}`);
}
// Make sure it doesn't get double called with the play event // Make sure it doesn't get double called with the play event
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3 if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
@@ -737,9 +761,10 @@ function setupVideoListeners() {
}; };
video.addEventListener('pause', () => paused()); video.addEventListener('pause', () => paused());
video.addEventListener('waiting', () => { video.addEventListener('waiting', () => {
logDebug("[SB] Not skipping due to buffering");
startedWaiting = true;
paused(); paused();
console.warn("[SB] Not skipping due to buffering");
}); });
startSponsorSchedule(); startSponsorSchedule();
@@ -950,12 +975,10 @@ function startSkipScheduleCheckingForStartSponsors() {
// See if there are any starting sponsors // See if there are any starting sponsors
let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1; let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1;
let found = false; let found = false;
let startingSegment: SponsorTime = null;
for (const time of sponsorTimes) { for (const time of sponsorTimes) {
if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
&& time.actionType !== ActionType.Poi) { && time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0]; startingSegmentTime = time.segment[0];
startingSegment = time;
found = true; found = true;
break; break;
} }
@@ -965,7 +988,6 @@ function startSkipScheduleCheckingForStartSponsors() {
if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
&& time.actionType !== ActionType.Poi) { && time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0]; startingSegmentTime = time.segment[0];
startingSegment = time;
found = true; found = true;
break; break;
} }
@@ -1003,26 +1025,6 @@ function startSkipScheduleCheckingForStartSponsors() {
} }
} }
/**
* Get the video info for the current tab from YouTube
*
* TODO: Replace
*/
async function getVideoInfo(): Promise<void> {
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1&c=TVHTML5&cver=7.20190319");
if (result.ok) {
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
if (!decodedData) {
console.error("[SB] Failed at getting video info from YouTube.");
console.error("[SB] Data returned from YouTube: " + result.responseText);
return;
}
videoInfo = JSON.parse(decodedData);
}
}
function getYouTubeVideoID(document: Document): string | boolean { function getYouTubeVideoID(document: Document): string | boolean {
const url = document.URL; const url = document.URL;
// clips should never skip, going from clip to full video has no indications. // clips should never skip, going from clip to full video has no indications.
@@ -1161,7 +1163,7 @@ async function whitelistCheck() {
?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed ?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed
?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious ?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious
?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube ?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube
?.getAttribute("href")?.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/)[1]; ?.getAttribute("href")?.match(/\/(?:channel|c|user)\/(UC[a-zA-Z0-9_-]{22}|[a-zA-Z0-9_-]+)/)?.[1];
try { try {
await utils.wait(() => !!getChannelID(), 6000, 20); await utils.wait(() => !!getChannelID(), 6000, 20);
@@ -1169,14 +1171,12 @@ async function whitelistCheck() {
channelIDInfo = { channelIDInfo = {
status: ChannelIDStatus.Found, status: ChannelIDStatus.Found,
id: getChannelID().match(/^\/?([^\s/]+)/)[0] id: getChannelID().match(/^\/?([^\s/]+)/)[0]
} };
} catch (e) { } catch (e) {
channelIDInfo = { channelIDInfo = {
status: ChannelIDStatus.Failed, status: ChannelIDStatus.Failed,
id: null id: null
} };
return;
} }
//see if this is a whitelisted channel //see if this is a whitelisted channel
@@ -1727,73 +1727,30 @@ function openInfoMenu() {
//hide info button //hide info button
if (playerButtons.info) playerButtons.info.button.style.display = "none"; if (playerButtons.info) playerButtons.info.button.style.display = "none";
sendRequestToCustomServer('GET', chrome.extension.getURL("popup.html"), function(xmlhttp) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { const popup = document.createElement("div");
const popup = document.createElement("div"); popup.id = "sponsorBlockPopupContainer";
popup.id = "sponsorBlockPopupContainer";
const frame = document.createElement("iframe");
frame.width = "374";
frame.height = "500";
frame.addEventListener("load", () => frame.contentWindow.postMessage("", "*"));
frame.src = chrome.extension.getURL("popup.html");
popup.appendChild(frame);
let htmlData = xmlhttp.responseText; const parentNodes = document.querySelectorAll("#secondary");
// Hack to replace head data (title, favicon) let parentNode = null;
htmlData = htmlData.replace(/<head>[\S\s]*<\/head>/gi, ""); for (let i = 0; i < parentNodes.length; i++) {
// Hack to replace body and html tag with div if (parentNodes[i].firstElementChild !== null) {
htmlData = htmlData.replace(/<body/gi, "<div"); parentNode = parentNodes[i];
htmlData = htmlData.replace(/<\/body/gi, "</div");
htmlData = htmlData.replace(/<html/gi, "<div");
htmlData = htmlData.replace(/<\/html/gi, "</div");
popup.innerHTML = htmlData;
//close button
const closeButton = document.createElement("button");
const closeButtonIcon = document.createElement("img");
closeButtonIcon.src = chrome.extension.getURL("icons/close.png");
closeButtonIcon.width = 15;
closeButtonIcon.height = 15;
closeButton.appendChild(closeButtonIcon);
closeButton.setAttribute("title", chrome.i18n.getMessage("closePopup"));
closeButton.classList.add("sbCloseButton");
closeButton.addEventListener("click", closeInfoMenu);
//add the close button
popup.prepend(closeButton);
const parentNodes = document.querySelectorAll("#secondary");
let parentNode = null;
for (let i = 0; i < parentNodes.length; i++) {
if (parentNodes[i].firstElementChild !== null) {
parentNode = parentNodes[i];
}
}
if (parentNode == null) {
//old youtube theme
parentNode = document.getElementById("watch7-sidebar-contents");
}
//make the logo source not 404
//query selector must be used since getElementByID doesn't work on a node and this isn't added to the document yet
const logo = <HTMLImageElement> popup.querySelector("#sponsorBlockPopupLogo");
const settings = <HTMLImageElement> popup.querySelector("#sbPopupIconSettings");
const edit = <HTMLImageElement> popup.querySelector("#sbPopupIconEdit");
const copy = <HTMLImageElement> popup.querySelector("#sbPopupIconCopyUserID");
const check = <HTMLImageElement> popup.querySelector("#sbPopupIconCheck");
const refreshSegments = <HTMLImageElement> popup.querySelector("#refreshSegments");
const heart = <HTMLImageElement> popup.querySelector(".sbHeart");
const close = <HTMLImageElement> popup.querySelector("#sbCloseDonate");
logo.src = chrome.extension.getURL("icons/IconSponsorBlocker256px.png");
settings.src = chrome.extension.getURL("icons/settings.svg");
edit.src = chrome.extension.getURL("icons/pencil.svg");
copy.src = chrome.extension.getURL("icons/clipboard.svg");
check.src = chrome.extension.getURL("icons/check.svg");
heart.src = chrome.extension.getURL("icons/heart.svg");
close.src = chrome.extension.getURL("icons/close.png");
refreshSegments.src = chrome.extension.getURL("icons/refresh.svg");
parentNode.insertBefore(popup, parentNode.firstChild);
//run the popup init script
runThePopup(messageListener);
} }
}); }
if (parentNode == null) {
//old youtube theme
parentNode = document.getElementById("watch7-sidebar-contents");
}
parentNode.insertBefore(popup, parentNode.firstChild);
} }
function closeInfoMenu() { function closeInfoMenu() {
@@ -2154,25 +2111,6 @@ function addCSS() {
} }
} }
function sendRequestToCustomServer(type, fullAddress, callback) {
const xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, fullAddress, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
xmlhttp.onerror = function() {
callback(xmlhttp, true);
};
}
//submit this request
xmlhttp.send();
}
/** /**
* Update the isAdPlaying flag and hide preview bar/controls if ad is playing * Update the isAdPlaying flag and hide preview bar/controls if ad is playing
*/ */
@@ -2245,3 +2183,18 @@ function checkForPreloadedSegment() {
Config.forceSyncUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
} }
} }
// Register listener for URL change via Navigation API
const navigationApiAvailable = "navigation" in window;
if (navigationApiAvailable) {
// TODO: Remove type cast once type declarations are updated
(window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", () => videoIDChange(getYouTubeVideoID(document)));
}
// Record availability of Navigation API
utils.wait(() => Config.local !== null).then(() => {
if (Config.local.navigationApiAvailable !== navigationApiAvailable) {
Config.local.navigationApiAvailable = navigationApiAvailable;
Config.forceLocalUpdate("navigationApiAvailable");
}
});

View File

@@ -1,15 +1,15 @@
import Config from "./config"; import Config from "./config";
import { showDonationLink } from "./utils/configUtils"; import { showDonationLink } from "./utils/configUtils";
import Utils from "./utils"; import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); import { GenericUtils } from "./utils/genericUtils";
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
utils.localizeHtmlPage(); localizeHtmlPage();
await utils.wait(() => Config.config !== null); await GenericUtils.wait(() => Config.config !== null);
if (!Config.config.darkMode) { if (!Config.config.darkMode) {
document.documentElement.setAttribute("data-theme", "light"); document.documentElement.setAttribute("data-theme", "light");

View File

@@ -2,10 +2,7 @@ import Config from "../config";
import { SponsorTime } from "../types"; import { SponsorTime } from "../types";
import { getSkippingText } from "../utils/categoryUtils"; import { getSkippingText } from "../utils/categoryUtils";
import { keybindToString } from "../utils/configUtils"; import { keybindToString } from "../utils/configUtils";
import Utils from "../utils";
import { AnimationUtils } from "../utils/animationUtils"; import { AnimationUtils } from "../utils/animationUtils";
const utils = new Utils();
export interface SkipButtonControlBarProps { export interface SkipButtonControlBarProps {
skip: (segment: SponsorTime) => void; skip: (segment: SponsorTime) => void;
@@ -53,7 +50,7 @@ export class SkipButtonControlBar {
this.skipIcon.id = "sbSkipIconControlBarImage"; this.skipIcon.id = "sbSkipIconControlBarImage";
this.textContainer = document.createElement("div"); this.textContainer = document.createElement("div");
this.container.appendChild(this.skipIcon); this.container.appendChild(this.skipIcon);
this.container.appendChild(this.textContainer); this.container.appendChild(this.textContainer);
this.container.addEventListener("click", () => this.toggleSkip()); this.container.addEventListener("click", () => this.toggleSkip());
@@ -73,7 +70,7 @@ export class SkipButtonControlBar {
attachToPage(): void { attachToPage(): void {
const mountingContainer = this.getMountingContainer(); const mountingContainer = this.getMountingContainer();
this.chapterText = document.querySelector(".ytp-chapter-container"); this.chapterText = document.querySelector(".ytp-chapter-container");
if (mountingContainer && !mountingContainer.contains(this.container)) { if (mountingContainer && !mountingContainer.contains(this.container)) {
if (this.onMobileYouTube) { if (this.onMobileYouTube) {
mountingContainer.appendChild(this.container); mountingContainer.appendChild(this.container);
@@ -172,10 +169,10 @@ export class SkipButtonControlBar {
const overlay = document.getElementById("player-control-overlay"); const overlay = document.getElementById("player-control-overlay");
if (overlay && this.enabled) { if (overlay && this.enabled) {
if (overlay?.classList?.contains("pointer-events-off")) { if (overlay?.classList?.contains("fadein")) {
this.hideButton();
} else {
this.showButton(); this.showButton();
} else {
this.hideButton();
} }
} }
} }
@@ -220,4 +217,3 @@ export class SkipButtonControlBar {
this.container.style.left = this.left + "px"; this.container.style.left = this.left + "px";
} }
} }

View File

@@ -16,7 +16,8 @@ interface DefaultMessage {
| "getChannelID" | "getChannelID"
| "isChannelWhitelisted" | "isChannelWhitelisted"
| "submitTimes" | "submitTimes"
| "refreshSegments"; | "refreshSegments"
| "closePopup";
} }
interface BoolValueMessage { interface BoolValueMessage {

View File

@@ -12,13 +12,14 @@ import Utils from "./utils";
import CategoryChooser from "./render/CategoryChooser"; import CategoryChooser from "./render/CategoryChooser";
import KeybindComponent from "./components/KeybindComponent"; import KeybindComponent from "./components/KeybindComponent";
import { showDonationLink } from "./utils/configUtils"; import { showDonationLink } from "./utils/configUtils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); const utils = new Utils();
let embed = false; let embed = false;
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
utils.localizeHtmlPage(); localizeHtmlPage();
// selected tab // selected tab
if (location.hash != "") { if (location.hash != "") {
@@ -232,12 +233,22 @@ async function init() {
} }
case "button-press": { case "button-press": {
const actionButton = optionsElements[i].querySelector(".trigger-button"); const actionButton = optionsElements[i].querySelector(".trigger-button");
const confirmMessage = optionsElements[i].getAttribute("data-confirm-message");
switch(optionsElements[i].getAttribute("data-sync")) { actionButton.addEventListener("click", () => {
case "copyDebugInformation": if (confirmMessage !== null && !confirm(chrome.i18n.getMessage(confirmMessage))) {
actionButton.addEventListener("click", copyDebugOutputToClipboard); return;
break; }
} switch (optionsElements[i].getAttribute("data-sync")) {
case "copyDebugInformation":
copyDebugOutputToClipboard();
break;
case "resetToDefault":
Config.resetToDefault();
window.location.reload();
break;
}
});
break; break;
} }

View File

@@ -1,5 +1,6 @@
import Config from "./config"; import Config from "./config";
import Utils from "./utils"; import Utils from "./utils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); const utils = new Utils();
// This is needed, if Config is not imported before Utils, things break. // This is needed, if Config is not imported before Utils, things break.
@@ -9,7 +10,7 @@ Config.config;
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
utils.localizeHtmlPage(); localizeHtmlPage();
const domains = document.location.hash.replace("#", "").split(","); const domains = document.location.hash.replace("#", "").split(",");

View File

@@ -7,6 +7,7 @@ import { showDonationLink } from "./utils/configUtils";
import { AnimationUtils } from "./utils/animationUtils"; import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
import { shortCategoryName } from "./utils/categoryUtils"; import { shortCategoryName } from "./utils/categoryUtils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); const utils = new Utils();
interface MessageListener { interface MessageListener {
@@ -23,13 +24,15 @@ class MessageHandler {
sendMessage(id: number, request: Message, callback?) { sendMessage(id: number, request: Message, callback?) {
if (this.messageListener) { if (this.messageListener) {
this.messageListener(request, null, callback); this.messageListener(request, null, callback);
} else { } else if (chrome.tabs) {
chrome.tabs.sendMessage(id, request, callback); chrome.tabs.sendMessage(id, request, callback);
} else {
chrome.runtime.sendMessage({ message: "tabs", data: request }, callback);
} }
} }
query(config, callback) { query(config, callback) {
if (this.messageListener) { if (this.messageListener || !chrome.tabs) {
// Send back dummy info // Send back dummy info
callback([{ callback([{
url: document.URL, url: document.URL,
@@ -42,15 +45,17 @@ class MessageHandler {
} }
} }
// To prevent clickjacking
let allowPopup = window === window.top;
window.addEventListener("message", async (e) => {
if (e.source !== window.parent) return;
if (e.origin.endsWith('.youtube.com')) return allowPopup = true;
});
//make this a function to allow this to run on the content page //make this a function to allow this to run on the content page
async function runThePopup(messageListener?: MessageListener): Promise<void> { async function runThePopup(messageListener?: MessageListener): Promise<void> {
const messageHandler = new MessageHandler(messageListener); const messageHandler = new MessageHandler(messageListener);
localizeHtmlPage();
utils.localizeHtmlPage();
await utils.wait(() => Config.config !== null);
type InputPageElements = { type InputPageElements = {
whitelistToggle?: HTMLInputElement, whitelistToggle?: HTMLInputElement,
@@ -59,6 +64,21 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}; };
type PageElements = { [key: string]: HTMLElement } & InputPageElements type PageElements = { [key: string]: HTMLElement } & InputPageElements
/** If true, the content script is in the process of creating a new segment. */
let creatingSegment = false;
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
//current video ID of this tab
let currentVideoID = null;
enum SegmentTab {
Segments,
Chapters
}
let segmentTab = SegmentTab.Segments;
const PageElements: PageElements = {}; const PageElements: PageElements = {};
[ [
@@ -93,7 +113,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
// Username // Username
"setUsernameContainer", "setUsernameContainer",
"setUsernameButton", "setUsernameButton",
"setUsernameStatusContainer", "setUsernameStatus",
"setUsernameStatus", "setUsernameStatus",
"setUsername", "setUsername",
"usernameInput", "usernameInput",
@@ -101,7 +121,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"submitUsername", "submitUsername",
"sbPopupIconCopyUserID", "sbPopupIconCopyUserID",
// More // More
"submissionSection", "submissionHint",
"mainControls", "mainControls",
"loadingIndicator", "loadingIndicator",
"videoFound", "videoFound",
@@ -116,9 +136,24 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"sponsorTimesDonateContainer", "sponsorTimesDonateContainer",
"sbConsiderDonateLink", "sbConsiderDonateLink",
"sbCloseDonate", "sbCloseDonate",
"sbBetaServerWarning" "sbBetaServerWarning",
"sbCloseButton"
].forEach(id => PageElements[id] = document.getElementById(id)); ].forEach(id => PageElements[id] = document.getElementById(id));
getSegmentsFromContentScript(false);
await utils.wait(() => Config.config !== null && allowPopup, 5000, 5);
document.querySelector("body").style.removeProperty("visibility");
PageElements.sbCloseButton.addEventListener("click", () => {
sendTabMessage({
message: "closePopup"
});
});
if (window !== window.top) {
PageElements.sbCloseButton.classList.remove("hidden");
}
// Hide donate button if wanted (Safari, or user choice) // Hide donate button if wanted (Safari, or user choice)
if (!showDonationLink()) { if (!showDonationLink()) {
PageElements.sbDonate.style.display = "none"; PageElements.sbDonate.style.display = "none";
@@ -155,21 +190,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments); PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => navigator.clipboard.writeText(await utils.getHash(Config.config.userID))); PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => navigator.clipboard.writeText(await utils.getHash(Config.config.userID)));
/** If true, the content script is in the process of creating a new segment. */
let creatingSegment = false;
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
//current video ID of this tab
let currentVideoID = null;
enum SegmentTab {
Segments,
Chapters
}
let segmentTab = SegmentTab.Segments;
//show proper disable skipping button //show proper disable skipping button
const disableSkipping = Config.config.disableSkipping; const disableSkipping = Config.config.disableSkipping;
if (disableSkipping != undefined && disableSkipping) { if (disableSkipping != undefined && disableSkipping) {
@@ -249,8 +269,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
// Must be delayed so it only happens once loaded // Must be delayed so it only happens once loaded
setTimeout(() => PageElements.sponsorblockPopup.classList.remove("preload"), 250); setTimeout(() => PageElements.sponsorblockPopup.classList.remove("preload"), 250);
getSegmentsFromContentScript(false);
PageElements.issueReporterTabSegments.addEventListener("click", () => { PageElements.issueReporterTabSegments.addEventListener("click", () => {
PageElements.issueReporterTabSegments.classList.add("sbSelected"); PageElements.issueReporterTabSegments.classList.add("sbSelected");
PageElements.issueReporterTabChapters.classList.remove("sbSelected"); PageElements.issueReporterTabChapters.classList.remove("sbSelected");
@@ -298,13 +316,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}); });
} }
function loadTabData(tabs, updating: boolean): void { async function loadTabData(tabs, updating: boolean): Promise<void> {
if (!currentVideoID) { if (!currentVideoID) {
//this isn't a YouTube video then //this isn't a YouTube video then
displayNoVideo(); displayNoVideo();
return; return;
} }
await utils.wait(() => Config.config !== null, 5000, 10);
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? []; sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
updateSegmentEditingUI(); updateSegmentEditingUI();
@@ -482,9 +501,17 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
} }
segmentTimeFromToNode.style.margin = "5px"; segmentTimeFromToNode.style.margin = "5px";
// for inline-styling purposes
const labelContainer = document.createElement("div");
if (actionType !== ActionType.Chapter) labelContainer.appendChild(categoryColorCircle);
if (actionType !== ActionType.Chapter) segmentSummary.appendChild(categoryColorCircle); const span = document.createElement('span');
segmentSummary.appendChild(textNode); span.className = "summaryLabel";
span.appendChild(textNode);
labelContainer.appendChild(span);
segmentSummary.appendChild(labelContainer);
segmentSummary.appendChild(segmentTimeFromToNode); segmentSummary.appendChild(segmentTimeFromToNode);
const votingButtons = document.createElement("details"); const votingButtons = document.createElement("details");
@@ -493,7 +520,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//thumbs up and down buttons //thumbs up and down buttons
const voteButtonsContainer = document.createElement("div"); const voteButtonsContainer = document.createElement("div");
voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID; voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID;
voteButtonsContainer.setAttribute("align", "center"); voteButtonsContainer.classList.add("sbVoteButtonsContainer");
const upvoteButton = document.createElement("img"); const upvoteButton = document.createElement("img");
upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID; upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID;
@@ -618,7 +645,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
function updateSegmentEditingUI() { function updateSegmentEditingUI() {
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart"); PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart");
PageElements.submissionSection.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none"; PageElements.submitTimes.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
PageElements.submissionHint.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
} }
//make the options div visible //make the options div visible
@@ -634,6 +662,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
chrome.runtime.sendMessage({ "message": "openHelp" }); chrome.runtime.sendMessage({ "message": "openHelp" });
} }
function sendTabMessage(data: Message): Promise<unknown> {
return new Promise((resolve) => {
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
data,
(response) => resolve(response)
);
}
);
});
}
//make the options username setting option visible //make the options username setting option visible
function setUsernameButton() { function setUsernameButton() {
PageElements.usernameInput.value = PageElements.usernameValue.innerText; PageElements.usernameInput.value = PageElements.usernameValue.innerText;
@@ -645,7 +689,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.setUsername.style.display = "flex"; PageElements.setUsername.style.display = "flex";
PageElements.setUsername.classList.add("SBExpanded"); PageElements.setUsername.classList.add("SBExpanded");
PageElements.setUsernameStatusContainer.style.display = "none"; PageElements.setUsernameStatus.style.display = "none";
PageElements.sponsorTimesContributionsContainer.classList.add("hidden"); PageElements.sponsorTimesContributionsContainer.classList.add("hidden");
} }
@@ -653,7 +697,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//submit the new username //submit the new username
function submitUsername() { function submitUsername() {
//add loading indicator //add loading indicator
PageElements.setUsernameStatusContainer.style.display = "unset"; PageElements.setUsernameStatus.style.display = "unset";
PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("Loading"); PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("Loading");
utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) { utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) {
@@ -666,7 +710,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.setUsername.classList.remove("SBExpanded"); PageElements.setUsername.classList.remove("SBExpanded");
PageElements.usernameValue.innerText = PageElements.usernameInput.value; PageElements.usernameValue.innerText = PageElements.usernameInput.value;
PageElements.setUsernameStatusContainer.style.display = "none"; PageElements.setUsernameStatus.style.display = "none";
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden"); PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
} else { } else {
@@ -911,9 +955,4 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//end of function //end of function
} }
if (chrome.tabs != undefined) { runThePopup();
//this means it is actually opened in the popup
runThePopup();
}
export default runThePopup;

View File

@@ -13,12 +13,19 @@ export interface ButtonListener {
listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
} }
export interface TextBox {
icon: string,
text: string
}
export interface NoticeOptions { export interface NoticeOptions {
title: string, title: string,
textBoxes?: string[], textBoxes?: TextBox[],
buttons?: ButtonListener[], buttons?: ButtonListener[],
fadeIn?: boolean, fadeIn?: boolean,
timed?: boolean timed?: boolean
style?: React.CSSProperties;
extraClass?: string;
} }
export default class GenericNotice { export default class GenericNotice {
@@ -27,9 +34,11 @@ export default class GenericNotice {
noticeElement: HTMLDivElement; noticeElement: HTMLDivElement;
noticeRef: React.MutableRefObject<NoticeComponent>; noticeRef: React.MutableRefObject<NoticeComponent>;
idSuffix: string;
constructor(contentContainer: ContentContainer, idSuffix: string, options: NoticeOptions) { constructor(contentContainer: ContentContainer, idSuffix: string, options: NoticeOptions) {
this.noticeRef = React.createRef(); this.noticeRef = React.createRef();
this.idSuffix = idSuffix;
this.contentContainer = contentContainer; this.contentContainer = contentContainer;
@@ -40,39 +49,47 @@ export default class GenericNotice {
referenceNode.prepend(this.noticeElement); referenceNode.prepend(this.noticeElement);
this.update(options);
}
update(options: NoticeOptions): void {
ReactDOM.render( ReactDOM.render(
<NoticeComponent <NoticeComponent
noticeTitle={options.title} noticeTitle={options.title}
idSuffix={idSuffix} idSuffix={this.idSuffix}
fadeIn={options.fadeIn ?? true} fadeIn={options.fadeIn ?? true}
timed={options.timed ?? true} timed={options.timed ?? true}
ref={this.noticeRef} ref={this.noticeRef}
style={options.style}
extraClass={options.extraClass}
closeListener={() => this.close()} > closeListener={() => this.close()} >
{this.getMessageBox(idSuffix, options.textBoxes)} {this.getMessageBox(this.idSuffix, options.textBoxes)}
<tr id={"sponsorSkipNoticeSpacer" + idSuffix} <tr id={"sponsorSkipNoticeSpacer" + this.idSuffix}
className="sponsorBlockSpacer"> className="sponsorBlockSpacer">
</tr> </tr>
<div className="sponsorSkipNoticeRightSection" <tr className="sponsorSkipNoticeRightSection"
style={{position: "relative"}}> style={{position: "relative"}}>
<td>
{this.getButtons(options.buttons)} {this.getButtons(options.buttons)}
</div> </td>
</tr>
</NoticeComponent>, </NoticeComponent>,
this.noticeElement this.noticeElement
); );
} }
getMessageBox(idSuffix: string, textBoxes: string[]): JSX.Element[] { getMessageBox(idSuffix: string, textBoxes: TextBox[]): JSX.Element[] {
if (textBoxes) { if (textBoxes) {
const result = []; const result = [];
for (let i = 0; i < textBoxes.length; i++) { for (let i = 0; i < textBoxes.length; i++) {
result.push( result.push(
<NoticeTextSelectionComponent idSuffix={idSuffix} <NoticeTextSelectionComponent idSuffix={idSuffix}
key={i} key={i}
text={textBoxes[i]} /> icon={textBoxes[i].icon}
text={textBoxes[i].text} />
) )
} }

View File

@@ -256,31 +256,6 @@ export default class Utils {
} }
} }
localizeHtmlPage(): void {
//Localize by replacing __MSG_***__ meta tags
const localizedMessage = this.getLocalizedMessage(document.title);
if (localizedMessage) document.title = localizedMessage;
const objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
for (let j = 0; j < objects.length; j++) {
const obj = objects[j];
const localizedMessage = this.getLocalizedMessage(obj.innerHTML.toString());
if (localizedMessage) obj.innerHTML = localizedMessage;
}
}
getLocalizedMessage(text: string): string | false {
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
return v1 ? chrome.i18n.getMessage(v1).replace(/</g, "&#60;")
.replace(/"/g, "&quot;").replace(/\n/g, "<br/>") : "";
});
if(valNewH != text) {
return valNewH;
} else {
return false;
}
}
/** /**
* @returns {String[]} Domains in regex form * @returns {String[]} Domains in regex form
*/ */

141
src/utils/constants.ts Normal file
View File

@@ -0,0 +1,141 @@
import { TextBox } from "../render/GenericNotice";
import { Category } from "../types";
export function getGuidelineInfo(category: Category): TextBox[] {
switch (category) {
case "sponsor":
return [{
icon: "icons/money.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "selfpromo":
return [{
icon: "icons/money.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/campaign.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "exclusive_access":
return [{
icon: "icons/money.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}];
case "interaction":
return [{
icon: "icons/lightbulb.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/lightbulb.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "intro":
return [{
icon: "icons/check-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "outro":
return [{
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "preview":
return [{
icon: "icons/check-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/check-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "filler":
return [{
icon: "icons/stopwatch.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/stopwatch.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/close-smaller.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
}, {
icon: "icons/segway.png",
text: chrome.i18n.getMessage(`generic_guideline1`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "music_offtopic":
return [{
icon: "icons/music-note.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/music-note.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/right-arrow.svg",
text: chrome.i18n.getMessage(`generic_guideline2`)
}];
case "poi_highlight":
return [{
icon: "icons/star.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline1`)
}, {
icon: "icons/bolt.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline2`)
}, {
icon: "icons/bolt.svg",
text: chrome.i18n.getMessage(`category_${category}_guideline3`)
}];
}
}

12
src/utils/logger.ts Normal file
View File

@@ -0,0 +1,12 @@
window["SBLogs"] = {
debug: [],
warn: []
};
export function logDebug(message: string) {
window["SBLogs"].debug.push(`[${new Date().toISOString()}] ${message}`);
}
export function logWarn(message: string) {
window["SBLogs"].warn.push(`[${new Date().toISOString()}] ${message}`);
}

View File

@@ -102,4 +102,27 @@ export function getExistingChapters(currentVideoID: VideoID, duration: number):
} }
return chapters; return chapters;
}
export function localizeHtmlPage(): void {
//Localize by replacing __MSG_***__ meta tags
const localizedTitle = getLocalizedMessage(document.title);
if (localizedTitle) document.title = localizedTitle;
const body = document.querySelector(".sponsorBlockPageBody");
const localizedMessage = getLocalizedMessage(body.innerHTML.toString());
if (localizedMessage) body.innerHTML = localizedMessage;
}
export function getLocalizedMessage(text: string): string | false {
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
return v1 ? chrome.i18n.getMessage(v1).replace(/</g, "&#60;")
.replace(/"/g, "&quot;").replace(/\n/g, "<br/>") : "";
});
if (valNewH != text) {
return valNewH;
} else {
return false;
}
} }

View File

@@ -1,12 +1,15 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const webpack = require("webpack"); import webpack from "webpack"
const path = require('path'); import path from "path"
const CopyPlugin = require('copy-webpack-plugin'); import { fileURLToPath } from "url"
const BuildManifest = require('./webpack.manifest'); import CopyPlugin from "copy-webpack-plugin"
const srcDir = '../src/'; import BuildManifest from "./webpack.manifest.cjs";
const fs = require("fs"); const srcDir = "../src/";
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); import fs from "fs";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const edgeLanguages = [ const edgeLanguages = [
"de", "de",
@@ -24,7 +27,7 @@ const edgeLanguages = [
"zh_CN" "zh_CN"
] ]
module.exports = env => ({ export default env => ({
entry: { entry: {
popup: path.join(__dirname, srcDir + 'popup.ts'), popup: path.join(__dirname, srcDir + 'popup.ts'),
background: path.join(__dirname, srcDir + 'background.ts'), background: path.join(__dirname, srcDir + 'background.ts'),

View File

@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */ import { merge } from "webpack-merge";
const merge = require('webpack-merge'); import common from './webpack.common.js';
const common = require('./webpack.common.js');
module.exports = env => merge(common(env), { export default env => merge(common(env), {
devtool: 'inline-source-map', devtool: 'inline-source-map',
mode: 'development' mode: 'development'
}); });

View File

@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */ import { merge } from "webpack-merge";
const merge = require('webpack-merge'); import common from './webpack.common.js';
const common = require('./webpack.common.js');
module.exports = env => { export default env => {
let mode = "production"; let mode = "production";
env.mode = mode; env.mode = mode;