mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 19:47:04 +03:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1648e104e1 | ||
|
|
c4d7c4511e | ||
|
|
cd78c46ef8 | ||
|
|
758b6f18db | ||
|
|
6d05b2a849 | ||
|
|
4729268083 | ||
|
|
b7a574fc16 | ||
|
|
c8cbd893f7 | ||
|
|
11b01fd3dd | ||
|
|
85e3d3bc18 | ||
|
|
47ea8cd017 | ||
|
|
61e78eb668 | ||
|
|
5ebd44c0c7 | ||
|
|
9888dcc323 | ||
|
|
49a166a6b2 | ||
|
|
2bd1271575 | ||
|
|
5d62b11a6d | ||
|
|
f4cac58322 | ||
|
|
4a532e751c | ||
|
|
4a3b33cb85 | ||
|
|
78e9f41854 | ||
|
|
48cfee57b7 | ||
|
|
b2ef9e5d6e | ||
|
|
c42ebce6e3 | ||
|
|
bc1d6006eb | ||
|
|
727d925879 | ||
|
|
5d48d9ac74 | ||
|
|
83ea183f58 | ||
|
|
a098858035 | ||
|
|
81e85c19ae | ||
|
|
fda4a03541 | ||
|
|
55c84662c0 | ||
|
|
5c9e06468e | ||
|
|
a3d38c57d7 | ||
|
|
7ec09dd385 | ||
|
|
bb7f069254 | ||
|
|
61fc1d2ed3 | ||
|
|
dabc63af73 | ||
|
|
08181c1d5f | ||
|
|
03cd1b535b | ||
|
|
8cc3843ada | ||
|
|
c4701092f4 | ||
|
|
63ef9b44c7 |
@@ -23,7 +23,8 @@
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"no-self-assign": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"react/prop-types": [2, { "ignore": ["children"] }]
|
||||
"react/prop-types": [2, { "ignore": ["children"] }],
|
||||
"@typescript-eslint/member-delimiter-style": "warn"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "SponsorBlock",
|
||||
"version": "5.0.7",
|
||||
"version": "5.1.0",
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_Description__",
|
||||
"homepage_url": "https://sponsor.ajay.app",
|
||||
|
||||
File diff suppressed because one or more lines are too long
4136
package-lock.json
generated
4136
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -4,36 +4,36 @@
|
||||
"description": "",
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.193",
|
||||
"@types/chrome": "^0.0.197",
|
||||
"@types/firefox-webext-browser": "^94.0.1",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/react": "^17.0.47",
|
||||
"@types/react-dom": "^17.0.17",
|
||||
"@types/selenium-webdriver": "^4.1.2",
|
||||
"@types/wicg-mediasession": "^1.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"chromedriver": "^103.0.0",
|
||||
"concurrently": "^7.3.0",
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/selenium-webdriver": "^4.1.5",
|
||||
"@types/wicg-mediasession": "^1.1.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.39.0",
|
||||
"@typescript-eslint/parser": "^5.39.0",
|
||||
"chromedriver": "^106.0.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.13",
|
||||
"jest": "^28.1.3",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"jest": "^29.1.2",
|
||||
"jest-environment-jsdom": "^29.1.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"schema-utils": "^4.0.0",
|
||||
"selenium-webdriver": "^4.3.1",
|
||||
"selenium-webdriver": "^4.5.0",
|
||||
"speed-measure-webpack-plugin": "^1.5.0",
|
||||
"ts-jest": "^28.0.7",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-loader": "^9.4.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.7",
|
||||
"web-ext": "^7.1.1",
|
||||
"typescript": "4.8",
|
||||
"web-ext": "^7.2.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
"Segments": {
|
||||
"message": "أجزاء"
|
||||
},
|
||||
"Chapters": {
|
||||
"message": "الفصول"
|
||||
},
|
||||
"upvoteButtonInfo": {
|
||||
"message": "التصويت على هذا الإرسال"
|
||||
},
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
"message": "Negatiivisesti äänestämäsi osiot pysyvät piilotettuina myös päivityksen jälkeen"
|
||||
},
|
||||
"trackDownvotesWarning": {
|
||||
"message": "Varoitus: Tämän käytöstä poisto poistaa kaikki aiemmin tallennetut negatiiviset äänet"
|
||||
"message": "Varoitus: Tämän poistaminen käytöstä poistaa kaikki aiemmin tallennetut alaäänet"
|
||||
},
|
||||
"enableQueryByHashPrefix": {
|
||||
"message": "Kysely tiiviste-etuliittellä"
|
||||
@@ -1242,6 +1242,6 @@
|
||||
"description": "Header of the unsubmitted segments list"
|
||||
},
|
||||
"exportSegmentsAsURL": {
|
||||
"message": "Jaa web-osoitteena"
|
||||
"message": "Jaa URL-osoitteena"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
"Segments": {
|
||||
"message": "מקטעים"
|
||||
},
|
||||
"SegmentsCap": {
|
||||
"message": "מקטעים"
|
||||
},
|
||||
"Chapters": {
|
||||
"message": "פרקים"
|
||||
},
|
||||
"upvoteButtonInfo": {
|
||||
"message": "הצבע לדיווח הזה"
|
||||
},
|
||||
@@ -211,6 +217,14 @@
|
||||
"message": "קוד מקור",
|
||||
"description": "Used on Firefox Store Page"
|
||||
},
|
||||
"nextChapterKeybind": {
|
||||
"message": "הפרק הבא",
|
||||
"description": "Keybind label"
|
||||
},
|
||||
"yourWork": {
|
||||
"message": "העבודה שלך",
|
||||
"description": "Used to describe the section that will show you the statistics from your submissions."
|
||||
},
|
||||
"errorCode": {
|
||||
"message": "קוד שגיאה: "
|
||||
},
|
||||
|
||||
@@ -223,13 +223,13 @@
|
||||
"message": "YouTube 탐색 바에서 삭제 버튼 표시"
|
||||
},
|
||||
"enableViewTracking": {
|
||||
"message": "건너뛴 횟수 추적 활성화"
|
||||
"message": "건너뛴 횟수 추적 사용"
|
||||
},
|
||||
"whatViewTracking": {
|
||||
"message": "이 기능으로 건너뛴 구간을 추적해서 사용자가 제출한 내용이 다른 분께 얼마나 도움이 되는지 알려주고 잘못된 구간이 데이터베이스에 들어가지 않도록 추천과 함께 분석에 사용해요. 이 확장 프로그램이 구간을 건너뛸 때마다 서버에 메시지를 보낼 거예요. 조회수가 정확하기 위해서는 이 설정을 변경하지 않기를 바라요. :)"
|
||||
},
|
||||
"enableViewTrackingInPrivate": {
|
||||
"message": "시크릿/사생활 보호 탭에서 건너뛴 횟수 추적 활성화"
|
||||
"message": "시크릿/사생활 보호 탭에서 건너뛴 횟수 추적 사용"
|
||||
},
|
||||
"enableTrackDownvotes": {
|
||||
"message": "비추천한 구간 저장"
|
||||
@@ -238,7 +238,7 @@
|
||||
"message": "비추천한 구간을 새로고침 이후에도 계속 숨겨요"
|
||||
},
|
||||
"trackDownvotesWarning": {
|
||||
"message": "경고: 비활성화하면 이전에 저장된 비추천 구간이 삭제돼요"
|
||||
"message": "경고: 사용하지 않으면 이전에 저장된 비추천 구간이 삭제돼요"
|
||||
},
|
||||
"enableQueryByHashPrefix": {
|
||||
"message": "해시 접두사로 요청 전송"
|
||||
@@ -377,16 +377,16 @@
|
||||
"description": "Used for skipping to things (Skipped to Highlight)"
|
||||
},
|
||||
"disableAutoSkip": {
|
||||
"message": "자동 건너뛰기 비활성화"
|
||||
"message": "자동 건너뛰기 사용 안 함"
|
||||
},
|
||||
"enableAutoSkip": {
|
||||
"message": "자동 건너뛰기 활성화"
|
||||
"message": "자동 건너뛰기 사용"
|
||||
},
|
||||
"audioNotification": {
|
||||
"message": "건너뛸 때 소리 재생"
|
||||
},
|
||||
"audioNotificationDescription": {
|
||||
"message": "구간을 건너뛸 때마다 소리를 재생해요. 자동 건너뛰기가 비활성화된 경우, 아무 소리도 재생되지 않아요."
|
||||
"message": "구간을 건너뛸 때마다 소리를 재생해요. 자동 건너뛰기를 사용하지 않는 경우, 아무 소리도 재생되지 않아요."
|
||||
},
|
||||
"showTimeWithSkips": {
|
||||
"message": "건너뛰기로 제외된 시간 표시"
|
||||
@@ -446,7 +446,7 @@
|
||||
"message": "지원되는 사이트: "
|
||||
},
|
||||
"optionsInfo": {
|
||||
"message": "Invidious 지원 활성화, 자동 건너뛰기 비활성화, 버튼 숨기기 등이 있어요."
|
||||
"message": "Invidious 지원 활성화, 자동 건너뛰기 사용 안 함, 버튼 숨기기 등이 있어요."
|
||||
},
|
||||
"addInvidiousInstance": {
|
||||
"message": "제3자 클라이언트 인스턴스 추가"
|
||||
@@ -458,7 +458,7 @@
|
||||
"message": "추가"
|
||||
},
|
||||
"addInvidiousInstanceError": {
|
||||
"message": "잘못된 도메인이에요. 도메인 부분만 포함해야 해요. 예: invious.ajay.app"
|
||||
"message": "잘못된 도메인이에요. 도메인 부분만 포함해야 해요. 예시: invious.ajay.app"
|
||||
},
|
||||
"resetInvidiousInstance": {
|
||||
"message": "Invidious 인스턴스 목록 초기화"
|
||||
@@ -830,7 +830,7 @@
|
||||
"message": "카테고리 선택"
|
||||
},
|
||||
"enableThisCategoryFirst": {
|
||||
"message": "\"{0}\" 카테고리의 구간을 제출하려면, 설정에서 활성화를 해주셔야 해요. 바로 설정 창으로 이동하실 거예요.",
|
||||
"message": "\"{0}\" 카테고리의 구간을 제출하려면, 설정에서 사용을 해주셔야 해요. 바로 설정 창으로 이동하실 거예요.",
|
||||
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
|
||||
},
|
||||
"poiOnlyOneSegment": {
|
||||
@@ -872,7 +872,7 @@
|
||||
"message": "권한 요청에 실패했어요. 거부를 누르셨나요?"
|
||||
},
|
||||
"adblockerIssueWhitelist": {
|
||||
"message": "이 문제를 해결할 수 없는 경우 SponsorBlock이 이 동영상의 채널 정보를 찾을 수 없는 것일 수 있으니, '건너뛰기 전 채널 강제 확인' 설정을 비활성화해주세요."
|
||||
"message": "이 문제를 해결할 수 없는 경우 SponsorBlock이 이 동영상의 채널 정보를 찾을 수 없는 것일 수 있으니, '건너뛰기 전 채널 강제 확인' 설정을 사용하지 않아야 해요"
|
||||
},
|
||||
"forceChannelCheck": {
|
||||
"message": "건너뛰기 전 채널 강제 확인"
|
||||
@@ -897,7 +897,7 @@
|
||||
"message": "카테고리 변경"
|
||||
},
|
||||
"nonMusicCategoryOnMusic": {
|
||||
"message": "이 동영상은 음악 동영상으로 분류되어 있어요. 동영상에 스폰서 광고 구간이 있나요? \"음악이 아닌 구간\"으로 지정된 카테고리인 경우, 확장 프로그램 설정을 열어 이 카테고리를 활성화하세요. 그리고, 이 구간을 \"스폰서 광고 구간\" 대신 \"음악이 아닌 구간\"으로 지정하세요. 혼동된다면 가이드라인을 읽어주세요."
|
||||
"message": "이 동영상은 음악 동영상으로 분류되어 있어요. 동영상에 스폰서 광고 구간이 있나요? \"음악이 아닌 구간\"으로 지정된 카테고리인 경우, 확장 프로그램 설정을 열어 이 카테고리를 사용하세요. 그리고, 이 구간을 \"스폰서 광고 구간\" 대신 \"음악이 아닌 구간\"으로 지정하세요. 혼동된다면 가이드라인을 읽어주세요."
|
||||
},
|
||||
"multipleSegments": {
|
||||
"message": "여러 구간"
|
||||
@@ -972,7 +972,7 @@
|
||||
"message": "아래 설정을 확인해 보세요"
|
||||
},
|
||||
"helpPageFeatureDisclaimer": {
|
||||
"message": "기본값으로 많은 기능이 비활성화되어 있어요. 인트로, 아웃트로 같은 부분을 건너뛰고 싶으시다면 아래 설정을 켜야 해요. 또한 UI 요소를 숨기거나 표시할 수 있답니다."
|
||||
"message": "기본값으로 많은 기능이 사용되지 않아요. 인트로, 아웃트로 같은 부분을 건너뛰고 싶으시다면 아래 설정을 사용해야 해요. 또한 UI 요소를 숨기거나 표시할 수 있답니다."
|
||||
},
|
||||
"helpPageHowSkippingWorks": {
|
||||
"message": "건너뛰기가 작동하는 방법"
|
||||
@@ -1002,7 +1002,7 @@
|
||||
"message": "이건 너무 느린 거 같아요"
|
||||
},
|
||||
"helpPageTooSlow1": {
|
||||
"message": "원하는 경우 단축키를 이용할 수 있어요. 쌍반점(세미콜론) 키를 눌러 스폰서 광고 구간의 시점/종점을 설정할 수 있으며 작은따옴표 키를 눌러 구간을 제출할 수 있답니다. 언제든지 설정에서 변경할 수 있어요. QWERTY 자판을 사용하지 않는 경우, 단축키를 변경해야 할 수도 있어요."
|
||||
"message": "원하는 경우 단축키를 사용할 수 있어요. 쌍반점(세미콜론) 키를 눌러 스폰서 광고 구간의 시점/종점을 설정할 수 있으며 작은따옴표 키를 눌러 구간을 제출할 수 있답니다. 언제든지 설정에서 변경할 수 있어요. QWERTY 자판을 사용하지 않는 경우, 단축키를 변경해야 할 수도 있어요."
|
||||
},
|
||||
"helpPageCopyOfDatabase": {
|
||||
"message": "데이터베이스의 복사본을 구할 수 있나요? 개발자분께 무슨 일이 생기면 어떻게 되는 거죠?"
|
||||
@@ -1011,7 +1011,7 @@
|
||||
"message": "데이터베이스는 여기에서 확인하실 수 있어요:"
|
||||
},
|
||||
"helpPageCopyOfDatabase2": {
|
||||
"message": "또한 소스 코드는 자유롭게 이용할 수 있어요. 따라서 데이터베이스에 무슨 일이 생기더라도, 제출된 구간이 사라지는 일은 없을 거예요."
|
||||
"message": "또한 소스 코드는 자유롭게 사용할 수 있어요. 따라서 데이터베이스에 무슨 일이 생기더라도, 제출된 구간이 사라지는 일은 없을 거예요."
|
||||
},
|
||||
"helpPageNews": {
|
||||
"message": "새로운 변경 사항은 어디에서 확인하나요?"
|
||||
@@ -1138,7 +1138,7 @@
|
||||
"message": "라이선스 키가 유효하지 않아요"
|
||||
},
|
||||
"hideUpsells": {
|
||||
"message": "추가 결제 없이는 숨김 설정을 이용하실 수 없어요"
|
||||
"message": "(추가 결제 없이는 숨김 설정을 사용할 수 없어요)"
|
||||
},
|
||||
"chooseACountry": {
|
||||
"message": "국가 선택"
|
||||
@@ -1176,10 +1176,10 @@
|
||||
"message": "라이선스 키를 입력하세요"
|
||||
},
|
||||
"chaptersPage1": {
|
||||
"message": "SponsorBlock 사용자 참여 챕터 기능은 라이선스 결제 사용자나, 이전 기여를 통해 접근을 허가받은 사용자만 이용할 수 있어요"
|
||||
"message": "SponsorBlock 사용자 참여 챕터 기능은 라이선스 결제 사용자나, 이전 기여를 통해 접근을 허가받은 사용자만 사용할 수 있어요"
|
||||
},
|
||||
"chaptersPage2": {
|
||||
"message": "참고: 여전히 챕터 제출 권한은 산정된 평판만을 바탕으로 부여돼요. 라이선스를 결제하면 다른 분이 제출한 챕터를 확인하는 기능만 추가로 이용할 수 있어요",
|
||||
"message": "참고: 여전히 챕터 제출 권한은 산정된 평판만을 바탕으로 부여돼요. 라이선스를 결제하면 다른 분이 제출한 챕터를 확인하는 기능만 추가로 사용할 수 있어요",
|
||||
"description": "On the chapters page for getting access to the paid chapters feature"
|
||||
},
|
||||
"chapterNewFeature": {
|
||||
@@ -1187,7 +1187,7 @@
|
||||
"description": "After the comma, a list of chapters for this video will appear"
|
||||
},
|
||||
"chapterNewFeature2": {
|
||||
"message": "새로운 기능: 사용자 참여 챕터 기능. 챕터는 사용자가 직접 이름을 지정할 수 있고 중첩시킬 수 있어 더욱 더 정확해요. 설정에서 활성화해서 무료로 사용할 수 있어요."
|
||||
"message": "새로운 기능: 사용자 참여 챕터 기능. 챕터는 사용자가 직접 이름을 지정할 수 있고 중첩시킬 수 있어 더욱 더 정확해요. 설정에서 사용해서 무료로 사용할 수 있어요."
|
||||
},
|
||||
"unsubmittedSegmentCounts": {
|
||||
"message": "You currently have {0} on {1}",
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
"message": "Pokazuj segmenty jako rozdziały",
|
||||
"description": "Refers to drawing segments on the YouTube seek bar as split up chapters, similar to the existing chapter system"
|
||||
},
|
||||
"showSegmentNameInChapterBar": {
|
||||
"message": "Pokaż bieżący segment poza czasem wideo"
|
||||
},
|
||||
"upvoteButtonInfo": {
|
||||
"message": "Zagłosuj na ten segment"
|
||||
},
|
||||
@@ -122,6 +125,9 @@
|
||||
"closePopup": {
|
||||
"message": "Zamknij okno"
|
||||
},
|
||||
"closeIcon": {
|
||||
"message": "Wyłącz ikonę"
|
||||
},
|
||||
"SubmitTimes": {
|
||||
"message": "Prześlij segmenty"
|
||||
},
|
||||
@@ -246,6 +252,9 @@
|
||||
"whatRefetchWhenNotFound": {
|
||||
"message": "Jeśli film jest nowy i nie znaleziono żadnych segmentów, dane będą pobierane na nowo co kilka minut w czasie oglądania."
|
||||
},
|
||||
"enableShowCategoryWithoutPermission": {
|
||||
"message": "Pokaż kategorie w menu zgłoszeń, nawet bez uprawnień do zgłaszania"
|
||||
},
|
||||
"showNotice": {
|
||||
"message": "Pokaż informacje ponownie"
|
||||
},
|
||||
@@ -408,9 +417,18 @@
|
||||
"statusReminder": {
|
||||
"message": "Sprawdź status serwera na status.sponsor.ajay.app"
|
||||
},
|
||||
"changeUserID": {
|
||||
"message": "Importuj/Eksportuj swój prywatny UserID"
|
||||
},
|
||||
"whatChangeUserID": {
|
||||
"message": "To powinno pozostać prywatne. Jest to niczym hasło i nie powinno zostać nikomu udostępnione. Przy jego użyciu ktoś może się pod ciebie podszywać. Jeśli szukasz publicznego ID użytkownika, kliknij ikonę schowka w wyskakującym oknie."
|
||||
},
|
||||
"setUserID": {
|
||||
"message": "Ustaw prywatny UserID"
|
||||
},
|
||||
"userIDChangeWarning": {
|
||||
"message": "Uwaga: Zmiana ID użytkownika jest trwała. Czy na pewno chcesz to zrobić? Na wszelki wypadek skopiuj swój poprzedni ID."
|
||||
},
|
||||
"createdBy": {
|
||||
"message": "Stworzony przez"
|
||||
},
|
||||
@@ -454,6 +472,9 @@
|
||||
"minDurationDescription": {
|
||||
"message": "Segmenty krótsze niż ustawiona wartość nie będą pomijane ani pokazywane w odtwarzaczu."
|
||||
},
|
||||
"enableManualSkipOnFullVideo": {
|
||||
"message": "Użyj ręcznego pomijania, gdy istnieje etykieta na całym filmie"
|
||||
},
|
||||
"skipNoticeDuration": {
|
||||
"message": "Czas trwania powiadomienia pominięcia (sekundy):"
|
||||
},
|
||||
@@ -502,6 +523,9 @@
|
||||
"exportOptionsUpload": {
|
||||
"message": "Wczytaj z pliku"
|
||||
},
|
||||
"whatExportOptions": {
|
||||
"message": "Jest to cała twoja konfiguracja w formacie JSON. Zawarty jest w niej twój prywatny UserID, więc uważaj, komu ją udostępniasz."
|
||||
},
|
||||
"setOptions": {
|
||||
"message": "Zapisz ustawienia"
|
||||
},
|
||||
@@ -1019,6 +1043,12 @@
|
||||
"hideSegment": {
|
||||
"message": "Ukryj segment"
|
||||
},
|
||||
"skipSegment": {
|
||||
"message": "Pomiń segment"
|
||||
},
|
||||
"playChapter": {
|
||||
"message": "Odtwórz rozdział"
|
||||
},
|
||||
"SponsorTimeEditScrollNewFeature": {
|
||||
"message": "Użyj scroll'a myszy po najechaniu nad pole edycji, aby szybko dostosować czas. Kombinacje z ctrl'em i shift'em mogą być użyte, aby doszlifować zmiany."
|
||||
},
|
||||
@@ -1085,6 +1115,9 @@
|
||||
"exportSegments": {
|
||||
"message": "Eksportuj segmenty"
|
||||
},
|
||||
"importSegments": {
|
||||
"message": "Importuj segmenty"
|
||||
},
|
||||
"Import": {
|
||||
"message": "Importuj",
|
||||
"description": "Button to initiate importing segments. Appears under the textbox where they paste in the data"
|
||||
@@ -1101,9 +1134,23 @@
|
||||
"chooseACountry": {
|
||||
"message": "Wybierz kraj"
|
||||
},
|
||||
"noDiscount": {
|
||||
"message": "Nie kwalifikujesz się do przeceny"
|
||||
},
|
||||
"discountLink": {
|
||||
"message": "Link rabatowy"
|
||||
},
|
||||
"selectYourCountry": {
|
||||
"message": "Wybierz swój kraj"
|
||||
},
|
||||
"alreadyDonated": {
|
||||
"message": "Jeśli do tej pory przekazałeś jakąkolwiek darowiznę, możesz odebrać darmowy dostęp poprzez wysyłanie maila do:",
|
||||
"description": "After the colon is an email address"
|
||||
},
|
||||
"cantAfford": {
|
||||
"message": "Jeśli nie możesz sobie pozwolić na zakup licencji, kliknij {tutaj} aby sprawdzić, czy kwalifikujesz się do zniżki",
|
||||
"description": "Keep the curly braces. The word 'here' should be translated as well."
|
||||
},
|
||||
"patreonSignIn": {
|
||||
"message": "Zaloguj się za pomocą Patreon"
|
||||
},
|
||||
@@ -1119,10 +1166,20 @@
|
||||
"enterLicenseKey": {
|
||||
"message": "Wprowadź klucz licencyjny"
|
||||
},
|
||||
"chaptersPage1": {
|
||||
"message": "Funkcja społecznościowych rozdziałów SponsorBlock jest dostępna tylko dla osób, które wykupią licencję, albo którym przyznano dostęp za darmo ze względu na swoje wcześniejszy wkład"
|
||||
},
|
||||
"chaptersPage2": {
|
||||
"message": "Uwaga: Przesyłanie rozdziałów jest nadal oparte na skalkulowanej reputacji. Kupowanie licencji pozwala tylko przeglądać rozdziały przesłane przez innych",
|
||||
"description": "On the chapters page for getting access to the paid chapters feature"
|
||||
},
|
||||
"chapterNewFeature": {
|
||||
"message": "Nowa funkcja: niestandardowe rozdziały ze źródeł społecznościowych. Są to sekcje niestandardowo nazwane w filmach, które mogą być ustawione w sposób bardziej precyzyjny. Kup licencję, aby wyświetlić rozdziały przedstawione na tym filmie, takie jak: ",
|
||||
"description": "After the comma, a list of chapters for this video will appear"
|
||||
},
|
||||
"chapterNewFeature2": {
|
||||
"message": "Nowa funkcja: niestandardowe rozdziały ze źródeł społecznościowych. Są to sekcje niestandardowo nazwane w filmach, które mogą być ustawione w sposób bardziej precyzyjny."
|
||||
},
|
||||
"unsubmittedSegmentCounts": {
|
||||
"message": "Aktualnie masz {0} na {1}",
|
||||
"description": "Example: You currently have 12 unsubmitted segments on 5 videos"
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
"message": "Renderizar segmentos como capítulos",
|
||||
"description": "Refers to drawing segments on the YouTube seek bar as split up chapters, similar to the existing chapter system"
|
||||
},
|
||||
"showSegmentNameInChapterBar": {
|
||||
"message": "Mostrar Segmento Atual ao Lado do Tempo do Vídeo"
|
||||
},
|
||||
"upvoteButtonInfo": {
|
||||
"message": "Votar nesse segmento positivamente"
|
||||
},
|
||||
@@ -122,6 +125,9 @@
|
||||
"closePopup": {
|
||||
"message": "Fechar Popup"
|
||||
},
|
||||
"closeIcon": {
|
||||
"message": "Ícone de Fechar"
|
||||
},
|
||||
"SubmitTimes": {
|
||||
"message": "Enviar Segmentos"
|
||||
},
|
||||
@@ -246,6 +252,12 @@
|
||||
"whatRefetchWhenNotFound": {
|
||||
"message": "Se o vídeo for novo e nenhum segmento for encontrado, continuaremos buscando enquanto você assiste."
|
||||
},
|
||||
"enableShowCategoryWithoutPermission": {
|
||||
"message": "Mostrar categorias no menu de envios mesmo sem permissão de envio"
|
||||
},
|
||||
"whatShowCategoryWithoutPermission": {
|
||||
"message": "Algumas categorias exigem autorização de envio devido a requisitos mínimos de reputação"
|
||||
},
|
||||
"showNotice": {
|
||||
"message": "Mostrar notificação outra vez"
|
||||
},
|
||||
@@ -408,9 +420,18 @@
|
||||
"statusReminder": {
|
||||
"message": "Verifique status.sponsor.ajay.app para o status do servidor."
|
||||
},
|
||||
"changeUserID": {
|
||||
"message": "Importar/Exportar Seu UserID Privado"
|
||||
},
|
||||
"whatChangeUserID": {
|
||||
"message": "Esta informação deve se mantida privada. Ela é como uma senha e não deve ser compartilhada. Outras pessoas poderão se passar por você caso obtenham acesso. Se estiver procurando por sua ID Pública de Usuário, clique no ícone de prancheta no popup."
|
||||
},
|
||||
"setUserID": {
|
||||
"message": "Definir UserID Privado"
|
||||
},
|
||||
"userIDChangeWarning": {
|
||||
"message": "Aviso: A modificação do ID de usuário privado é permanente. Você tem certeza de que quer fazer isso? Certifique-se de fazer backup do anterior."
|
||||
},
|
||||
"createdBy": {
|
||||
"message": "Criado por"
|
||||
},
|
||||
@@ -454,6 +475,12 @@
|
||||
"minDurationDescription": {
|
||||
"message": "Segmentos menores do que o valor definido não serão pulados ou mostrados no reprodutor."
|
||||
},
|
||||
"enableManualSkipOnFullVideo": {
|
||||
"message": "Usar o pulo manual quando houver um rótulo de vídeo completo"
|
||||
},
|
||||
"whatManualSkipOnFullVideo": {
|
||||
"message": "Para pessoas que desejam assistir ao vídeo sem interrupção se ele for totalmente patrocinado ou autopromoção."
|
||||
},
|
||||
"skipNoticeDuration": {
|
||||
"message": "Duração do aviso prévio de pular (segundos):"
|
||||
},
|
||||
@@ -709,6 +736,12 @@
|
||||
"category_chapter": {
|
||||
"message": "Capítulo"
|
||||
},
|
||||
"category_chapter_description": {
|
||||
"message": "Capítulos personalizados que descrevem as principais seções de um vídeo."
|
||||
},
|
||||
"category_chapter_guideline1": {
|
||||
"message": "Não mencione os nomes dos patrocinadores"
|
||||
},
|
||||
"category_livestream_messages": {
|
||||
"message": "Livestream: Leituras de Doação/Mensagem"
|
||||
},
|
||||
@@ -739,6 +772,9 @@
|
||||
"showOverlay_full": {
|
||||
"message": "Mostrar Rótulo"
|
||||
},
|
||||
"showOverlay_chapter": {
|
||||
"message": "Mostrar Capítulos"
|
||||
},
|
||||
"autoSkipOnMusicVideos": {
|
||||
"message": "Pular automaticamente todos os segmentos quando há um segmento que não é música"
|
||||
},
|
||||
@@ -866,6 +902,9 @@
|
||||
"categoryPillTitleText": {
|
||||
"message": "Este vídeo inteiro está rotulado como esta categoria e está muito integrado para poder ser separado"
|
||||
},
|
||||
"chapterNameTooltipWarning": {
|
||||
"message": "Um de seus nomes de capítulo é semelhante a uma categoria. Sempre que possível, você deve usar categorias."
|
||||
},
|
||||
"experiementOptOut": {
|
||||
"message": "Optar por sair de todos os experimentos futuros",
|
||||
"description": "This is used in a popup about a new experiment to get a list of unlisted videos to back up since all unlisted videos uploaded before 2017 will be set to private."
|
||||
@@ -873,6 +912,9 @@
|
||||
"hideForever": {
|
||||
"message": "Ocultar para sempre"
|
||||
},
|
||||
"warningChatInfo": {
|
||||
"message": "Percebemos que você estava cometendo alguns erros comuns que não são prejudiciais"
|
||||
},
|
||||
"Donate": {
|
||||
"message": "Doar"
|
||||
},
|
||||
@@ -945,6 +987,9 @@
|
||||
"LearnMore": {
|
||||
"message": "Saiba mais"
|
||||
},
|
||||
"FullDetails": {
|
||||
"message": "Ver Detalhes Completos"
|
||||
},
|
||||
"CopyDownvoteButtonInfo": {
|
||||
"message": "Dá voto negativo e cria uma cópia local para você reenviar"
|
||||
},
|
||||
@@ -969,6 +1014,12 @@
|
||||
"hideSegment": {
|
||||
"message": "Ocultar segmento"
|
||||
},
|
||||
"skipSegment": {
|
||||
"message": "Pular segmento"
|
||||
},
|
||||
"playChapter": {
|
||||
"message": "Reproduzir capítulo"
|
||||
},
|
||||
"SponsorTimeEditScrollNewFeature": {
|
||||
"message": "Use a roda do mouse enquanto mantêm o cursor sobre a caixa de edição para ajustar o tempo rapidamente. Combinações das teclas ctrl e shift podem ser usadas para refinar as mudanças."
|
||||
},
|
||||
@@ -1031,5 +1082,69 @@
|
||||
},
|
||||
"confirmResetToDefault": {
|
||||
"message": "Tem certeza de que deseja redefinir todas as configurações para os valores padrão? Essa ação não poderá ser desfeita."
|
||||
},
|
||||
"exportSegments": {
|
||||
"message": "Exportar segmentos"
|
||||
},
|
||||
"importSegments": {
|
||||
"message": "Importar segmentos"
|
||||
},
|
||||
"Import": {
|
||||
"message": "Importar",
|
||||
"description": "Button to initiate importing segments. Appears under the textbox where they paste in the data"
|
||||
},
|
||||
"selectYourCountry": {
|
||||
"message": "Selecione o seu país"
|
||||
},
|
||||
"unsubmittedSegmentCountsZero": {
|
||||
"message": "No momento, você não tem segmentos não enviados",
|
||||
"description": "Replaces 'unsubmittedSegmentCounts' string when there are no unsubmitted segments"
|
||||
},
|
||||
"unsubmittedSegmentsSingular": {
|
||||
"message": "segmento não enviado",
|
||||
"description": "Example: You currently have 1 *unsubmitted segment* on 1 video"
|
||||
},
|
||||
"unsubmittedSegmentsPlural": {
|
||||
"message": "segmentos não enviados",
|
||||
"description": "Example: You currently have 12 *unsubmitted segments* on 5 videos"
|
||||
},
|
||||
"videosSingular": {
|
||||
"message": "vídeo",
|
||||
"description": "Example: You currently have 3 unsubmitted segments on 1 *video*"
|
||||
},
|
||||
"videosPlural": {
|
||||
"message": "vídeos",
|
||||
"description": "Example: You currently have 12 unsubmitted segments on 5 *videos*"
|
||||
},
|
||||
"clearUnsubmittedSegments": {
|
||||
"message": "Excluir todos os segmentos",
|
||||
"description": "Label for a button in settings"
|
||||
},
|
||||
"clearUnsubmittedSegmentsConfirm": {
|
||||
"message": "Tem certeza de que deseja excluir todos os segmentos não enviados?",
|
||||
"description": "Confirmation message for the Clear unsubmitted segments button"
|
||||
},
|
||||
"showUnsubmittedSegments": {
|
||||
"message": "Mostrar segmentos",
|
||||
"description": "Show/hide button for the unsubmitted segments list"
|
||||
},
|
||||
"hideUnsubmittedSegments": {
|
||||
"message": "Ocultar segmentos",
|
||||
"description": "Show/hide button for the unsubmitted segments list"
|
||||
},
|
||||
"videoID": {
|
||||
"message": "ID do Vídeo",
|
||||
"description": "Header of the unsubmitted segments list"
|
||||
},
|
||||
"segmentCount": {
|
||||
"message": "Número de segmentos",
|
||||
"description": "Header of the unsubmitted segments list"
|
||||
},
|
||||
"actions": {
|
||||
"message": "Ações",
|
||||
"description": "Header of the unsubmitted segments list"
|
||||
},
|
||||
"exportSegmentsAsURL": {
|
||||
"message": "Compartilhar como URL"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,19 @@
|
||||
"Segments": {
|
||||
"message": "片段"
|
||||
},
|
||||
"SegmentsCap": {
|
||||
"message": "片段"
|
||||
},
|
||||
"Chapters": {
|
||||
"message": "章节"
|
||||
},
|
||||
"renderAsChapters": {
|
||||
"message": "将片段显示为章节",
|
||||
"description": "Refers to drawing segments on the YouTube seek bar as split up chapters, similar to the existing chapter system"
|
||||
},
|
||||
"showSegmentNameInChapterBar": {
|
||||
"message": "在视频时间旁显示当前片段"
|
||||
},
|
||||
"upvoteButtonInfo": {
|
||||
"message": "为这个提交点赞"
|
||||
},
|
||||
@@ -52,6 +65,9 @@
|
||||
"reskip": {
|
||||
"message": "继续跳过"
|
||||
},
|
||||
"unmute": {
|
||||
"message": "取消静音"
|
||||
},
|
||||
"paused": {
|
||||
"message": "已暂停"
|
||||
},
|
||||
@@ -85,6 +101,9 @@
|
||||
"noVideoID": {
|
||||
"message": "未找到 YouTube 视频。\n如果识别错误,请刷新此页面。"
|
||||
},
|
||||
"refreshSegments": {
|
||||
"message": "刷新片段"
|
||||
},
|
||||
"success": {
|
||||
"message": "成功 !"
|
||||
},
|
||||
@@ -106,6 +125,9 @@
|
||||
"closePopup": {
|
||||
"message": "关闭弹窗"
|
||||
},
|
||||
"closeIcon": {
|
||||
"message": "关闭图标"
|
||||
},
|
||||
"SubmitTimes": {
|
||||
"message": "提交片段"
|
||||
},
|
||||
@@ -155,6 +177,9 @@
|
||||
"setUsername": {
|
||||
"message": "设定用户名"
|
||||
},
|
||||
"copySegmentID": {
|
||||
"message": "复制片段 ID"
|
||||
},
|
||||
"discordAdvert": {
|
||||
"message": "快加入官方 Discord 服务器来提供建议与反馈!"
|
||||
},
|
||||
@@ -179,6 +204,9 @@
|
||||
"hideInfoButton": {
|
||||
"message": "在 Youtube 播放器上隐藏信息按钮"
|
||||
},
|
||||
"autoHideInfoButton": {
|
||||
"message": "自动隐藏信息按钮"
|
||||
},
|
||||
"hideDeleteButton": {
|
||||
"message": "在 Youtube 播放器上隐藏删除按钮"
|
||||
},
|
||||
@@ -251,6 +279,13 @@
|
||||
"skip": {
|
||||
"message": "跳过"
|
||||
},
|
||||
"mute": {
|
||||
"message": "静音"
|
||||
},
|
||||
"full": {
|
||||
"message": "整个视频",
|
||||
"description": "Used for the name of the option to label an entire video as sponsor or self promotion."
|
||||
},
|
||||
"skip_category": {
|
||||
"message": "跳过{0}?"
|
||||
},
|
||||
@@ -304,6 +339,9 @@
|
||||
"supportOtherSites": {
|
||||
"message": "支持第三方 YouTube 网站"
|
||||
},
|
||||
"supportedSites": {
|
||||
"message": "支持的站点: "
|
||||
},
|
||||
"optionsInfo": {
|
||||
"message": "启用 Invidious 支持,禁用自动跳过,隐藏按钮等等。"
|
||||
},
|
||||
@@ -358,6 +396,15 @@
|
||||
"exportOptions": {
|
||||
"message": "导入/导出所有选项"
|
||||
},
|
||||
"exportOptionsCopy": {
|
||||
"message": "编辑/复制"
|
||||
},
|
||||
"exportOptionsDownload": {
|
||||
"message": "保存到文件"
|
||||
},
|
||||
"exportOptionsUpload": {
|
||||
"message": "从文件加载"
|
||||
},
|
||||
"setOptions": {
|
||||
"message": "设定选项"
|
||||
},
|
||||
@@ -382,6 +429,9 @@
|
||||
"preview": {
|
||||
"message": "预览"
|
||||
},
|
||||
"unsubmitted": {
|
||||
"message": "未提交"
|
||||
},
|
||||
"inspect": {
|
||||
"message": "检查"
|
||||
},
|
||||
@@ -400,22 +450,38 @@
|
||||
"copyDebugInformationComplete": {
|
||||
"message": "调试信息已复制到剪切板中。 您可以随意移除任何您不想分享的信息。请将其另存为 .txt 文件或粘贴到错误报告中。"
|
||||
},
|
||||
"keyAlreadyUsed": {
|
||||
"message": "此快捷键已绑定到另一个动作。请选择其他快捷键。"
|
||||
},
|
||||
"to": {
|
||||
"message": "到",
|
||||
"description": "Used between segments. Example: 1:20 to 1:30"
|
||||
},
|
||||
"CopiedExclamation": {
|
||||
"message": "复制成功!",
|
||||
"description": "Used after something has been copied to the clipboard. Example: 'Copied!'"
|
||||
},
|
||||
"category_sponsor": {
|
||||
"message": "赞助商广告"
|
||||
},
|
||||
"category_sponsor_description": {
|
||||
"message": "付费推广、付费推荐和直接广告。不应用于自我推广或免费提及、推荐他们喜欢的事物/创作者/网站/产品。"
|
||||
},
|
||||
"category_sponsor_guideline1": {
|
||||
"message": "付费推广"
|
||||
},
|
||||
"category_sponsor_guideline2": {
|
||||
"message": "捐赠或自制周边不属于此项"
|
||||
},
|
||||
"category_selfpromo": {
|
||||
"message": "未收钱的/自我推销"
|
||||
},
|
||||
"category_selfpromo_description": {
|
||||
"message": "类似于 “赞助商广告” ,但为无报酬或自我推广。包括有关商品、捐赠的部分或合作者的信息。"
|
||||
},
|
||||
"category_selfpromo_guideline1": {
|
||||
"message": "捐赠、会员和自制周边"
|
||||
},
|
||||
"category_interaction": {
|
||||
"message": "互动提醒(订阅)"
|
||||
},
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
transform: scaleY(0.625) translateY(-30%) translateY(1.5px);
|
||||
}
|
||||
|
||||
.ytp-big-mode .sponsorTwoTooltips .sponsorCategoryTooltip {
|
||||
top: 75px !important;
|
||||
}
|
||||
|
||||
.progress-bar-line > #previewbar {
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
@@ -50,37 +50,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
******************************
|
||||
|
||||
object-assign
|
||||
4.1.1 <https://github.com/sindresorhus/object-assign>
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
******************************
|
||||
|
||||
react
|
||||
17.0.2 <https://github.com/facebook/react>
|
||||
18.2.0 <https://github.com/facebook/react>
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
@@ -107,7 +80,7 @@ SOFTWARE.
|
||||
******************************
|
||||
|
||||
react-dom
|
||||
17.0.2 <https://github.com/facebook/react>
|
||||
18.2.0 <https://github.com/facebook/react>
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
@@ -134,7 +107,7 @@ SOFTWARE.
|
||||
******************************
|
||||
|
||||
scheduler
|
||||
0.20.2 <https://github.com/facebook/react>
|
||||
0.23.0 <https://github.com/facebook/react>
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
@@ -24,7 +24,7 @@ if (utils.isFirefox()) {
|
||||
utils.wait(() => Config.config !== null).then(function() {
|
||||
if (Config.config.supportInvidious) utils.setupExtraSiteContentScripts();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onTabUpdatedListener(tabId: number) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
@@ -77,17 +77,17 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
ok: response.ok
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return true;
|
||||
case "submitVote":
|
||||
submitVote(request.type, request.UUID, request.category).then(callback);
|
||||
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "registerContentScript":
|
||||
case "registerContentScript":
|
||||
registerFirefoxContentScript(request);
|
||||
return false;
|
||||
case "unregisterContentScript":
|
||||
case "unregisterContentScript":
|
||||
unregisterFirefoxContentScript(request.id)
|
||||
return false;
|
||||
case "tabs": {
|
||||
@@ -106,6 +106,8 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
return true;
|
||||
}
|
||||
case "time":
|
||||
case "infoUpdated":
|
||||
case "videoChanged":
|
||||
if (sender.tab) {
|
||||
popupPort[sender.tab.id]?.postMessage(request);
|
||||
}
|
||||
@@ -156,8 +158,8 @@ chrome.runtime.onInstalled.addListener(function () {
|
||||
/**
|
||||
* Only works on Firefox.
|
||||
* Firefox requires that it be applied after every extension restart.
|
||||
*
|
||||
* @param {JSON} options
|
||||
*
|
||||
* @param {JSON} options
|
||||
*/
|
||||
function registerFirefoxContentScript(options: Registration) {
|
||||
const oldRegistration = contentScriptRegistrations[options.id];
|
||||
@@ -174,7 +176,7 @@ function registerFirefoxContentScript(options: Registration) {
|
||||
/**
|
||||
* Only works on Firefox.
|
||||
* Firefox requires that this is handled by the background script
|
||||
*
|
||||
*
|
||||
*/
|
||||
function unregisterFirefoxContentScript(id: string) {
|
||||
contentScriptRegistrations[id].unregister();
|
||||
@@ -225,10 +227,10 @@ async function asyncRequestToServer(type: string, address: string, data = {}) {
|
||||
|
||||
/**
|
||||
* Sends a request to the specified url
|
||||
*
|
||||
*
|
||||
* @param type The request type "GET", "POST", etc.
|
||||
* @param address The address to add to the SponsorBlock server address
|
||||
* @param callback
|
||||
* @param callback
|
||||
*/
|
||||
async function sendRequestToCustomServer(type: string, url: string, data = {}) {
|
||||
// If GET, convert JSON to parameters
|
||||
@@ -248,4 +250,4 @@ async function sendRequestToCustomServer(type: string, url: string, data = {}) {
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,41 +8,42 @@ enum CountdownMode {
|
||||
}
|
||||
|
||||
export interface NoticeProps {
|
||||
noticeTitle: string,
|
||||
noticeTitle: string;
|
||||
|
||||
maxCountdownTime?: () => number,
|
||||
dontPauseCountdown?: boolean,
|
||||
amountOfPreviousNotices?: number,
|
||||
showInSecondSlot?: boolean,
|
||||
timed?: boolean,
|
||||
idSuffix?: string,
|
||||
maxCountdownTime?: () => number;
|
||||
dontPauseCountdown?: boolean;
|
||||
amountOfPreviousNotices?: number;
|
||||
showInSecondSlot?: boolean;
|
||||
timed?: boolean;
|
||||
idSuffix?: string;
|
||||
|
||||
fadeIn?: boolean,
|
||||
startFaded?: boolean,
|
||||
firstColumn?: React.ReactElement[] | React.ReactElement,
|
||||
firstRow?: React.ReactElement,
|
||||
bottomRow?: React.ReactElement[],
|
||||
fadeIn?: boolean;
|
||||
startFaded?: boolean;
|
||||
firstColumn?: React.ReactElement[] | React.ReactElement;
|
||||
firstRow?: React.ReactElement;
|
||||
bottomRow?: React.ReactElement[];
|
||||
|
||||
smaller?: boolean,
|
||||
limitWidth?: boolean,
|
||||
extraClass?: string,
|
||||
hideLogo?: boolean,
|
||||
hideRightInfo?: boolean,
|
||||
smaller?: boolean;
|
||||
limitWidth?: boolean;
|
||||
extraClass?: string;
|
||||
hideLogo?: boolean;
|
||||
hideRightInfo?: boolean;
|
||||
|
||||
// Callback for when this is closed
|
||||
closeListener: () => void,
|
||||
onMouseEnter?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void,
|
||||
closeListener: () => void;
|
||||
onMouseEnter?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
|
||||
zIndex?: number,
|
||||
style?: React.CSSProperties
|
||||
zIndex?: number;
|
||||
style?: React.CSSProperties;
|
||||
biggerCloseButton?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface NoticeState {
|
||||
maxCountdownTime: () => number,
|
||||
maxCountdownTime: () => number;
|
||||
|
||||
countdownTime: number,
|
||||
countdownMode: CountdownMode,
|
||||
countdownTime: number;
|
||||
countdownMode: CountdownMode;
|
||||
|
||||
mouseHovering: boolean;
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface NoticeTextSelectionProps {
|
||||
icon?: string,
|
||||
text: string,
|
||||
idSuffix: string,
|
||||
onClick?: (event: React.MouseEvent) => unknown
|
||||
icon?: string;
|
||||
text: string;
|
||||
idSuffix: string;
|
||||
onClick?: (event: React.MouseEvent) => unknown;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface NoticeTextSelectionState {
|
||||
|
||||
@@ -14,15 +14,16 @@ import { DEFAULT_CATEGORY } from "../utils/categoryUtils";
|
||||
const utils = new Utils();
|
||||
|
||||
export interface SponsorTimeEditProps {
|
||||
index: number,
|
||||
index: number;
|
||||
|
||||
idSuffix: string,
|
||||
idSuffix: string;
|
||||
// Contains functions and variables from the content script needed by the skip notice
|
||||
contentContainer: ContentContainer,
|
||||
contentContainer: ContentContainer;
|
||||
|
||||
submissionNotice: SubmissionNoticeComponent;
|
||||
categoryList?: Category[];
|
||||
categoryChangeListener?: (index: number, category: Category) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SponsorTimeEditState {
|
||||
@@ -540,9 +541,14 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
|
||||
if (time === null) time = sponsorTime.segment[0];
|
||||
|
||||
const addedTime = sponsorTime.segment.length === 1;
|
||||
sponsorTime.segment[index] = time;
|
||||
if (sponsorTime.actionType === ActionType.Poi) sponsorTime.segment[1] = time;
|
||||
|
||||
if (addedTime) {
|
||||
this.props.contentContainer().updateEditButtonsOnPlayer();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
sponsorTimeEdits: this.getFormattedSponsorTimesEdits(sponsorTime)
|
||||
}, () => this.saveEditTimes());
|
||||
@@ -574,6 +580,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
saveEditTimes(): void {
|
||||
const sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
|
||||
const category = this.categoryOptionRef.current.value as Category
|
||||
|
||||
if (this.state.editing) {
|
||||
const startTime = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[0]);
|
||||
@@ -581,11 +588,18 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
|
||||
// Change segment time only if the format was correct
|
||||
if (startTime !== null && endTime !== null) {
|
||||
const addingTime = sponsorTimesSubmitting[this.props.index].segment.length === 1;
|
||||
sponsorTimesSubmitting[this.props.index].segment = [startTime, endTime];
|
||||
|
||||
if (addingTime) {
|
||||
this.props.contentContainer().updateEditButtonsOnPlayer();
|
||||
}
|
||||
}
|
||||
} else if (this.state.sponsorTimeEdits[1] === null && category === "outro") {
|
||||
sponsorTimesSubmitting[this.props.index].segment[1] = this.props.contentContainer().v.duration;
|
||||
this.props.contentContainer().updateEditButtonsOnPlayer();
|
||||
}
|
||||
|
||||
const category = this.categoryOptionRef.current.value as Category
|
||||
sponsorTimesSubmitting[this.props.index].category = category;
|
||||
|
||||
const actionType = this.getNextActionType(category, this.actionTypeOptionRef?.current?.value as ActionType);
|
||||
|
||||
@@ -20,8 +20,8 @@ export interface SubmissionNoticeProps {
|
||||
}
|
||||
|
||||
export interface SubmissionNoticeState {
|
||||
noticeTitle: string,
|
||||
messages: string[],
|
||||
noticeTitle: string;
|
||||
messages: string[];
|
||||
idSuffix: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface CategorySkipOptionsProps {
|
||||
category: Category;
|
||||
defaultColor?: string;
|
||||
defaultPreviewColor?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface CategorySkipOptionsState {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import Config from "../../config";
|
||||
import { Keybind } from "../../types";
|
||||
import KeybindDialogComponent from "./KeybindDialogComponent";
|
||||
@@ -14,6 +14,7 @@ export interface KeybindState {
|
||||
}
|
||||
|
||||
let dialog;
|
||||
let root: Root;
|
||||
|
||||
class KeybindComponent extends React.Component<KeybindProps, KeybindState> {
|
||||
constructor(props: KeybindProps) {
|
||||
@@ -56,11 +57,12 @@ class KeybindComponent extends React.Component<KeybindProps, KeybindState> {
|
||||
dialog = parent.document.createElement("div");
|
||||
dialog.id = "keybind-dialog";
|
||||
parent.document.body.prepend(dialog);
|
||||
ReactDOM.render(<KeybindDialogComponent option={this.props.option} closeListener={(updateWith) => this.closeEditDialog(updateWith)} />, dialog);
|
||||
root = createRoot(dialog);
|
||||
root.render(<KeybindDialogComponent option={this.props.option} closeListener={(updateWith) => this.closeEditDialog(updateWith)} />);
|
||||
}
|
||||
|
||||
closeEditDialog(updateWith: Keybind): void {
|
||||
ReactDOM.unmountComponentAtNode(dialog);
|
||||
root.unmount();
|
||||
dialog.remove();
|
||||
if (updateWith != null)
|
||||
this.setState({keybind: updateWith});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { exportTimes, exportTimesAsHashParam } from "../../utils/exporter";
|
||||
|
||||
export interface UnsubmittedVideosListItemProps {
|
||||
videoID: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface UnsubmittedVideosListItemState {
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface UnsubmittedVideosProps {
|
||||
}
|
||||
|
||||
export interface UnsubmittedVideosState {
|
||||
tableVisible: boolean,
|
||||
tableVisible: boolean;
|
||||
}
|
||||
|
||||
class UnsubmittedVideosComponent extends React.Component<UnsubmittedVideosProps, UnsubmittedVideosState> {
|
||||
|
||||
194
src/config.ts
194
src/config.ts
@@ -8,122 +8,122 @@ export interface Permission {
|
||||
}
|
||||
|
||||
interface SBConfig {
|
||||
userID: string,
|
||||
isVip: boolean,
|
||||
permissions: Record<Category, Permission>,
|
||||
userID: string;
|
||||
isVip: boolean;
|
||||
permissions: Record<Category, Permission>;
|
||||
/* Contains unsubmitted segments that the user has created. */
|
||||
unsubmittedSegments: Record<string, SponsorTime[]>,
|
||||
defaultCategory: Category,
|
||||
renderSegmentsAsChapters: boolean,
|
||||
whitelistedChannels: string[],
|
||||
forceChannelCheck: boolean,
|
||||
minutesSaved: number,
|
||||
skipCount: number,
|
||||
sponsorTimesContributed: number,
|
||||
submissionCountSinceCategories: number, // New count used to show the "Read The Guidelines!!" message
|
||||
showTimeWithSkips: boolean,
|
||||
disableSkipping: boolean,
|
||||
muteSegments: boolean,
|
||||
fullVideoSegments: boolean,
|
||||
manualSkipOnFullVideo: boolean,
|
||||
trackViewCount: boolean,
|
||||
trackViewCountInPrivate: boolean,
|
||||
trackDownvotes: boolean,
|
||||
dontShowNotice: boolean,
|
||||
noticeVisibilityMode: NoticeVisbilityMode,
|
||||
hideVideoPlayerControls: boolean,
|
||||
hideInfoButtonPlayerControls: boolean,
|
||||
hideDeleteButtonPlayerControls: boolean,
|
||||
hideUploadButtonPlayerControls: boolean,
|
||||
hideSkipButtonPlayerControls: boolean,
|
||||
hideDiscordLaunches: number,
|
||||
hideDiscordLink: boolean,
|
||||
invidiousInstances: string[],
|
||||
supportInvidious: boolean,
|
||||
serverAddress: string,
|
||||
minDuration: number,
|
||||
skipNoticeDuration: number,
|
||||
audioNotificationOnSkip: boolean,
|
||||
checkForUnlistedVideos: boolean,
|
||||
testingServer: boolean,
|
||||
refetchWhenNotFound: boolean,
|
||||
ytInfoPermissionGranted: boolean,
|
||||
allowExpirements: boolean,
|
||||
showDonationLink: boolean,
|
||||
showPopupDonationCount: number,
|
||||
showUpsells: boolean,
|
||||
donateClicked: number,
|
||||
autoHideInfoButton: boolean,
|
||||
autoSkipOnMusicVideos: boolean,
|
||||
unsubmittedSegments: Record<string, SponsorTime[]>;
|
||||
defaultCategory: Category;
|
||||
renderSegmentsAsChapters: boolean;
|
||||
whitelistedChannels: string[];
|
||||
forceChannelCheck: boolean;
|
||||
minutesSaved: number;
|
||||
skipCount: number;
|
||||
sponsorTimesContributed: number;
|
||||
submissionCountSinceCategories: number; // New count used to show the "Read The Guidelines!!" message
|
||||
showTimeWithSkips: boolean;
|
||||
disableSkipping: boolean;
|
||||
muteSegments: boolean;
|
||||
fullVideoSegments: boolean;
|
||||
manualSkipOnFullVideo: boolean;
|
||||
trackViewCount: boolean;
|
||||
trackViewCountInPrivate: boolean;
|
||||
trackDownvotes: boolean;
|
||||
dontShowNotice: boolean;
|
||||
noticeVisibilityMode: NoticeVisbilityMode;
|
||||
hideVideoPlayerControls: boolean;
|
||||
hideInfoButtonPlayerControls: boolean;
|
||||
hideDeleteButtonPlayerControls: boolean;
|
||||
hideUploadButtonPlayerControls: boolean;
|
||||
hideSkipButtonPlayerControls: boolean;
|
||||
hideDiscordLaunches: number;
|
||||
hideDiscordLink: boolean;
|
||||
invidiousInstances: string[];
|
||||
supportInvidious: boolean;
|
||||
serverAddress: string;
|
||||
minDuration: number;
|
||||
skipNoticeDuration: number;
|
||||
audioNotificationOnSkip: boolean;
|
||||
checkForUnlistedVideos: boolean;
|
||||
testingServer: boolean;
|
||||
refetchWhenNotFound: boolean;
|
||||
ytInfoPermissionGranted: boolean;
|
||||
allowExpirements: boolean;
|
||||
showDonationLink: boolean;
|
||||
showPopupDonationCount: number;
|
||||
showUpsells: boolean;
|
||||
donateClicked: number;
|
||||
autoHideInfoButton: boolean;
|
||||
autoSkipOnMusicVideos: boolean;
|
||||
colorPalette: {
|
||||
red: string,
|
||||
white: string,
|
||||
locked: string
|
||||
},
|
||||
scrollToEditTimeUpdate: boolean,
|
||||
categoryPillUpdate: boolean,
|
||||
showChapterInfoMessage: boolean,
|
||||
darkMode: boolean,
|
||||
showCategoryGuidelines: boolean,
|
||||
showCategoryWithoutPermission: boolean,
|
||||
showSegmentNameInChapterBar: boolean,
|
||||
red: string;
|
||||
white: string;
|
||||
locked: string;
|
||||
};
|
||||
scrollToEditTimeUpdate: boolean;
|
||||
categoryPillUpdate: boolean;
|
||||
showChapterInfoMessage: boolean;
|
||||
darkMode: boolean;
|
||||
showCategoryGuidelines: boolean;
|
||||
showCategoryWithoutPermission: boolean;
|
||||
showSegmentNameInChapterBar: boolean;
|
||||
|
||||
// Used to cache calculated text color info
|
||||
categoryPillColors: {
|
||||
[key in Category]: {
|
||||
lastColor: string,
|
||||
textColor: string
|
||||
lastColor: string;
|
||||
textColor: string;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
skipKeybind: Keybind,
|
||||
startSponsorKeybind: Keybind,
|
||||
submitKeybind: Keybind,
|
||||
nextChapterKeybind: Keybind,
|
||||
previousChapterKeybind: Keybind,
|
||||
skipKeybind: Keybind;
|
||||
startSponsorKeybind: Keybind;
|
||||
submitKeybind: Keybind;
|
||||
nextChapterKeybind: Keybind;
|
||||
previousChapterKeybind: Keybind;
|
||||
|
||||
// What categories should be skipped
|
||||
categorySelections: CategorySelection[],
|
||||
categorySelections: CategorySelection[];
|
||||
|
||||
payments: {
|
||||
licenseKey: string,
|
||||
lastCheck: number,
|
||||
lastFreeCheck: number,
|
||||
freeAccess: boolean,
|
||||
chaptersAllowed: boolean
|
||||
}
|
||||
licenseKey: string;
|
||||
lastCheck: number;
|
||||
lastFreeCheck: number;
|
||||
freeAccess: boolean;
|
||||
chaptersAllowed: boolean;
|
||||
};
|
||||
|
||||
// Preview bar
|
||||
barTypes: {
|
||||
"preview-chooseACategory": PreviewBarOption,
|
||||
"sponsor": PreviewBarOption,
|
||||
"preview-sponsor": PreviewBarOption,
|
||||
"selfpromo": PreviewBarOption,
|
||||
"preview-selfpromo": PreviewBarOption,
|
||||
"exclusive_access": PreviewBarOption,
|
||||
"interaction": PreviewBarOption,
|
||||
"preview-interaction": PreviewBarOption,
|
||||
"intro": PreviewBarOption,
|
||||
"preview-intro": PreviewBarOption,
|
||||
"outro": PreviewBarOption,
|
||||
"preview-outro": PreviewBarOption,
|
||||
"preview": PreviewBarOption,
|
||||
"preview-preview": PreviewBarOption,
|
||||
"music_offtopic": PreviewBarOption,
|
||||
"preview-music_offtopic": PreviewBarOption,
|
||||
"poi_highlight": PreviewBarOption,
|
||||
"preview-poi_highlight": PreviewBarOption,
|
||||
"filler": PreviewBarOption,
|
||||
"preview-filler": PreviewBarOption,
|
||||
}
|
||||
"preview-chooseACategory": PreviewBarOption;
|
||||
"sponsor": PreviewBarOption;
|
||||
"preview-sponsor": PreviewBarOption;
|
||||
"selfpromo": PreviewBarOption;
|
||||
"preview-selfpromo": PreviewBarOption;
|
||||
"exclusive_access": PreviewBarOption;
|
||||
"interaction": PreviewBarOption;
|
||||
"preview-interaction": PreviewBarOption;
|
||||
"intro": PreviewBarOption;
|
||||
"preview-intro": PreviewBarOption;
|
||||
"outro": PreviewBarOption;
|
||||
"preview-outro": PreviewBarOption;
|
||||
"preview": PreviewBarOption;
|
||||
"preview-preview": PreviewBarOption;
|
||||
"music_offtopic": PreviewBarOption;
|
||||
"preview-music_offtopic": PreviewBarOption;
|
||||
"poi_highlight": PreviewBarOption;
|
||||
"preview-poi_highlight": PreviewBarOption;
|
||||
"filler": PreviewBarOption;
|
||||
"preview-filler": PreviewBarOption;
|
||||
};
|
||||
}
|
||||
|
||||
export type VideoDownvotes = { segments: { uuid: HashedValue, hidden: SponsorHideType }[] , lastAccess: number };
|
||||
export type VideoDownvotes = { segments: { uuid: HashedValue; hidden: SponsorHideType }[] ; lastAccess: number };
|
||||
|
||||
interface SBStorage {
|
||||
/* VideoID prefixes to UUID prefixes */
|
||||
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>,
|
||||
navigationApiAvailable: boolean,
|
||||
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>;
|
||||
navigationApiAvailable: boolean;
|
||||
}
|
||||
|
||||
export interface SBObject {
|
||||
@@ -340,7 +340,7 @@ const Config: SBObject = {
|
||||
|
||||
// Function setup
|
||||
|
||||
function configProxy(): { sync: SBConfig, local: SBStorage } {
|
||||
function configProxy(): { sync: SBConfig; local: SBStorage } {
|
||||
chrome.storage.onChanged.addListener((changes: {[key: string]: chrome.storage.StorageChange}, areaName) => {
|
||||
if (areaName === "sync") {
|
||||
for (const key in changes) {
|
||||
|
||||
115
src/content.ts
115
src/content.ts
@@ -8,6 +8,7 @@ import {
|
||||
ContentContainer,
|
||||
HashedValue,
|
||||
Keybind,
|
||||
PageType,
|
||||
ScheduledTime,
|
||||
SegmentUUID,
|
||||
SkipToTimeParams,
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
ToggleSkippable,
|
||||
VideoID,
|
||||
VideoInfo,
|
||||
PageType
|
||||
} from "./types";
|
||||
import Utils from "./utils";
|
||||
import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar";
|
||||
@@ -68,7 +68,7 @@ let channelIDInfo: ChannelIDInfo;
|
||||
// Locked Categories in this tab, like: ["sponsor","intro","outro"]
|
||||
let lockedCategories: Category[] = [];
|
||||
// Used to calculate a more precise "virtual" video time
|
||||
let lastKnownVideoTime: { videoTime: number, preciseTime: number } = {
|
||||
let lastKnownVideoTime: { videoTime: number; preciseTime: number } = {
|
||||
videoTime: null,
|
||||
preciseTime: null
|
||||
};
|
||||
@@ -125,7 +125,7 @@ let categoryPill: CategoryPill = null;
|
||||
let controls: HTMLElement | null = null;
|
||||
|
||||
/** Contains buttons created by `createButton()`. */
|
||||
const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImageElement, setupListener: boolean}> = {};
|
||||
const playerButtons: Record<string, {button: HTMLButtonElement; image: HTMLImageElement; setupListener: boolean}> = {};
|
||||
|
||||
// Direct Links after the config is loaded
|
||||
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document)));
|
||||
@@ -215,7 +215,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
case "getVideoID":
|
||||
sendResponse({
|
||||
videoID: sponsorVideoID,
|
||||
creatingSegment: isSegmentCreationInProgress(),
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -243,15 +242,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
// update video on refresh if videoID invalid
|
||||
if (!sponsorVideoID) videoIDChange(getYouTubeVideoID(document));
|
||||
// fetch segments
|
||||
sponsorsLookup(false).then(() => sendResponse({
|
||||
found: sponsorDataFound,
|
||||
status: lastResponseStatus,
|
||||
sponsorTimes: sponsorTimes,
|
||||
time: video.currentTime,
|
||||
onMobileYouTube
|
||||
}));
|
||||
sponsorsLookup(false);
|
||||
|
||||
return true;
|
||||
break;
|
||||
case "unskip":
|
||||
unskipSponsorTime(sponsorTimes.find((segment) => segment.UUID === request.UUID), null, true);
|
||||
break;
|
||||
@@ -276,7 +269,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
const importedSegments = importTimes(request.data, video.duration);
|
||||
let addedSegments = false;
|
||||
for (const segment of importedSegments) {
|
||||
if (!sponsorTimesSubmitting.concat(sponsorTimes ?? []).some(
|
||||
if (!sponsorTimesSubmitting.some(
|
||||
(s) => Math.abs(s.segment[0] - segment.segment[0]) < 1
|
||||
&& Math.abs(s.segment[1] - segment.segment[1]) < 1)
|
||||
&& (segment.category !== "chapter" || utils.getCategorySelection("chapter"))) {
|
||||
@@ -384,7 +377,7 @@ function resetValues() {
|
||||
categoryPill?.setVisibility(false);
|
||||
}
|
||||
|
||||
async function videoIDChange(id): Promise<void> {
|
||||
async function videoIDChange(id: string): Promise<void> {
|
||||
// don't switch to invalid value
|
||||
if (!id && sponsorVideoID && !document?.URL?.includes("youtube.com/clip/")) return;
|
||||
//if the id has not changed return unless the video element has changed
|
||||
@@ -438,10 +431,14 @@ async function videoIDChange(id): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
//close popup
|
||||
closeInfoMenu();
|
||||
// Notify the popup about the video change
|
||||
chrome.runtime.sendMessage({
|
||||
message: "videoChanged",
|
||||
videoID: sponsorVideoID,
|
||||
whitelisted: channelWhitelisted
|
||||
});
|
||||
|
||||
sponsorsLookup(id);
|
||||
sponsorsLookup();
|
||||
|
||||
// Make sure all player buttons are properly added
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
@@ -511,7 +508,7 @@ function createPreviewBar(): void {
|
||||
|
||||
if (el) {
|
||||
const chapterVote = new ChapterVote(voteAsync);
|
||||
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious, chapterVote);
|
||||
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious, chapterVote, () => importExistingChapters(false));
|
||||
|
||||
updatePreviewBar();
|
||||
|
||||
@@ -948,13 +945,13 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
setupVideoMutationListener();
|
||||
|
||||
const showChapterMessage = Config.config.showUpsells
|
||||
&& Config.config.payments.lastCheck !== 0
|
||||
&& Config.config.payments.lastCheck !== 0
|
||||
&& !noRefreshFetchingChaptersAllowed()
|
||||
&& Config.config.showChapterInfoMessage
|
||||
&& Config.config.skipCount > 200;
|
||||
|
||||
if (!showChapterMessage
|
||||
&& Config.config.showChapterInfoMessage
|
||||
if (!showChapterMessage
|
||||
&& Config.config.showChapterInfoMessage
|
||||
&& Config.config.payments.freeAccess) {
|
||||
Config.config.showChapterInfoMessage = false;
|
||||
|
||||
@@ -1004,6 +1001,14 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
?.sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
if (!recievedSegments || !recievedSegments.length) {
|
||||
// return if no video found
|
||||
chrome.runtime.sendMessage({
|
||||
message: "infoUpdated",
|
||||
found: false,
|
||||
status: lastResponseStatus,
|
||||
sponsorTimes: sponsorTimes,
|
||||
time: video.currentTime,
|
||||
onMobileYouTube
|
||||
});
|
||||
retryFetch(404);
|
||||
return;
|
||||
}
|
||||
@@ -1093,6 +1098,16 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
|
||||
importExistingChapters(true);
|
||||
|
||||
// notify popup of segment changes
|
||||
chrome.runtime.sendMessage({
|
||||
message: "infoUpdated",
|
||||
found: sponsorDataFound,
|
||||
status: lastResponseStatus,
|
||||
sponsorTimes: sponsorTimes,
|
||||
time: video.currentTime,
|
||||
onMobileYouTube
|
||||
});
|
||||
|
||||
if (Config.config.isVip) {
|
||||
lockedCategoriesLookup();
|
||||
}
|
||||
@@ -1138,8 +1153,8 @@ async function lockedCategoriesLookup(): Promise<void> {
|
||||
}
|
||||
|
||||
function retryFetch(errorCode: number): void {
|
||||
if (!Config.config.refetchWhenNotFound) return;
|
||||
sponsorDataFound = false;
|
||||
if (!Config.config.refetchWhenNotFound) return;
|
||||
|
||||
if (retryFetchTimeout) clearTimeout(retryFetchTimeout);
|
||||
if ((errorCode !== 404 && retryCount > 1) || (errorCode !== 404 && retryCount > 10)) {
|
||||
@@ -1219,12 +1234,12 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoID(document: Document, url?: string): string | boolean {
|
||||
function getYouTubeVideoID(document: Document, url?: string): string {
|
||||
url ||= document.URL;
|
||||
// pageType shortcut
|
||||
if (pageType === PageType.Channel) return getYouTubeVideoIDFromDocument()
|
||||
if (pageType === PageType.Channel) return getYouTubeVideoIDFromDocument();
|
||||
// clips should never skip, going from clip to full video has no indications.
|
||||
if (url.includes("youtube.com/clip/")) return false;
|
||||
if (url.includes("youtube.com/clip/")) return null;
|
||||
// skip to document and don't hide if on /embed/
|
||||
if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(false, PageType.Embed);
|
||||
// skip to URL if matches youtube watch or invidious or matches youtube pattern
|
||||
@@ -1235,10 +1250,10 @@ function getYouTubeVideoID(document: Document, url?: string): string | boolean {
|
||||
return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(false);
|
||||
}
|
||||
|
||||
function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watch): string | boolean {
|
||||
function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watch): string {
|
||||
const selector = "a.ytp-title-link[data-sessionlink='feature=player-title']";
|
||||
// get ID from document (channel trailer / embedded playlist)
|
||||
const element = pageHint === PageType.Embed ? document.querySelector(selector)
|
||||
const element = pageHint === PageType.Embed ? document.querySelector(selector)
|
||||
: video?.parentElement?.parentElement?.querySelector(selector);
|
||||
const videoURL = element?.getAttribute("href");
|
||||
if (videoURL) {
|
||||
@@ -1247,11 +1262,11 @@ function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watc
|
||||
pageType = pageHint;
|
||||
return getYouTubeVideoIDFromURL(videoURL);
|
||||
} else {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getYouTubeVideoIDFromURL(url: string): string | boolean {
|
||||
function getYouTubeVideoIDFromURL(url: string): string {
|
||||
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
|
||||
|
||||
//Attempt to parse url
|
||||
@@ -1260,7 +1275,7 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
|
||||
urlObject = new URL(url);
|
||||
} catch (e) {
|
||||
console.error("[SB] Unable to parse URL: " + url);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if valid hostname
|
||||
@@ -1274,7 +1289,7 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
|
||||
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoIDFromURL(url)));
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
} else {
|
||||
onInvidious = false;
|
||||
}
|
||||
@@ -1282,17 +1297,17 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
|
||||
//Get ID from searchParam
|
||||
if (urlObject.searchParams.has("v") && ["/watch", "/watch/"].includes(urlObject.pathname) || urlObject.pathname.startsWith("/tv/watch")) {
|
||||
const id = urlObject.searchParams.get("v");
|
||||
return id.length == 11 ? id : false;
|
||||
return id.length == 11 ? id : null;
|
||||
} else if (urlObject.pathname.startsWith("/embed/") || urlObject.pathname.startsWith("/shorts/")) {
|
||||
try {
|
||||
const id = urlObject.pathname.split("/")[2]
|
||||
if (id?.length >=11 ) return id.slice(0, 11);
|
||||
} catch (e) {
|
||||
console.error("[SB] Video ID not valid for " + url);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1403,7 +1418,7 @@ async function whitelistCheck() {
|
||||
* Returns info about the next upcoming sponsor skip
|
||||
*/
|
||||
function getNextSkipIndex(currentTime: number, includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean):
|
||||
{array: ScheduledTime[], index: number, endIndex: number, extraIndexes: number[], openNotice: boolean} {
|
||||
{array: ScheduledTime[]; index: number; endIndex: number; extraIndexes: number[]; openNotice: boolean} {
|
||||
|
||||
const autoSkipSorter = (segment: ScheduledTime) => {
|
||||
const skipOption = utils.getCategorySelection(segment.category)?.option;
|
||||
@@ -1515,7 +1530,7 @@ function getLatestEndTimeIndex(sponsorTimes: SponsorTime[], index: number, hideH
|
||||
* the current time, but end after
|
||||
*/
|
||||
function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean,
|
||||
minimum?: number, hideHiddenSponsors = false): {includedTimes: ScheduledTime[], scheduledTimes: number[]} {
|
||||
minimum?: number, hideHiddenSponsors = false): {includedTimes: ScheduledTime[]; scheduledTimes: number[]} {
|
||||
if (!sponsorTimes) return {includedTimes: [], scheduledTimes: []};
|
||||
|
||||
const includedTimes: ScheduledTime[] = [];
|
||||
@@ -1577,7 +1592,9 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
|
||||
sponsorSkipped[index] = true;
|
||||
if (!counted) {
|
||||
Config.config.minutesSaved = Config.config.minutesSaved + secondsSkipped / 60;
|
||||
Config.config.skipCount = Config.config.skipCount + 1;
|
||||
if (segment.actionType !== ActionType.Chapter) {
|
||||
Config.config.skipCount = Config.config.skipCount + 1;
|
||||
}
|
||||
counted = true;
|
||||
}
|
||||
|
||||
@@ -1749,7 +1766,7 @@ function createButton(baseID: string, title: string, callback: () => void, image
|
||||
}
|
||||
|
||||
function shouldAutoSkip(segment: SponsorTime): boolean {
|
||||
return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
||||
return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
||||
&& (utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
||||
(Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType !== ActionType.Poi));
|
||||
@@ -1792,7 +1809,8 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
|
||||
updateEditButtonsOnPlayer();
|
||||
|
||||
// Don't show the info button on embeds
|
||||
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
|
||||
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious
|
||||
|| document.getElementById("sponsorBlockPopupContainer") != null) {
|
||||
playerButtons.info.button.style.display = "none";
|
||||
} else {
|
||||
playerButtons.info.button.style.removeProperty("display");
|
||||
@@ -1902,11 +1920,15 @@ function isSegmentCreationInProgress(): boolean {
|
||||
|
||||
function cancelCreatingSegment() {
|
||||
if (isSegmentCreationInProgress()) {
|
||||
sponsorTimesSubmitting.splice(sponsorTimesSubmitting.length - 1, 1);
|
||||
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
|
||||
if (sponsorTimesSubmitting.length > 1) { // If there's more than one segment: remove last
|
||||
sponsorTimesSubmitting.pop();
|
||||
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
|
||||
} else { // Otherwise delete the video entry & close submission menu
|
||||
resetSponsorSubmissionNotice();
|
||||
sponsorTimesSubmitting = [];
|
||||
delete Config.config.unsubmittedSegments[sponsorVideoID];
|
||||
}
|
||||
Config.forceSyncUpdate("unsubmittedSegments");
|
||||
|
||||
if (sponsorTimesSubmitting.length <= 0) resetSponsorSubmissionNotice();
|
||||
}
|
||||
|
||||
updateEditButtonsOnPlayer();
|
||||
@@ -2286,11 +2308,16 @@ function windowListenerHandler(event: MessageEvent): void {
|
||||
}
|
||||
|
||||
function updateActiveSegment(currentTime: number): void {
|
||||
previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime);
|
||||
const activeSegments = previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime);
|
||||
chrome.runtime.sendMessage({
|
||||
message: "time",
|
||||
time: currentTime
|
||||
});
|
||||
|
||||
const chapterSegments = activeSegments?.filter((segment) => segment.actionType === ActionType.Chapter);
|
||||
if (chapterSegments?.length > 0) {
|
||||
sendTelemetryAndCount(chapterSegments, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
function nextChapter(): void {
|
||||
|
||||
@@ -6,26 +6,26 @@
|
||||
import { PageType } from "./types";
|
||||
|
||||
interface StartMessage {
|
||||
type: "navigation",
|
||||
pageType: PageType
|
||||
videoID: string | null,
|
||||
type: "navigation";
|
||||
pageType: PageType;
|
||||
videoID: string | null;
|
||||
}
|
||||
|
||||
interface FinishMessage extends StartMessage {
|
||||
channelID: string,
|
||||
channelTitle: string
|
||||
channelID: string;
|
||||
channelTitle: string;
|
||||
}
|
||||
|
||||
interface AdMessage {
|
||||
type: "ad",
|
||||
playing: boolean
|
||||
type: "ad";
|
||||
playing: boolean;
|
||||
}
|
||||
|
||||
interface VideoData {
|
||||
type: "data",
|
||||
videoID: string,
|
||||
isLive: boolean,
|
||||
isPremiere: boolean
|
||||
type: "data";
|
||||
videoID: string;
|
||||
isLive: boolean;
|
||||
isPremiere: boolean;
|
||||
}
|
||||
|
||||
type WindowMessage = StartMessage | FinishMessage | AdMessage | VideoData;
|
||||
|
||||
2
src/globals.d.ts
vendored
2
src/globals.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import { SBObject } from "./config";
|
||||
declare global {
|
||||
interface Window { SB: SBObject; }
|
||||
interface Window { SB: SBObject }
|
||||
// Remove this once the API becomes stable and types are shipped in @types/chrome
|
||||
namespace chrome {
|
||||
namespace declarativeContent {
|
||||
|
||||
@@ -46,6 +46,8 @@ class PreviewBar {
|
||||
segments: PreviewBarSegment[] = [];
|
||||
existingChapters: PreviewBarSegment[] = [];
|
||||
videoDuration = 0;
|
||||
updateExistingChapters: () => void;
|
||||
lastChapterUpdate = 0;
|
||||
|
||||
// For chapter bar
|
||||
hoveredSection: HTMLElement;
|
||||
@@ -58,7 +60,7 @@ class PreviewBar {
|
||||
unfilteredChapterGroups: ChapterGroup[];
|
||||
chapterGroups: ChapterGroup[];
|
||||
|
||||
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, chapterVote: ChapterVote, test=false) {
|
||||
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, chapterVote: ChapterVote, updateExistingChapters: () => void, test=false) {
|
||||
if (test) return;
|
||||
this.container = document.createElement('ul');
|
||||
this.container.id = 'previewbar';
|
||||
@@ -67,6 +69,7 @@ class PreviewBar {
|
||||
this.onMobileYouTube = onMobileYouTube;
|
||||
this.onInvidious = onInvidious;
|
||||
this.chapterVote = chapterVote;
|
||||
this.updateExistingChapters = updateExistingChapters;
|
||||
|
||||
this.updatePageElements();
|
||||
this.createElement(parent);
|
||||
@@ -214,6 +217,16 @@ class PreviewBar {
|
||||
while (this.container.firstChild) {
|
||||
this.container.removeChild(this.container.firstChild);
|
||||
}
|
||||
|
||||
if (this.customChaptersBar) this.customChaptersBar.style.display = "none";
|
||||
this.originalChapterBar?.style?.removeProperty("display");
|
||||
this.chapterVote?.setVisibility(false);
|
||||
|
||||
document.querySelectorAll(`.sponsorBlockChapterBar`).forEach((e) => {
|
||||
if (e !== this.customChaptersBar) {
|
||||
e.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set(segments: PreviewBarSegment[], videoDuration: number): void {
|
||||
@@ -637,11 +650,17 @@ class PreviewBar {
|
||||
|
||||
cursor += sectionWidthDecimal;
|
||||
}
|
||||
|
||||
if (sections.length !== 0 && sections.length !== this.existingChapters?.length
|
||||
&& Date.now() - this.lastChapterUpdate > 3000) {
|
||||
this.lastChapterUpdate = Date.now();
|
||||
this.updateExistingChapters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private findLeftAndScale(selector: string, currentElement: HTMLElement, progressBar: HTMLElement):
|
||||
{ left: number, scale: number } {
|
||||
{ left: number; scale: number } {
|
||||
const sections = currentElement.parentElement.parentElement.parentElement.children;
|
||||
let currentWidth = 0;
|
||||
let lastWidth = 0;
|
||||
@@ -716,7 +735,7 @@ class PreviewBar {
|
||||
}
|
||||
}
|
||||
|
||||
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): void {
|
||||
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): SponsorTime[] {
|
||||
if (!Config.config.showSegmentNameInChapterBar
|
||||
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
|
||||
const chaptersContainer = this.getChaptersContainer();
|
||||
@@ -737,6 +756,7 @@ class PreviewBar {
|
||||
});
|
||||
|
||||
this.setActiveSegments(activeSegments);
|
||||
return activeSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -765,9 +785,14 @@ class PreviewBar {
|
||||
|
||||
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
||||
chapterTitle.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category);
|
||||
if (chosenSegment.actionType !== ActionType.Chapter) {
|
||||
chapterTitle.classList.add("sponsorBlock-segment-title");
|
||||
} else {
|
||||
chapterTitle.classList.remove("sponsorBlock-segment-title");
|
||||
}
|
||||
|
||||
const chapterVoteContainer = this.chapterVote.getContainer();
|
||||
if (chosenSegment.source === SponsorSourceType.Server) {
|
||||
const chapterVoteContainer = this.chapterVote.getContainer();
|
||||
if (!chapterButton.contains(chapterVoteContainer)) {
|
||||
const oldVoteContainers = document.querySelectorAll("#chapterVote");
|
||||
if (oldVoteContainers.length > 0) {
|
||||
|
||||
@@ -9,7 +9,7 @@ interface BaseMessage {
|
||||
}
|
||||
|
||||
interface DefaultMessage {
|
||||
message:
|
||||
message:
|
||||
"update"
|
||||
| "sponsorStart"
|
||||
| "getVideoID"
|
||||
@@ -83,19 +83,19 @@ interface GetVideoIdResponse {
|
||||
videoID: string;
|
||||
}
|
||||
|
||||
interface GetChannelIDResponse {
|
||||
export interface GetChannelIDResponse {
|
||||
channelID: string;
|
||||
}
|
||||
|
||||
interface SponsorStartResponse {
|
||||
export interface SponsorStartResponse {
|
||||
creatingSegment: boolean;
|
||||
}
|
||||
|
||||
interface IsChannelWhitelistedResponse {
|
||||
export interface IsChannelWhitelistedResponse {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export type MessageResponse =
|
||||
export type MessageResponse =
|
||||
IsInfoFoundMessageResponse
|
||||
| GetVideoIdResponse
|
||||
| GetChannelIDResponse
|
||||
@@ -111,7 +111,7 @@ export interface VoteResponse {
|
||||
responseText: string;
|
||||
}
|
||||
|
||||
export interface ImportSegmentsResponse {
|
||||
interface ImportSegmentsResponse {
|
||||
importedSegments: SponsorTime[];
|
||||
}
|
||||
|
||||
@@ -120,4 +120,14 @@ export interface TimeUpdateMessage {
|
||||
time: number;
|
||||
}
|
||||
|
||||
export type PopupMessage = TimeUpdateMessage;
|
||||
export type InfoUpdatedMessage = IsInfoFoundMessageResponse & {
|
||||
message: "infoUpdated";
|
||||
}
|
||||
|
||||
export interface VideoChangedPopupMessage {
|
||||
message: "videoChanged";
|
||||
videoID: string;
|
||||
whitelisted: boolean;
|
||||
}
|
||||
|
||||
export type PopupMessage = TimeUpdateMessage | InfoUpdatedMessage | VideoChangedPopupMessage;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import Config from "./config";
|
||||
import * as CompileConfig from "../config.json";
|
||||
@@ -258,7 +258,8 @@ async function init() {
|
||||
break;
|
||||
}
|
||||
case "keybind-change": {
|
||||
ReactDOM.render(React.createElement(KeybindComponent, {option: option}), optionsElements[i].querySelector("div"));
|
||||
const root = createRoot(optionsElements[i].querySelector("div"));
|
||||
root.render(React.createElement(KeybindComponent, {option: option}));
|
||||
break;
|
||||
}
|
||||
case "display": {
|
||||
|
||||
443
src/popup.ts
443
src/popup.ts
@@ -1,8 +1,24 @@
|
||||
import Config from "./config";
|
||||
|
||||
import Utils from "./utils";
|
||||
import { SponsorTime, SponsorHideType, ActionType, SegmentUUID, SponsorSourceType, StorageChangesObject } from "./types";
|
||||
import { Message, MessageResponse, IsInfoFoundMessageResponse, ImportSegmentsResponse, PopupMessage } from "./messageTypes";
|
||||
import {
|
||||
ActionType,
|
||||
SegmentUUID,
|
||||
SponsorHideType,
|
||||
SponsorSourceType,
|
||||
SponsorTime,
|
||||
StorageChangesObject,
|
||||
} from "./types";
|
||||
import {
|
||||
GetChannelIDResponse,
|
||||
IsChannelWhitelistedResponse,
|
||||
IsInfoFoundMessageResponse,
|
||||
Message,
|
||||
MessageResponse,
|
||||
PopupMessage,
|
||||
SponsorStartResponse,
|
||||
VoteResponse,
|
||||
} from "./messageTypes";
|
||||
import { showDonationLink } from "./utils/configUtils";
|
||||
import { AnimationUtils } from "./utils/animationUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
@@ -11,6 +27,7 @@ import { localizeHtmlPage } from "./utils/pageUtils";
|
||||
import { exportTimes } from "./utils/exporter";
|
||||
import GenericNotice from "./render/GenericNotice";
|
||||
import { noRefreshFetchingChaptersAllowed } from "./utils/licenseKey";
|
||||
|
||||
const utils = new Utils();
|
||||
|
||||
interface MessageListener {
|
||||
@@ -61,14 +78,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
localizeHtmlPage();
|
||||
|
||||
type InputPageElements = {
|
||||
whitelistToggle?: HTMLInputElement,
|
||||
toggleSwitch?: HTMLInputElement,
|
||||
usernameInput?: HTMLInputElement,
|
||||
whitelistToggle?: HTMLInputElement;
|
||||
toggleSwitch?: HTMLInputElement;
|
||||
usernameInput?: HTMLInputElement;
|
||||
};
|
||||
type PageElements = { [key: string]: HTMLElement } & InputPageElements
|
||||
|
||||
/** If true, the content script is in the process of creating a new segment. */
|
||||
let creatingSegment = false;
|
||||
let stopLoadingAnimation = null;
|
||||
|
||||
//the start and end time pairs (2d)
|
||||
let sponsorTimes: SponsorTime[] = [];
|
||||
@@ -188,7 +204,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
}
|
||||
|
||||
PageElements.exportSegmentsButton.addEventListener("click", exportSegments);
|
||||
PageElements.importSegmentsButton.addEventListener("click",
|
||||
PageElements.importSegmentsButton.addEventListener("click",
|
||||
() => PageElements.importSegmentsMenu.classList.toggle("hidden"));
|
||||
PageElements.importSegmentsSubmit.addEventListener("click", importSegments);
|
||||
|
||||
@@ -260,7 +276,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
if (dontShowNotice != undefined && dontShowNotice) {
|
||||
PageElements.showNoticeAgain.style.display = "unset";
|
||||
}
|
||||
|
||||
|
||||
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions"];
|
||||
if (!Config.config.payments.freeAccess && !noRefreshFetchingChaptersAllowed()) values.push("freeChaptersAccess");
|
||||
|
||||
@@ -376,7 +392,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
messageHandler.sendMessage(tabs[0].id, { message: 'getVideoID' }, function (result) {
|
||||
if (result !== undefined && result.videoID) {
|
||||
currentVideoID = result.videoID;
|
||||
creatingSegment = result.creatingSegment;
|
||||
|
||||
loadTabData(tabs, updating);
|
||||
} else if (result === undefined && chrome.runtime.lastError) {
|
||||
@@ -411,7 +426,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
}, (tabs) => onTabs(tabs, updating));
|
||||
}
|
||||
|
||||
function infoFound(request: IsInfoFoundMessageResponse) {
|
||||
async function infoFound(request: IsInfoFoundMessageResponse) {
|
||||
// End any loading animation
|
||||
if (stopLoadingAnimation != null) {
|
||||
stopLoadingAnimation();
|
||||
stopLoadingAnimation = null;
|
||||
}
|
||||
|
||||
if (chrome.runtime.lastError) {
|
||||
//This page doesn't have the injected content script, or at least not yet
|
||||
displayNoVideo();
|
||||
@@ -427,13 +448,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.loadingIndicator.style.display = "none";
|
||||
|
||||
downloadedTimes = request.sponsorTimes ?? [];
|
||||
displayDownloadedSponsorTimes(downloadedTimes, request.time);
|
||||
if (request.found) {
|
||||
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
|
||||
|
||||
PageElements.issueReporterImportExport.classList.remove("hidden");
|
||||
if (request.sponsorTimes) {
|
||||
displayDownloadedSponsorTimes(request.sponsorTimes, request.time);
|
||||
}
|
||||
} else if (request.status == 404 || request.status == 200) {
|
||||
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
|
||||
PageElements.issueReporterImportExport.classList.remove("hidden");
|
||||
@@ -444,62 +462,40 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
}
|
||||
|
||||
//see if whitelist button should be swapped
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{ message: 'isChannelWhitelisted' },
|
||||
function (response) {
|
||||
if (response.value) {
|
||||
PageElements.whitelistChannel.style.display = "none";
|
||||
PageElements.unwhitelistChannel.style.display = "unset";
|
||||
PageElements.whitelistToggle.checked = true;
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
|
||||
}
|
||||
});
|
||||
const response = await sendTabMessageAsync({ message: 'isChannelWhitelisted' }) as IsChannelWhitelistedResponse;
|
||||
if (response.value) {
|
||||
PageElements.whitelistChannel.style.display = "none";
|
||||
PageElements.unwhitelistChannel.style.display = "unset";
|
||||
PageElements.whitelistToggle.checked = true;
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function sendSponsorStartMessage() {
|
||||
async function sendSponsorStartMessage() {
|
||||
//the content script will get the message if a YouTube page is open
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
}, (tabs) => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{ from: 'popup', message: 'sponsorStart' },
|
||||
async (response) => {
|
||||
startSponsorCallback(response);
|
||||
const response = await sendTabMessageAsync({ from: 'popup', message: 'sponsorStart' }) as SponsorStartResponse;
|
||||
startSponsorCallback(response);
|
||||
|
||||
// Perform a second update after the config changes take effect as a workaround for a race condition
|
||||
const removeListener = (listener: typeof lateUpdate) => {
|
||||
const index = Config.configSyncListeners.indexOf(listener);
|
||||
if (index !== -1) Config.configSyncListeners.splice(index, 1);
|
||||
};
|
||||
// Perform a second update after the config changes take effect as a workaround for a race condition
|
||||
const removeListener = (listener: typeof lateUpdate) => {
|
||||
const index = Config.configSyncListeners.indexOf(listener);
|
||||
if (index !== -1) Config.configSyncListeners.splice(index, 1);
|
||||
};
|
||||
|
||||
const lateUpdate = () => {
|
||||
startSponsorCallback(response);
|
||||
removeListener(lateUpdate);
|
||||
};
|
||||
const lateUpdate = () => {
|
||||
startSponsorCallback(response);
|
||||
removeListener(lateUpdate);
|
||||
};
|
||||
|
||||
Config.configSyncListeners.push(lateUpdate);
|
||||
Config.configSyncListeners.push(lateUpdate);
|
||||
|
||||
// Remove the listener after 200ms in case the changes were propagated by the time we got the response
|
||||
setTimeout(() => removeListener(lateUpdate), 200);
|
||||
},
|
||||
);
|
||||
});
|
||||
// Remove the listener after 200ms in case the changes were propagated by the time we got the response
|
||||
setTimeout(() => removeListener(lateUpdate), 200);
|
||||
}
|
||||
|
||||
function startSponsorCallback(response: { creatingSegment: boolean }) {
|
||||
creatingSegment = response.creatingSegment;
|
||||
|
||||
function startSponsorCallback(response: SponsorStartResponse) {
|
||||
// Only update the segments after a segment was created
|
||||
if (!creatingSegment) {
|
||||
if (!response.creatingSegment) {
|
||||
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] || [];
|
||||
}
|
||||
|
||||
@@ -514,7 +510,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.issueReporterTabs.classList.add("hidden");
|
||||
currentSegmentTab = SegmentTab.Segments;
|
||||
} else {
|
||||
if (currentSegmentTab === SegmentTab.Segments
|
||||
if (currentSegmentTab === SegmentTab.Segments
|
||||
&& sponsorTimes.every((segment) => segment.actionType === ActionType.Chapter)) {
|
||||
PageElements.issueReporterTabs.classList.add("hidden");
|
||||
currentSegmentTab = SegmentTab.Chapters;
|
||||
@@ -529,7 +525,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
if (currentSegmentTab === SegmentTab.Segments) {
|
||||
return segment.actionType !== ActionType.Chapter;
|
||||
} else if (currentSegmentTab === SegmentTab.Chapters) {
|
||||
return segment.actionType === ActionType.Chapter
|
||||
return segment.actionType === ActionType.Chapter
|
||||
&& segment.source !== SponsorSourceType.YouTube;
|
||||
} else {
|
||||
return true;
|
||||
@@ -546,7 +542,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
if (downloadedTimes.length > 0) {
|
||||
PageElements.exportSegmentsButton.classList.remove("hidden");
|
||||
} else {
|
||||
} else {
|
||||
PageElements.exportSegmentsButton.classList.add("hidden");
|
||||
}
|
||||
|
||||
@@ -590,7 +586,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
if (downloadedTimes[i].actionType === ActionType.Full) {
|
||||
segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full");
|
||||
} else {
|
||||
segmentTimeFromToNode.innerText = GenericUtils.getFormattedTime(downloadedTimes[i].segment[0], true) +
|
||||
segmentTimeFromToNode.innerText = GenericUtils.getFormattedTime(downloadedTimes[i].segment[0], true) +
|
||||
(actionType !== ActionType.Poi
|
||||
? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(downloadedTimes[i].segment[1], true)
|
||||
: "");
|
||||
@@ -676,26 +672,18 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
downloadedTimes[i].hidden = SponsorHideType.Hidden;
|
||||
}
|
||||
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{
|
||||
message: "hideSegment",
|
||||
type: downloadedTimes[i].hidden,
|
||||
UUID: UUID
|
||||
}
|
||||
);
|
||||
});
|
||||
sendTabMessage({
|
||||
message: "hideSegment",
|
||||
type: downloadedTimes[i].hidden,
|
||||
UUID: UUID
|
||||
})
|
||||
});
|
||||
|
||||
const skipButton = document.createElement("img");
|
||||
skipButton.id = "sponsorTimesSkipButtonContainer" + UUID;
|
||||
skipButton.className = "voteButton";
|
||||
skipButton.src = chrome.runtime.getURL("icons/skip.svg");
|
||||
skipButton.title = actionType === ActionType.Chapter ? chrome.i18n.getMessage("playChapter")
|
||||
skipButton.title = actionType === ActionType.Chapter ? chrome.i18n.getMessage("playChapter")
|
||||
: chrome.i18n.getMessage("skipSegment");
|
||||
skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton));
|
||||
votingButtons.addEventListener("dblclick", () => skipSegment(actionType, UUID));
|
||||
@@ -708,8 +696,9 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) {
|
||||
voteButtonsContainer.appendChild(hideButton);
|
||||
}
|
||||
voteButtonsContainer.appendChild(skipButton);
|
||||
|
||||
if (downloadedTimes[i].actionType !== ActionType.Full) {
|
||||
voteButtonsContainer.appendChild(skipButton);
|
||||
}
|
||||
|
||||
// Will contain request status
|
||||
const voteStatusContainer = document.createElement("div");
|
||||
@@ -732,15 +721,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
|
||||
function submitTimes() {
|
||||
if (sponsorTimes.length > 0) {
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{ message: 'submitTimes' },
|
||||
);
|
||||
});
|
||||
sendTabMessage({ message: 'submitTimes' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,9 +731,16 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
PageElements.showNoticeAgain.style.display = "none";
|
||||
}
|
||||
|
||||
function isCreatingSegment(): boolean {
|
||||
const segments = Config.config.unsubmittedSegments[currentVideoID];
|
||||
if (!segments) return false;
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
return lastSegment && lastSegment?.segment?.length !== 2;
|
||||
}
|
||||
|
||||
/** Updates any UI related to segment editing and submission according to the current state. */
|
||||
function updateSegmentEditingUI() {
|
||||
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart");
|
||||
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(isCreatingSegment() ? "sponsorEnd" : "sponsorStart");
|
||||
|
||||
PageElements.submitTimes.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
|
||||
PageElements.submissionHint.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
|
||||
@@ -771,20 +759,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
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)
|
||||
);
|
||||
}
|
||||
function sendTabMessage(data: Message, callback?) {
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
data,
|
||||
callback
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function sendTabMessageAsync(data: Message): Promise<unknown> {
|
||||
return new Promise((resolve) => sendTabMessage(data, (response) => resolve(response)))
|
||||
}
|
||||
|
||||
//make the options username setting option visible
|
||||
@@ -861,186 +851,122 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
thanksForVotingText.removeAttribute("innerText");
|
||||
}
|
||||
|
||||
function vote(type, UUID) {
|
||||
async function vote(type, UUID) {
|
||||
//add loading info
|
||||
addVoteMessage(chrome.i18n.getMessage("Loading"), UUID);
|
||||
const response = await sendTabMessageAsync({
|
||||
message: "submitVote",
|
||||
type: type,
|
||||
UUID: UUID
|
||||
}) as VoteResponse;
|
||||
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{
|
||||
message: "submitVote",
|
||||
type: type,
|
||||
UUID: UUID
|
||||
}, function (response) {
|
||||
if (response != undefined) {
|
||||
//see if it was a success or failure
|
||||
if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) {
|
||||
//success (treat rate limits as a success)
|
||||
addVoteMessage(chrome.i18n.getMessage("voted"), UUID);
|
||||
} else if (response.successType == -1) {
|
||||
addVoteMessage(GenericUtils.getErrorMessage(response.statusCode, response.responseText), UUID);
|
||||
}
|
||||
setTimeout(() => removeVoteMessage(UUID), 1500);
|
||||
}
|
||||
}
|
||||
);
|
||||
if (response != undefined) {
|
||||
//see if it was a success or failure
|
||||
if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) {
|
||||
//success (treat rate limits as a success)
|
||||
addVoteMessage(chrome.i18n.getMessage("voted"), UUID);
|
||||
} else if (response.successType == -1) {
|
||||
addVoteMessage(GenericUtils.getErrorMessage(response.statusCode, response.responseText), UUID);
|
||||
}
|
||||
setTimeout(() => removeVoteMessage(UUID), 1500);
|
||||
}
|
||||
}
|
||||
|
||||
async function whitelistChannel() {
|
||||
//get the channel url
|
||||
const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse;
|
||||
if (!response.channelID) {
|
||||
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
|
||||
return;
|
||||
}
|
||||
|
||||
//get whitelisted channels
|
||||
let whitelistedChannels = Config.config.whitelistedChannels;
|
||||
if (whitelistedChannels == undefined) {
|
||||
whitelistedChannels = [];
|
||||
}
|
||||
|
||||
//add on this channel
|
||||
whitelistedChannels.push(response.channelID);
|
||||
|
||||
//change button
|
||||
PageElements.whitelistChannel.style.display = "none";
|
||||
PageElements.unwhitelistChannel.style.display = "unset";
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
|
||||
|
||||
//show 'consider force channel check' alert
|
||||
if (!Config.config.forceChannelCheck) PageElements.whitelistForceCheck.classList.remove("hidden");
|
||||
|
||||
//save this
|
||||
Config.config.whitelistedChannels = whitelistedChannels;
|
||||
|
||||
//send a message to the client
|
||||
sendTabMessage({
|
||||
message: 'whitelistChange',
|
||||
value: true
|
||||
});
|
||||
}
|
||||
|
||||
function whitelistChannel() {
|
||||
async function unwhitelistChannel() {
|
||||
//get the channel url
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{ message: 'getChannelID' },
|
||||
function (response) {
|
||||
if (!response.channelID) {
|
||||
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
|
||||
return;
|
||||
}
|
||||
const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse;
|
||||
|
||||
//get whitelisted channels
|
||||
let whitelistedChannels = Config.config.whitelistedChannels;
|
||||
if (whitelistedChannels == undefined) {
|
||||
whitelistedChannels = [];
|
||||
}
|
||||
//get whitelisted channels
|
||||
let whitelistedChannels = Config.config.whitelistedChannels;
|
||||
if (whitelistedChannels == undefined) {
|
||||
whitelistedChannels = [];
|
||||
}
|
||||
|
||||
//add on this channel
|
||||
whitelistedChannels.push(response.channelID);
|
||||
//remove this channel
|
||||
const index = whitelistedChannels.indexOf(response.channelID);
|
||||
whitelistedChannels.splice(index, 1);
|
||||
|
||||
//change button
|
||||
PageElements.whitelistChannel.style.display = "none";
|
||||
PageElements.unwhitelistChannel.style.display = "unset";
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
|
||||
//change button
|
||||
PageElements.whitelistChannel.style.display = "unset";
|
||||
PageElements.unwhitelistChannel.style.display = "none";
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.remove("rotated");
|
||||
|
||||
//show 'consider force channel check' alert
|
||||
if (!Config.config.forceChannelCheck) PageElements.whitelistForceCheck.classList.remove("hidden");
|
||||
//hide 'consider force channel check' alert
|
||||
PageElements.whitelistForceCheck.classList.add("hidden");
|
||||
|
||||
//save this
|
||||
Config.config.whitelistedChannels = whitelistedChannels;
|
||||
//save this
|
||||
Config.config.whitelistedChannels = whitelistedChannels;
|
||||
|
||||
//send a message to the client
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id, {
|
||||
message: 'whitelistChange',
|
||||
value: true
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
//send a message to the client
|
||||
sendTabMessage({
|
||||
message: 'whitelistChange',
|
||||
value: false
|
||||
});
|
||||
}
|
||||
|
||||
function unwhitelistChannel() {
|
||||
//get the channel url
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{ message: 'getChannelID' },
|
||||
function (response) {
|
||||
//get whitelisted channels
|
||||
let whitelistedChannels = Config.config.whitelistedChannels;
|
||||
if (whitelistedChannels == undefined) {
|
||||
whitelistedChannels = [];
|
||||
}
|
||||
|
||||
//remove this channel
|
||||
const index = whitelistedChannels.indexOf(response.channelID);
|
||||
whitelistedChannels.splice(index, 1);
|
||||
|
||||
//change button
|
||||
PageElements.whitelistChannel.style.display = "unset";
|
||||
PageElements.unwhitelistChannel.style.display = "none";
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.remove("rotated");
|
||||
|
||||
//hide 'consider force channel check' alert
|
||||
PageElements.whitelistForceCheck.classList.add("hidden");
|
||||
|
||||
//save this
|
||||
Config.config.whitelistedChannels = whitelistedChannels;
|
||||
|
||||
//send a message to the client
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id, {
|
||||
message: 'whitelistChange',
|
||||
value: false
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
function startLoadingAnimation() {
|
||||
stopLoadingAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
|
||||
}
|
||||
|
||||
function refreshSegments() {
|
||||
const stopAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
|
||||
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
{ message: 'refreshSegments' },
|
||||
(response) => {
|
||||
infoFound(response);
|
||||
stopAnimation();
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
startLoadingAnimation();
|
||||
sendTabMessage({ message: 'refreshSegments' });
|
||||
}
|
||||
|
||||
function skipSegment(actionType: ActionType, UUID: SegmentUUID, element?: HTMLElement): void {
|
||||
if (actionType === ActionType.Chapter) {
|
||||
sendMessage({
|
||||
sendTabMessage({
|
||||
message: "unskip",
|
||||
UUID: UUID
|
||||
});
|
||||
} else {
|
||||
sendMessage({
|
||||
sendTabMessage({
|
||||
message: "reskip",
|
||||
UUID: UUID
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (element) {
|
||||
const stopAnimation = AnimationUtils.applyLoadingAnimation(element, 0.3);
|
||||
stopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(request: Message): void {
|
||||
messageHandler.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, tabs => {
|
||||
messageHandler.sendMessage(
|
||||
tabs[0].id,
|
||||
request
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Should skipping be disabled (visuals stay)
|
||||
*/
|
||||
@@ -1073,10 +999,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
async function importSegments() {
|
||||
const text = (PageElements.importSegmentsText as HTMLInputElement).value;
|
||||
|
||||
await sendTabMessage({
|
||||
sendTabMessage({
|
||||
message: "importSegments",
|
||||
data: text
|
||||
}) as ImportSegmentsResponse;
|
||||
});
|
||||
|
||||
PageElements.importSegmentsMenu.classList.add("hidden");
|
||||
}
|
||||
@@ -1141,6 +1067,27 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
case "time":
|
||||
displayDownloadedSponsorTimes(downloadedTimes, msg.time);
|
||||
break;
|
||||
case "infoUpdated":
|
||||
infoFound(msg);
|
||||
break;
|
||||
case "videoChanged":
|
||||
currentVideoID = msg.videoID
|
||||
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
|
||||
updateSegmentEditingUI();
|
||||
|
||||
if (msg.whitelisted) {
|
||||
PageElements.whitelistChannel.style.display = "none";
|
||||
PageElements.unwhitelistChannel.style.display = "unset";
|
||||
PageElements.whitelistToggle.checked = true;
|
||||
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
|
||||
}
|
||||
|
||||
// Clear segments list & start loading animation
|
||||
// We'll get a ping once they're loaded
|
||||
startLoadingAnimation();
|
||||
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("Loading");
|
||||
displayDownloadedSponsorTimes([], 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import CategoryChooserComponent from "../components/options/CategoryChooserComponent";
|
||||
|
||||
class CategoryChooser {
|
||||
@@ -9,9 +10,9 @@ class CategoryChooser {
|
||||
constructor(element: Element) {
|
||||
this.ref = React.createRef();
|
||||
|
||||
ReactDOM.render(
|
||||
<CategoryChooserComponent ref={this.ref} />,
|
||||
element
|
||||
const root = createRoot(element);
|
||||
root.render(
|
||||
<CategoryChooserComponent ref={this.ref} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import CategoryPillComponent, { CategoryPillState } from "../components/CategoryPillComponent";
|
||||
import Config from "../config";
|
||||
import { VoteResponse } from "../messageTypes";
|
||||
@@ -10,6 +10,7 @@ import { Tooltip } from "./Tooltip";
|
||||
export class CategoryPill {
|
||||
container: HTMLElement;
|
||||
ref: React.RefObject<CategoryPillComponent>;
|
||||
root: Root;
|
||||
|
||||
unsavedState: CategoryPillState;
|
||||
|
||||
@@ -38,10 +39,8 @@ export class CategoryPill {
|
||||
this.unsavedState = this.ref.current.state;
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<CategoryPillComponent ref={this.ref} vote={vote} />,
|
||||
this.container
|
||||
);
|
||||
this.root = createRoot(this.container);
|
||||
this.root.render(<CategoryPillComponent ref={this.ref} vote={vote} />);
|
||||
|
||||
if (this.unsavedState) {
|
||||
this.ref.current?.setState(this.unsavedState);
|
||||
@@ -64,7 +63,7 @@ export class CategoryPill {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.container);
|
||||
this.root.unmount();
|
||||
this.container.remove();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import ChapterVoteComponent, { ChapterVoteState } from "../components/ChapterVoteComponent";
|
||||
import { VoteResponse } from "../messageTypes";
|
||||
import { Category, SegmentUUID, SponsorTime } from "../types";
|
||||
@@ -7,6 +7,7 @@ import { Category, SegmentUUID, SponsorTime } from "../types";
|
||||
export class ChapterVote {
|
||||
container: HTMLElement;
|
||||
ref: React.RefObject<ChapterVoteComponent>;
|
||||
root: Root;
|
||||
|
||||
unsavedState: ChapterVoteState;
|
||||
|
||||
@@ -19,10 +20,8 @@ export class ChapterVote {
|
||||
this.container.id = "chapterVote";
|
||||
this.container.style.height = "100%";
|
||||
|
||||
ReactDOM.render(
|
||||
<ChapterVoteComponent ref={this.ref} vote={vote} />,
|
||||
this.container
|
||||
);
|
||||
this.root = createRoot(this.container);
|
||||
this.root.render(<ChapterVoteComponent ref={this.ref} vote={vote} />);
|
||||
}
|
||||
|
||||
getContainer(): HTMLElement {
|
||||
@@ -30,7 +29,7 @@ export class ChapterVote {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.container);
|
||||
this.root.unmount();
|
||||
this.container.remove();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import NoticeComponent from "../components/NoticeComponent";
|
||||
|
||||
import Utils from "../utils";
|
||||
@@ -9,17 +9,17 @@ import { ButtonListener, ContentContainer } from "../types";
|
||||
import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent";
|
||||
|
||||
export interface TextBox {
|
||||
icon: string,
|
||||
text: string
|
||||
icon: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface NoticeOptions {
|
||||
title: string,
|
||||
referenceNode?: HTMLElement,
|
||||
textBoxes?: TextBox[],
|
||||
buttons?: ButtonListener[],
|
||||
fadeIn?: boolean,
|
||||
timed?: boolean
|
||||
title: string;
|
||||
referenceNode?: HTMLElement;
|
||||
textBoxes?: TextBox[];
|
||||
buttons?: ButtonListener[];
|
||||
fadeIn?: boolean;
|
||||
timed?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
extraClass?: string;
|
||||
maxCountdownTime?: () => number;
|
||||
@@ -35,6 +35,7 @@ export default class GenericNotice {
|
||||
noticeElement: HTMLDivElement;
|
||||
noticeRef: React.MutableRefObject<NoticeComponent>;
|
||||
idSuffix: string;
|
||||
root: Root;
|
||||
|
||||
constructor(contentContainer: ContentContainer, idSuffix: string, options: NoticeOptions) {
|
||||
this.noticeRef = React.createRef();
|
||||
@@ -49,11 +50,13 @@ export default class GenericNotice {
|
||||
|
||||
referenceNode.prepend(this.noticeElement);
|
||||
|
||||
this.update(options);
|
||||
this.root = createRoot(this.noticeElement);
|
||||
|
||||
this.update(options);
|
||||
}
|
||||
|
||||
update(options: NoticeOptions): void {
|
||||
ReactDOM.render(
|
||||
this.root.render(
|
||||
<NoticeComponent
|
||||
noticeTitle={options.title}
|
||||
idSuffix={this.idSuffix}
|
||||
@@ -92,8 +95,7 @@ export default class GenericNotice {
|
||||
</>
|
||||
: null}
|
||||
|
||||
</NoticeComponent>,
|
||||
this.noticeElement
|
||||
</NoticeComponent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ export default class GenericNotice {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.noticeElement);
|
||||
this.root.unmount();
|
||||
|
||||
this.noticeElement.remove();
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
export interface RectangleTooltipProps {
|
||||
text: string,
|
||||
link?: string,
|
||||
referenceNode: HTMLElement,
|
||||
prependElement?: HTMLElement, // Element to append before
|
||||
bottomOffset?: string,
|
||||
leftOffset?: string,
|
||||
timeout?: number,
|
||||
htmlId?: string,
|
||||
maxHeight?: string,
|
||||
maxWidth?: string,
|
||||
backgroundColor?: string,
|
||||
fontSize?: string,
|
||||
text: string;
|
||||
link?: string;
|
||||
referenceNode: HTMLElement;
|
||||
prependElement?: HTMLElement; // Element to append before
|
||||
bottomOffset?: string;
|
||||
leftOffset?: string;
|
||||
timeout?: number;
|
||||
htmlId?: string;
|
||||
maxHeight?: string;
|
||||
maxWidth?: string;
|
||||
backgroundColor?: string;
|
||||
fontSize?: string;
|
||||
buttonFunction?: () => void;
|
||||
}
|
||||
|
||||
export class RectangleTooltip {
|
||||
text: string;
|
||||
container: HTMLDivElement;
|
||||
|
||||
root: Root;
|
||||
timer: NodeJS.Timeout;
|
||||
|
||||
constructor(props: RectangleTooltipProps) {
|
||||
@@ -47,7 +47,8 @@ export class RectangleTooltip {
|
||||
this.timer = setTimeout(() => this.close(), props.timeout * 1000);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
this.root = createRoot(this.container);
|
||||
this.root.render(
|
||||
<div style={{
|
||||
bottom: props.bottomOffset,
|
||||
left: props.leftOffset,
|
||||
@@ -81,13 +82,12 @@ export class RectangleTooltip {
|
||||
|
||||
{chrome.i18n.getMessage("GotIt")}
|
||||
</button>
|
||||
</div>,
|
||||
this.container
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
close(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.container);
|
||||
this.root.unmount();
|
||||
this.container.remove();
|
||||
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import Utils from "../utils";
|
||||
const utils = new Utils();
|
||||
@@ -18,6 +18,7 @@ class SkipNotice {
|
||||
noticeElement: HTMLDivElement;
|
||||
|
||||
skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
|
||||
root: Root;
|
||||
|
||||
constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer, unskipTime: number = null, startReskip = false) {
|
||||
this.skipNoticeRef = React.createRef();
|
||||
@@ -41,7 +42,8 @@ class SkipNotice {
|
||||
|
||||
referenceNode.prepend(this.noticeElement);
|
||||
|
||||
ReactDOM.render(
|
||||
this.root = createRoot(this.noticeElement);
|
||||
this.root.render(
|
||||
<SkipNoticeComponent segments={segments}
|
||||
autoSkip={autoSkip}
|
||||
startReskip={startReskip}
|
||||
@@ -50,8 +52,7 @@ class SkipNotice {
|
||||
closeListener={() => this.close()}
|
||||
smaller={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAll
|
||||
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAutoSkip && autoSkip)}
|
||||
unskipTime={unskipTime} />,
|
||||
this.noticeElement
|
||||
unskipTime={unskipTime} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ class SkipNotice {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.noticeElement);
|
||||
this.root.unmount();
|
||||
|
||||
this.noticeElement.remove();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
|
||||
import Utils from "../utils";
|
||||
const utils = new Utils();
|
||||
@@ -17,6 +17,8 @@ class SubmissionNotice {
|
||||
|
||||
noticeElement: HTMLDivElement;
|
||||
|
||||
root: Root;
|
||||
|
||||
constructor(contentContainer: ContentContainer, callback: () => unknown) {
|
||||
this.noticeRef = React.createRef();
|
||||
|
||||
@@ -30,13 +32,13 @@ class SubmissionNotice {
|
||||
|
||||
referenceNode.prepend(this.noticeElement);
|
||||
|
||||
ReactDOM.render(
|
||||
this.root = createRoot(this.noticeElement);
|
||||
this.root.render(
|
||||
<SubmissionNoticeComponent
|
||||
contentContainer={contentContainer}
|
||||
callback={callback}
|
||||
ref={this.noticeRef}
|
||||
closeListener={() => this.close(false)} />,
|
||||
this.noticeElement
|
||||
closeListener={() => this.close(false)} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,7 +48,7 @@ class SubmissionNotice {
|
||||
|
||||
close(callRef = true): void {
|
||||
if (callRef) this.noticeRef.current.cancel();
|
||||
ReactDOM.unmountComponentAtNode(this.noticeElement);
|
||||
this.root.unmount();
|
||||
|
||||
this.noticeElement.remove();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import { ButtonListener } from "../types";
|
||||
|
||||
export interface TooltipProps {
|
||||
@@ -26,6 +26,7 @@ export class Tooltip {
|
||||
container: HTMLDivElement;
|
||||
|
||||
timer: NodeJS.Timeout;
|
||||
root: Root;
|
||||
|
||||
constructor(props: TooltipProps) {
|
||||
props.bottomOffset ??= "70px";
|
||||
@@ -54,8 +55,9 @@ export class Tooltip {
|
||||
}
|
||||
|
||||
const backgroundColor = `rgba(28, 28, 28, ${props.opacity})`;
|
||||
|
||||
ReactDOM.render(
|
||||
|
||||
this.root = createRoot(this.container);
|
||||
this.root.render(
|
||||
<div style={{bottom: props.bottomOffset, left: props.leftOffset, right: props.rightOffset, backgroundColor}}
|
||||
className={"sponsorBlockTooltip" + (props.displayTriangle ? " sbTriangle" : "") + ` ${props.extraClass}`}>
|
||||
<div>
|
||||
@@ -93,8 +95,7 @@ export class Tooltip {
|
||||
{chrome.i18n.getMessage("GotIt")}
|
||||
</button>
|
||||
: null}
|
||||
</div>,
|
||||
this.container
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,7 +121,7 @@ export class Tooltip {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.container);
|
||||
this.root.unmount();
|
||||
this.container.remove();
|
||||
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import UnsubmittedVideosComponent from "../components/options/UnsubmittedVideosComponent";
|
||||
|
||||
class UnsubmittedVideos {
|
||||
@@ -9,9 +9,9 @@ class UnsubmittedVideos {
|
||||
constructor(element: Element) {
|
||||
this.ref = React.createRef();
|
||||
|
||||
ReactDOM.render(
|
||||
<UnsubmittedVideosComponent ref={this.ref} />,
|
||||
element
|
||||
const root = createRoot(element);
|
||||
root.render(
|
||||
<UnsubmittedVideosComponent ref={this.ref} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
198
src/types.ts
198
src/types.ts
@@ -4,32 +4,32 @@ import SkipNotice from "./render/SkipNotice";
|
||||
|
||||
export interface ContentContainer {
|
||||
(): {
|
||||
vote: (type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) => void,
|
||||
dontShowNoticeAgain: () => void,
|
||||
unskipSponsorTime: (segment: SponsorTime, unskipTime: number, forceSeek?: boolean) => void,
|
||||
sponsorTimes: SponsorTime[],
|
||||
sponsorTimesSubmitting: SponsorTime[],
|
||||
skipNotices: SkipNotice[],
|
||||
v: HTMLVideoElement,
|
||||
sponsorVideoID,
|
||||
reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void,
|
||||
updatePreviewBar: () => void,
|
||||
onMobileYouTube: boolean,
|
||||
sponsorSubmissionNotice: SubmissionNotice,
|
||||
resetSponsorSubmissionNotice: (callRef?: boolean) => void,
|
||||
updateEditButtonsOnPlayer: () => void,
|
||||
previewTime: (time: number, unpause?: boolean) => void,
|
||||
videoInfo: VideoInfo,
|
||||
getRealCurrentTime: () => number,
|
||||
lockedCategories: string[],
|
||||
channelIDInfo: ChannelIDInfo
|
||||
}
|
||||
vote: (type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) => void;
|
||||
dontShowNoticeAgain: () => void;
|
||||
unskipSponsorTime: (segment: SponsorTime, unskipTime: number, forceSeek?: boolean) => void;
|
||||
sponsorTimes: SponsorTime[];
|
||||
sponsorTimesSubmitting: SponsorTime[];
|
||||
skipNotices: SkipNotice[];
|
||||
v: HTMLVideoElement;
|
||||
sponsorVideoID;
|
||||
reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void;
|
||||
updatePreviewBar: () => void;
|
||||
onMobileYouTube: boolean;
|
||||
sponsorSubmissionNotice: SubmissionNotice;
|
||||
resetSponsorSubmissionNotice: (callRef?: boolean) => void;
|
||||
updateEditButtonsOnPlayer: () => void;
|
||||
previewTime: (time: number, unpause?: boolean) => void;
|
||||
videoInfo: VideoInfo;
|
||||
getRealCurrentTime: () => number;
|
||||
lockedCategories: string[];
|
||||
channelIDInfo: ChannelIDInfo;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FetchResponse {
|
||||
responseText: string,
|
||||
status: number,
|
||||
ok: boolean
|
||||
responseText: string;
|
||||
status: number;
|
||||
ok: boolean;
|
||||
}
|
||||
|
||||
export type HashedValue = string & { __hashBrand: unknown };
|
||||
@@ -46,7 +46,7 @@ export enum CategorySkipOption {
|
||||
|
||||
export interface CategorySelection {
|
||||
name: Category;
|
||||
option: CategorySkipOption
|
||||
option: CategorySkipOption;
|
||||
}
|
||||
|
||||
export enum SponsorHideType {
|
||||
@@ -97,95 +97,95 @@ export interface ScheduledTime extends SponsorTime {
|
||||
}
|
||||
|
||||
export interface PreviewBarOption {
|
||||
color: string,
|
||||
opacity: string
|
||||
color: string;
|
||||
opacity: string;
|
||||
}
|
||||
|
||||
|
||||
export interface Registration {
|
||||
message: string,
|
||||
id: string,
|
||||
allFrames: boolean,
|
||||
js: browser.extensionTypes.ExtensionFileOrCode[],
|
||||
css: browser.extensionTypes.ExtensionFileOrCode[],
|
||||
matches: string[]
|
||||
message: string;
|
||||
id: string;
|
||||
allFrames: boolean;
|
||||
js: browser.extensionTypes.ExtensionFileOrCode[];
|
||||
css: browser.extensionTypes.ExtensionFileOrCode[];
|
||||
matches: string[];
|
||||
}
|
||||
|
||||
export interface BackgroundScriptContainer {
|
||||
registerFirefoxContentScript: (opts: Registration) => void,
|
||||
unregisterFirefoxContentScript: (id: string) => void
|
||||
registerFirefoxContentScript: (opts: Registration) => void;
|
||||
unregisterFirefoxContentScript: (id: string) => void;
|
||||
}
|
||||
|
||||
export interface VideoInfo {
|
||||
responseContext: {
|
||||
serviceTrackingParams: Array<{service: string, params: Array<{key: string, value: string}>}>,
|
||||
serviceTrackingParams: Array<{service: string; params: Array<{key: string; value: string}>}>;
|
||||
webResponseContextExtensionData: {
|
||||
hasDecorated: boolean
|
||||
}
|
||||
},
|
||||
hasDecorated: boolean;
|
||||
};
|
||||
};
|
||||
playabilityStatus: {
|
||||
status: string,
|
||||
playableInEmbed: boolean,
|
||||
status: string;
|
||||
playableInEmbed: boolean;
|
||||
miniplayer: {
|
||||
miniplayerRenderer: {
|
||||
playbackMode: string
|
||||
}
|
||||
}
|
||||
playbackMode: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
streamingData: unknown;
|
||||
playbackTracking: unknown;
|
||||
videoDetails: {
|
||||
videoId: string,
|
||||
title: string,
|
||||
lengthSeconds: string,
|
||||
keywords: string[],
|
||||
channelId: string,
|
||||
isOwnerViewing: boolean,
|
||||
shortDescription: string,
|
||||
isCrawlable: boolean,
|
||||
videoId: string;
|
||||
title: string;
|
||||
lengthSeconds: string;
|
||||
keywords: string[];
|
||||
channelId: string;
|
||||
isOwnerViewing: boolean;
|
||||
shortDescription: string;
|
||||
isCrawlable: boolean;
|
||||
thumbnail: {
|
||||
thumbnails: Array<{url: string, width: number, height: number}>
|
||||
},
|
||||
averageRating: number,
|
||||
allowRatings: boolean,
|
||||
viewCount: string,
|
||||
author: string,
|
||||
isPrivate: boolean,
|
||||
isUnpluggedCorpus: boolean,
|
||||
isLiveContent: boolean,
|
||||
thumbnails: Array<{url: string; width: number; height: number}>;
|
||||
};
|
||||
averageRating: number;
|
||||
allowRatings: boolean;
|
||||
viewCount: string;
|
||||
author: string;
|
||||
isPrivate: boolean;
|
||||
isUnpluggedCorpus: boolean;
|
||||
isLiveContent: boolean;
|
||||
};
|
||||
playerConfig: unknown;
|
||||
storyboards: unknown;
|
||||
microformat: {
|
||||
playerMicroformatRenderer: {
|
||||
thumbnail: {
|
||||
thumbnails: Array<{url: string, width: number, height: number}>
|
||||
},
|
||||
thumbnails: Array<{url: string; width: number; height: number}>;
|
||||
};
|
||||
embed: {
|
||||
iframeUrl: string,
|
||||
flashUrl: string,
|
||||
width: number,
|
||||
height: number,
|
||||
flashSecureUrl: string,
|
||||
},
|
||||
iframeUrl: string;
|
||||
flashUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
flashSecureUrl: string;
|
||||
};
|
||||
title: {
|
||||
simpleText: string,
|
||||
},
|
||||
simpleText: string;
|
||||
};
|
||||
description: {
|
||||
simpleText: string,
|
||||
},
|
||||
lengthSeconds: string,
|
||||
ownerProfileUrl: string,
|
||||
externalChannelId: string,
|
||||
availableCountries: string[],
|
||||
isUnlisted: boolean,
|
||||
hasYpcMetadata: boolean,
|
||||
viewCount: string,
|
||||
category: Category,
|
||||
publishDate: string,
|
||||
ownerChannelName: string,
|
||||
uploadDate: string,
|
||||
}
|
||||
simpleText: string;
|
||||
};
|
||||
lengthSeconds: string;
|
||||
ownerProfileUrl: string;
|
||||
externalChannelId: string;
|
||||
availableCountries: string[];
|
||||
isUnlisted: boolean;
|
||||
hasYpcMetadata: boolean;
|
||||
viewCount: string;
|
||||
category: Category;
|
||||
publishDate: string;
|
||||
ownerChannelName: string;
|
||||
uploadDate: string;
|
||||
};
|
||||
};
|
||||
trackingParams: string;
|
||||
attestation: unknown;
|
||||
@@ -205,17 +205,17 @@ export enum ChannelIDStatus {
|
||||
}
|
||||
|
||||
export interface ChannelIDInfo {
|
||||
id: string,
|
||||
status: ChannelIDStatus
|
||||
id: string;
|
||||
status: ChannelIDStatus;
|
||||
}
|
||||
|
||||
export interface SkipToTimeParams {
|
||||
v: HTMLVideoElement,
|
||||
skipTime: number[],
|
||||
skippingSegments: SponsorTime[],
|
||||
openNotice: boolean,
|
||||
forceAutoSkip?: boolean,
|
||||
unskipTime?: number
|
||||
v: HTMLVideoElement;
|
||||
skipTime: number[];
|
||||
skippingSegments: SponsorTime[];
|
||||
openNotice: boolean;
|
||||
forceAutoSkip?: boolean;
|
||||
unskipTime?: number;
|
||||
}
|
||||
|
||||
export interface ToggleSkippable {
|
||||
@@ -232,11 +232,11 @@ export enum NoticeVisbilityMode {
|
||||
}
|
||||
|
||||
export type Keybind = {
|
||||
key: string,
|
||||
code?: string,
|
||||
ctrl?: boolean,
|
||||
alt?: boolean,
|
||||
shift?: boolean
|
||||
key: string;
|
||||
code?: string;
|
||||
ctrl?: boolean;
|
||||
alt?: boolean;
|
||||
shift?: boolean;
|
||||
}
|
||||
|
||||
export enum PageType {
|
||||
@@ -249,6 +249,6 @@ export enum PageType {
|
||||
}
|
||||
|
||||
export interface ButtonListener {
|
||||
name: string,
|
||||
listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
name: string;
|
||||
listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
}
|
||||
@@ -3,11 +3,15 @@ import { checkLicenseKey } from "./utils/licenseKey";
|
||||
import { localizeHtmlPage } from "./utils/pageUtils";
|
||||
|
||||
import * as countries from "../public/res/countries.json";
|
||||
import Utils from "./utils";
|
||||
import { Category, CategorySkipOption } from "./types";
|
||||
|
||||
// This is needed, if Config is not imported before Utils, things break.
|
||||
// Probably due to cyclic dependencies
|
||||
Config.config;
|
||||
|
||||
const utils = new Utils();
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
@@ -31,6 +35,13 @@ async function init() {
|
||||
Config.config.payments.licenseKey = licenseKey;
|
||||
Config.forceSyncUpdate("payments");
|
||||
|
||||
if (!utils.getCategorySelection("chapter")) {
|
||||
Config.config.categorySelections.push({
|
||||
name: "chapter" as Category,
|
||||
option: CategorySkipOption.ShowOverlay
|
||||
});
|
||||
}
|
||||
|
||||
alert(chrome.i18n.getMessage("redeemSuccess"));
|
||||
} else {
|
||||
alert(chrome.i18n.getMessage("redeemFailed"));
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class Utils {
|
||||
/* Used for waitForElement */
|
||||
creatingWaitingMutationObserver = false;
|
||||
waitingMutationObserver: MutationObserver = null;
|
||||
waitingElements: { selector: string, visibleCheck: boolean, callback: (element: Element) => void }[] = [];
|
||||
waitingElements: { selector: string; visibleCheck: boolean; callback: (element: Element) => void }[] = [];
|
||||
|
||||
constructor(backgroundScriptContainer: BackgroundScriptContainer = null) {
|
||||
this.backgroundScriptContainer = backgroundScriptContainer;
|
||||
|
||||
@@ -25,7 +25,7 @@ function applyLoadingAnimation(element: HTMLElement, time: number, callback?: ()
|
||||
});
|
||||
}
|
||||
|
||||
function setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void, show: () => void } {
|
||||
function setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void; show: () => void } {
|
||||
if (enabled) element.classList.add("autoHiding");
|
||||
element.classList.add("hidden");
|
||||
element.classList.add("animationDone");
|
||||
|
||||
@@ -38,7 +38,7 @@ export function importTimes(data: string, videoDuration: number): SponsorTime[]
|
||||
const match = line.match(/(?:((?:\d+:)?\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g);
|
||||
if (match) {
|
||||
const startTime = GenericUtils.getFormattedTimeToSeconds(match[0]);
|
||||
if (startTime) {
|
||||
if (startTime !== null) {
|
||||
const specialCharsMatcher = /^(?:\s+seconds?)?[-:()\s]*|(?:\s+at)?[-:()\s]+$/g
|
||||
const titleLeft = line.split(match[0])[0].replace(specialCharsMatcher, "");
|
||||
let titleRight = null;
|
||||
|
||||
@@ -93,7 +93,7 @@ function getLuminance(color: string): number {
|
||||
}
|
||||
|
||||
/* From https://stackoverflow.com/a/5624139 */
|
||||
function hexToRgb(hex: string): {r: number, g: number, b: number} {
|
||||
function hexToRgb(hex: string): {r: number; g: number; b: number} {
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
|
||||
|
||||
@@ -70,7 +70,8 @@ export function getExistingChapters(currentVideoID: VideoID, duration: number):
|
||||
const chaptersBox = document.querySelector("ytd-macro-markers-list-renderer");
|
||||
|
||||
const chapters: SponsorTime[] = [];
|
||||
if (chaptersBox) {
|
||||
// .ytp-timed-markers-container indicates that key-moments are present, which should not be divided
|
||||
if (chaptersBox && !(document.querySelector(".ytp-timed-markers-container")?.childElementCount > 0)) {
|
||||
let lastSegment: SponsorTime = null;
|
||||
const links = chaptersBox.querySelectorAll("ytd-macro-markers-list-item-renderer > a");
|
||||
for (const link of links) {
|
||||
@@ -78,6 +79,7 @@ export function getExistingChapters(currentVideoID: VideoID, duration: number):
|
||||
const description = link.querySelector("#details h4") as HTMLElement;
|
||||
if (timeElement && description?.innerText?.length > 0 && link.getAttribute("href")?.includes(currentVideoID)) {
|
||||
const time = GenericUtils.getFormattedTimeToSeconds(timeElement.innerText);
|
||||
if (time === null) return [];
|
||||
|
||||
if (lastSegment) {
|
||||
lastSegment.segment[1] = time;
|
||||
|
||||
@@ -6,9 +6,9 @@ import { GenericUtils } from "./genericUtils";
|
||||
const utils = new Utils();
|
||||
|
||||
export interface ChatConfig {
|
||||
displayName: string,
|
||||
composerInitialValue?: string,
|
||||
customDescription?: string
|
||||
displayName: string;
|
||||
composerInitialValue?: string;
|
||||
customDescription?: string;
|
||||
}
|
||||
|
||||
export async function openWarningDialog(contentContainer: ContentContainer): Promise<void> {
|
||||
|
||||
@@ -254,4 +254,25 @@ describe("Import segments", () => {
|
||||
category: "chapter" as Category
|
||||
}]);
|
||||
});
|
||||
|
||||
it ("00:00", () => {
|
||||
const input = ` 00:00 Cap 1
|
||||
00:10 Cap 2
|
||||
00:12 Cap 3`;
|
||||
|
||||
const result = importTimes(input, 8000);
|
||||
expect(result).toMatchObject([{
|
||||
segment: [0, 10],
|
||||
description: "Cap 1",
|
||||
category: "chapter" as Category
|
||||
}, {
|
||||
segment: [10, 12],
|
||||
description: "Cap 2",
|
||||
category: "chapter" as Category
|
||||
}, {
|
||||
segment: [12, 8000],
|
||||
description: "Cap 3",
|
||||
category: "chapter" as Category
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import PreviewBar, { PreviewBarSegment } from "../src/js-components/previewBar";
|
||||
describe("createChapterRenderGroups", () => {
|
||||
let previewBar: PreviewBar;
|
||||
beforeEach(() => {
|
||||
previewBar = new PreviewBar(null, null, null, null, true);
|
||||
previewBar = new PreviewBar(null, null, null, null, null, true);
|
||||
})
|
||||
|
||||
it("Two unrelated times", () => {
|
||||
|
||||
Reference in New Issue
Block a user