diff --git a/manifest/manifest.json b/manifest/manifest.json index 6065f83e..2292923c 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -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", diff --git a/public/_locales/ar/messages.json b/public/_locales/ar/messages.json index e1b7dfca..a5b97cca 100644 --- a/public/_locales/ar/messages.json +++ b/public/_locales/ar/messages.json @@ -25,6 +25,9 @@ "Segments": { "message": "أجزاء" }, + "Chapters": { + "message": "الفصول" + }, "upvoteButtonInfo": { "message": "التصويت على هذا الإرسال" }, diff --git a/public/_locales/fi/messages.json b/public/_locales/fi/messages.json index 5dfc6eea..2fedec6f 100644 --- a/public/_locales/fi/messages.json +++ b/public/_locales/fi/messages.json @@ -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" } } diff --git a/public/_locales/he/messages.json b/public/_locales/he/messages.json index 849a9e0a..c420fefc 100644 --- a/public/_locales/he/messages.json +++ b/public/_locales/he/messages.json @@ -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": "קוד שגיאה: " }, diff --git a/public/_locales/ko/messages.json b/public/_locales/ko/messages.json index 6c9ea1d5..e3c04ece 100644 --- a/public/_locales/ko/messages.json +++ b/public/_locales/ko/messages.json @@ -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}", diff --git a/public/_locales/pl/messages.json b/public/_locales/pl/messages.json index 554dbaec..6cdddc31 100644 --- a/public/_locales/pl/messages.json +++ b/public/_locales/pl/messages.json @@ -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" diff --git a/public/_locales/pt_BR/messages.json b/public/_locales/pt_BR/messages.json index 2bcb2bd0..0b8082ad 100644 --- a/public/_locales/pt_BR/messages.json +++ b/public/_locales/pt_BR/messages.json @@ -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" } } diff --git a/public/_locales/zh_CN/messages.json b/public/_locales/zh_CN/messages.json index dcb13f12..040a4b41 100644 --- a/public/_locales/zh_CN/messages.json +++ b/public/_locales/zh_CN/messages.json @@ -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": "互动提醒(订阅)" }, diff --git a/src/background.ts b/src/background.ts index 3b55743b..9391d610 100644 --- a/src/background.ts +++ b/src/background.ts @@ -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; -} \ No newline at end of file +} diff --git a/src/content.ts b/src/content.ts index b2ab889a..104127fe 100644 --- a/src/content.ts +++ b/src/content.ts @@ -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; @@ -384,7 +377,7 @@ function resetValues() { categoryPill?.setVisibility(false); } -async function videoIDChange(id): Promise { +async function videoIDChange(id: string): Promise { // 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 { } } - //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(); @@ -1013,6 +1010,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; } @@ -1102,6 +1107,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(); } @@ -1147,8 +1162,8 @@ async function lockedCategoriesLookup(): Promise { } 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)) { @@ -1228,12 +1243,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 @@ -1244,7 +1259,7 @@ 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) @@ -1256,11 +1271,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 @@ -1269,7 +1284,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 @@ -1283,7 +1298,7 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean { utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoIDFromURL(url))); } - return false; + return null; } else { onInvidious = false; } @@ -1291,17 +1306,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; } /** @@ -1803,7 +1818,8 @@ async function updateVisibilityOfPlayerControlsButton(): Promise { 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"); diff --git a/src/messageTypes.ts b/src/messageTypes.ts index c48aba34..78cf59cf 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -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; diff --git a/src/popup.ts b/src/popup.ts index 338ecfcd..ede826e2 100644 --- a/src/popup.ts +++ b/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 { @@ -67,8 +84,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { }; 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 { } 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 { 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 { 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 { }, (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 { 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 { } //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 { 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 { 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 { 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 { 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 { 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)); @@ -733,15 +721,7 @@ async function runThePopup(messageListener?: MessageListener): Promise { function submitTimes() { if (sponsorTimes.length > 0) { - messageHandler.query({ - active: true, - currentWindow: true - }, tabs => { - messageHandler.sendMessage( - tabs[0].id, - { message: 'submitTimes' }, - ); - }); + sendTabMessage({ message: 'submitTimes' }) } } @@ -751,9 +731,16 @@ async function runThePopup(messageListener?: MessageListener): Promise { 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"; @@ -772,20 +759,22 @@ async function runThePopup(messageListener?: MessageListener): Promise { chrome.runtime.sendMessage({ "message": "openHelp" }); } - function sendTabMessage(data: Message): Promise { - 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 { + return new Promise((resolve) => sendTabMessage(data, (response) => resolve(response))) } //make the options username setting option visible @@ -862,186 +851,122 @@ async function runThePopup(messageListener?: MessageListener): Promise { 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) */ @@ -1074,10 +999,10 @@ async function runThePopup(messageListener?: MessageListener): Promise { async function importSegments() { const text = (PageElements.importSegmentsText as HTMLInputElement).value; - await sendTabMessage({ + sendTabMessage({ message: "importSegments", data: text - }) as ImportSegmentsResponse; + }); PageElements.importSegmentsMenu.classList.add("hidden"); } @@ -1142,6 +1067,27 @@ async function runThePopup(messageListener?: MessageListener): Promise { 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; } } }