Compare commits

...

354 Commits
4.4.1 ... 5.0.2

Author SHA1 Message Date
Ajay
b7d85ca3c7 Add action type to preview bar test 2022-09-16 02:02:48 -04:00
Ajay
56611598b2 Fix filtered chapter group generation 2022-09-16 01:49:50 -04:00
Ajay
23e0666569 Improved behavior of next chapter keybind with overlap 2022-09-16 01:38:37 -04:00
Ajay
6571bba218 bump version 2022-09-16 01:00:53 -04:00
Ajay
51fc6fde22 Improve next chapter and previous chapter keybind 2022-09-16 00:57:43 -04:00
Ajay
b8d6d4a0b3 Handle preview bar hover without js 2022-09-15 23:37:11 -04:00
Ajay
6381f36a90 Fix hover for first chapter 2022-09-15 23:33:48 -04:00
Ajay
b9ef35dbbe Fix left over from merge conflict causing some skips to be ignored 2022-09-15 23:28:04 -04:00
Ajay
b43e3dab71 Fix doubling up segments in multi segment skip notice 2022-09-15 21:54:03 -04:00
Ajay
901dbb1ecf Fix info button animation 2022-09-15 15:35:38 -04:00
Ajay
68e01fbcc0 Add more checks to prevent double seek bar or no seek bar 2022-09-15 12:46:19 -04:00
Ajay
43d4b7ef18 Fix segments not available when hover preview -> click on same video 2022-09-15 12:10:39 -04:00
Ajay
4a00f3398e Fix last imported chapter not displaying sometimes 2022-09-14 03:40:24 -04:00
Ajay
8054e3d8f2 Fix chapters getting offset when small chapters filtered out 2022-09-14 03:18:41 -04:00
Ajay
b0e1d5e7fa Fix seek bar sometimes becoming empty when one seek section is completely filled 2022-09-14 02:58:22 -04:00
Ajay
d9e723b265 Sync official chapter margin 2022-09-14 02:27:51 -04:00
Ajay
9bb8a0986f Fix preview bar size offset with big mode 2022-09-13 23:59:00 -04:00
Ajay
6418d09039 Fix last preview bar being off 2022-09-13 23:40:16 -04:00
Ajay
afab681a60 Fix too many hover text tooltips 2022-09-13 11:56:23 -04:00
Ajay
8db077887d import segments instead of chapters 2022-09-13 11:52:09 -04:00
Ajay
c06b7857f8 Move to controls to make info button visible in selenium test 2022-09-05 00:32:04 -04:00
Ajay
e798cfdfe3 clarify what needs to be translated 2022-09-05 00:18:28 -04:00
Ajay
0e76342b04 fix typo 2022-09-05 00:17:44 -04:00
Ajay
d91e38fec9 bump version 2022-09-05 00:16:37 -04:00
Ajay
3316072f5d Fix votes appearing for unsubmitted segments 2022-09-05 00:14:23 -04:00
Ajay
4c568212ac Hide custom chapter bar while generating 2022-09-05 00:03:57 -04:00
Ajay
eaa119f152 Make sure original chapter bar that is used is always the right one 2022-09-05 00:01:11 -04:00
Ajay
e7deabe8d9 Properly handle hover previews for chapters and clear old unused ones 2022-09-04 23:57:10 -04:00
Ajay
6d47700ebd Safer document script 2022-09-04 23:14:15 -04:00
Ajay
93c616de23 Prevent some event bubbling issues 2022-09-04 22:04:48 -04:00
Ajay
ee25b41d7e Don't carry over incorrect/harmful vote menu between videos 2022-09-04 21:58:56 -04:00
Ajay
00f134029a Prevent creating multiple chapter vote containers 2022-09-04 21:52:14 -04:00
Ajay
00d625013b Add option to manual skip when a full video segment exists 2022-09-03 23:16:18 -04:00
Ajay
e81ff66dd3 Fix chapter -> full -> chapter not saving times 2022-09-03 21:28:02 -04:00
Ajay
97af12416e Fix copy tooltip 2022-09-03 01:58:22 -04:00
Ajay
bf191dab92 fix react errors about using inherit 2022-09-03 01:09:16 -04:00
Ajay
f8c61b7848 Don't use video before it is set 2022-09-03 00:36:28 -04:00
Ajay
5b136f2da8 Fix crashes on invidious 2022-09-03 00:32:20 -04:00
Ajay
8b80b33810 Don't show empty chapter bar for youtube chapters in popup 2022-09-03 00:27:18 -04:00
Ajay
e3c36ae6e2 Fix the freezing on firefox due to hover preview text 2022-09-03 00:22:03 -04:00
Ajay
533b15f44b Add support for hours in import segments 2022-09-02 21:42:36 -04:00
Ajay Ramachandran
4f0f8655f4 Merge pull request #1425 from mchangrh/contentScriptRebase
rebase document script out of videoInfo
2022-09-02 15:20:40 -04:00
Ajay Ramachandran
668f6856d1 bump version 2022-09-02 15:20:25 -04:00
Ajay
c8e2bb0c13 Auto update hidden categories when redeemed 2022-09-02 14:38:49 -04:00
Ajay
39ed7ea83c Fix license code box 2022-09-02 14:26:41 -04:00
Ajay
f1b2ff801a Fix redirect uri 2022-09-02 13:50:29 -04:00
Ajay Ramachandran
1d9c3a8b80 fix typo 2022-09-02 04:55:17 -04:00
Ajay
29c6151fe3 Ensure channel id is defined before declaring it found 2022-09-02 01:30:13 -04:00
Ajay
1377be9915 Use events for channel id and fallback to current system
Also fix formatting
2022-09-02 01:30:12 -04:00
Michael C
c479a601cd rebase document script out of videoInfo #1312 2022-09-02 01:30:09 -04:00
Ajay Ramachandran
f66a4d25bf Merge pull request #1446 from mini-bomba/clearUnsubmittedSegments
Add a section in options for unsubmitted segments
2022-09-02 00:17:57 -04:00
Ajay
9c7d153f15 Move segment export to backup page and improve margins 2022-09-02 00:15:05 -04:00
mini-bomba
bbea534781 Add "Export segments as URL" option the unsubmitted videos section 2022-09-01 23:03:25 -04:00
mini-bomba
df2586e76d Load segment description from hashparams 2022-09-01 23:03:25 -04:00
mini-bomba
59093cdf21 Move new react components to components/options/, following latest changes 2022-09-01 23:03:25 -04:00
mini-bomba
5f6307041a Add an Export Segments button to the unsubmitted segments list 2022-09-01 23:03:25 -04:00
mini-bomba
26f2143247 Don't force-sync unsubmitted segments when clear confirm prompt is cancelled 2022-09-01 23:03:25 -04:00
mini-bomba
bd292ff886 Split unsubmittedSegmentCounts string into many to account for singular/plural forms of nouns
hopefully with enough context for translators to properly translate...
2022-09-01 23:03:25 -04:00
mini-bomba
9915d46ad4 Add a section in options for unsubmitted segments 2022-09-01 23:03:25 -04:00
Ajay
2b5a02e068 Make required segments thicker in preview bar 2022-09-01 21:35:58 -04:00
Ajay
1f68f512fa Fix linting error 2022-09-01 20:04:53 -04:00
Ajay
d18f7c6195 Make limited width option better 2022-09-01 17:10:01 -04:00
Ajay
015ac7d46e Fix test breaking due to chrome.* api 2022-09-01 16:52:10 -04:00
Ajay
6631dfdea3 Also check license status for submitting chapter 2022-09-01 16:44:02 -04:00
Ajay
212fbb83fe Add tooltip to sort segments 2022-09-01 16:33:34 -04:00
Ajay
9e08d6012c Fix export/import not appearing without segments and without chapter enabled 2022-09-01 16:32:23 -04:00
Ajay
69c0fe1caf Make importer import non chapters too 2022-09-01 16:25:43 -04:00
Ajay
fcecd1163d Improve locked category display 2022-09-01 16:10:57 -04:00
Ajay
29ea112b4f Move hyphen so it is not treated as a range 2022-09-01 16:07:29 -04:00
Ajay Ramachandran
2b96fd5f57 Merge pull request #1001 from ajayyy/chapters
Chapters
2022-09-01 15:24:35 -04:00
Ajay
3e40745621 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-09-01 15:21:23 -04:00
Ajay
c6e30236e9 Add license requirement 2022-09-01 15:15:30 -04:00
Ajay
34c4ecf940 Don't include chapters in time without skips 2022-08-28 23:47:27 -04:00
Ajay
3550c168e2 Fix active segment sometimes disapearing 2022-08-28 23:45:02 -04:00
Ajay
901d6e6c92 Add voting for chapters 2022-08-28 23:38:40 -04:00
Ajay Ramachandran
f05d081cd6 Merge pull request #1459 from mchangrh/no-html-error
catch all html in error messages
2022-08-22 23:21:39 -04:00
Michael C
aadc1be56c catch all html in error messages 2022-08-22 20:44:23 -04:00
Ajay Ramachandran
19e230ea6a Merge pull request #1453 from AlecRust/fix-width-when-embedded
Fix popup width when embedded in page
2022-08-21 22:23:52 -04:00
Alec Rust
bc1263c341 Fix popup width when embedded in page 2022-08-21 11:56:31 +01:00
Ajay
42d76cf257 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-08-19 23:16:45 -04:00
Ajay
d06b7411dc Move options associated with specific categories into their div 2022-08-19 01:26:45 -04:00
Ajay
b14d766ffb Don't show hidden segments in active segment box 2022-08-18 02:44:49 -04:00
Ajay
32ff8db241 Fix buffering sometimes not rendering all the way 2022-08-18 02:39:56 -04:00
Ajay
ea87c8ca24 Fix seek bar progress offsets with custom chapter sections 2022-08-18 02:24:54 -04:00
Michael M. Chang
780ea4a9d0 update wording of Preview to be inline with wiki (#1441)
Co-authored-by: Ajay Ramachandran <dev@ajay.app>
2022-08-17 23:20:16 -04:00
Ajay
6ce4797772 Fix preview bar when video duration innacurate 2022-08-17 01:28:19 -04:00
Ajay
8e738a6097 Fix preview bars rendering incorrectly when native chapters are displayed 2022-08-17 01:21:06 -04:00
Ajay
7d3f86ded1 Fix skipping after paused at zero sometimes not working
Affects some autoplay blocking

Resolves #1437
2022-08-16 16:42:47 -04:00
Ajay
faeb5dede0 Add page for refreshing invidious permissions if it was revoked
Fixes #1354
2022-08-16 16:00:34 -04:00
Ajay Ramachandran
eae8485713 Merge pull request #1422 from mchangrh/localDisableMute
allow disabling mute segments locally
2022-08-14 00:23:29 -04:00
Ajay Ramachandran
87ca0a8a50 Merge pull request #1427 from mini-bomba/theming_fixes
Fix issues with DR & Invidious themes
2022-08-14 00:18:48 -04:00
Ajay
99c5375c6a Handle permission userinfo using new logic 2022-08-14 00:14:59 -04:00
mini-bomba
a62f6ca696 Fix issues with DR & Invidious themes 2022-07-30 00:35:55 +02:00
Michael C
6eb1d5d954 allow disabling mute segments locally 2022-07-29 00:37:02 -04:00
Ajay Ramachandran
81b01ac5cc Merge pull request #1417 from mchangrh/update-dependencies
Update dependencies
2022-07-25 17:18:13 -04:00
Michael C
6f47b66837 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into update-dependencies 2022-07-25 16:11:30 -04:00
Michael C
cf43e04d47 update dependnecies 2022-07-25 16:10:10 -04:00
Ajay
cda57e2d2b Make title link selector more specific
Co-authored-by: Michael C <michael@mchang.name>
2022-07-25 16:03:08 -04:00
Ajay
a9186a35e5 Fix hover previews on load and channel trailers 2022-07-25 02:40:48 -04:00
Ajay
1a6e6279c8 Fix autoskipping when skips load before video 2022-07-24 21:45:25 -04:00
Ajay Ramachandran
522a04eecb bump version 2022-07-21 23:40:26 -04:00
Ajay
d8dfbef1a7 Fix issue with navigator triggering events before document.url updates 2022-07-21 15:14:57 -04:00
Ajay
60ea7190f9 Pin github actions 2022-07-21 00:13:32 -04:00
Ajay
804870f18a Fix new warning ui 2022-07-20 21:07:57 -04:00
Ajay Ramachandran
7c302af207 bump version 2022-07-20 21:03:20 -04:00
Ajay
2cc1dcc6fd Add better UI for warnings allowing you to accept without chatting 2022-07-20 18:48:53 -04:00
Ajay
31cc4b4960 Fix running dev broken 2022-07-20 16:49:23 -04:00
Ajay
af86534992 Fix categories not being able to be disabled 2022-07-19 23:37:42 -04:00
Ajay
0f9122aa1c Fix skipping two segments at the same time for auto skip on music videos 2022-07-19 23:27:32 -04:00
Ajay
d0e35032a5 Include overlap when unmuting if about to autoskip 2022-07-14 17:39:08 -04:00
Ajay Ramachandran
acf26d3127 Merge pull request #1400 from mchangrh/update-deps
update dependencies
2022-07-13 12:25:37 -04:00
Ajay
d352c6efb4 bump version 2022-07-13 12:16:11 -04:00
Ajay
5ff9b10f21 Fix double skip issue 2022-07-13 12:15:56 -04:00
Michael C
80c67d8340 update dependencies 2022-07-12 23:47:22 -04:00
Ajay Ramachandran
3ee2e2517a bump version 2022-07-12 11:43:58 -04:00
Ajay Ramachandran
dd7f227305 Merge pull request #1388 from mchangrh/revert-module-fix-ci
Revert module & fix ci
2022-07-12 11:43:32 -04:00
Ajay Ramachandran
c1d3c7d680 New Crowdin updates (#1393) 2022-07-12 11:42:09 -04:00
Ajay
fae6d0d0cf Add comments 2022-07-12 01:03:28 -04:00
Ajay
60d106fc52 Fix cases with multiple segments starting at the exact same time 2022-07-12 00:59:13 -04:00
Ajay
a4df2eab8f Retry for errors again 2022-07-11 15:00:48 -04:00
Ajay
fdbcf47149 make skip to next chapter go to next endpoint and fix reskip stackoverflow 2022-07-10 21:51:56 -04:00
Ajay
b1ef8a5d47 Don't draw chapters bar when no custom segments 2022-07-10 02:06:26 -04:00
Ajay
4cb6baaff0 Fix chapter import for no segments 2022-07-10 02:02:12 -04:00
Ajay
6cb4fac041 Add hotkeys for skipping to next and previous chapter 2022-07-10 01:58:39 -04:00
Ajay
d7176a9c97 Import chapters with no segments as well 2022-07-10 01:13:53 -04:00
Ajay
2eb0a34858 Always import chapters when segments 2022-07-10 00:56:31 -04:00
Ajay
cf86e91988 Added guideline reminders for 2022-07-09 23:42:52 -04:00
Ajay
058c41dd7e Rename chapter option to show chapter 2022-07-07 17:05:27 -04:00
Ajay
7a50167222 Remove first event check 2022-07-05 16:02:05 -04:00
Ajay
969b303c59 Limit chapter in UI to those who can submit 2022-07-05 13:34:30 -04:00
Ajay
8114e0dcf7 Fix some chapter and mute not skipping from popup 2022-07-04 01:21:36 -04:00
Ajay
561b3a2263 Fix double click skip in popup 2022-07-04 01:21:11 -04:00
Ajay
e0edb63501 Put chapter option higher up 2022-07-04 01:10:41 -04:00
Ajay
70ef867ec5 Don't count skip time for chapter 2022-07-04 01:05:14 -04:00
Ajay
23336fa65b Don't send message if tab not found 2022-07-04 00:49:32 -04:00
Ajay
fea90d024e Made render segments as chapters only affect non chapter segments 2022-07-04 00:43:55 -04:00
Ajay
de85d93602 Hide chapter chevron when it won't do anything 2022-07-03 23:50:18 -04:00
Ajay
e48d956577 Fix segments disapearing when changing skip options 2022-07-03 22:44:32 -04:00
Ajay Ramachandran
efec6a113f bump version 2022-07-03 19:24:24 -04:00
Ajay Ramachandran
0121a2aebd New translations messages.json (German) (#1391) 2022-07-03 19:24:02 -04:00
Ajay
7badfd9b32 Fix existing chapters opening skip notice when chapters disabled 2022-07-03 18:33:32 -04:00
Ajay
d0497d60e8 Add indicator where current player is for segments in popup 2022-07-03 17:53:40 -04:00
Ajay
e223d12520 Fix scrubbing on mobile 2022-07-03 16:42:45 -04:00
Ajay Ramachandran
27e8e83c59 Merge pull request #1389 from mchangrh/export-timestamp
append timestamp to export filename
2022-07-03 00:27:40 -04:00
Ajay Ramachandran
c7f254db70 Use templates 2022-07-03 00:24:28 -04:00
Ajay
85c3cd4a81 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-07-03 00:22:58 -04:00
Ajay
8d9042aeeb Only ignore play event when buffering 2022-07-03 00:22:56 -04:00
Michael C
373edf883d append timestamp to export filename 2022-07-01 02:07:54 -04:00
Michael C
7ed01a181e sort invidiousList for consistency, update from failed CI 2022-06-30 21:47:54 -04:00
Michael C
4119fd8433 revert module conversion 2022-06-30 21:39:28 -04:00
Ajay Ramachandran
cc7d7c0a0c New Crowdin updates (#1368) 2022-06-30 19:54:08 -04:00
Ajay Ramachandran
61b39a99db bump version 2022-06-30 19:53:26 -04:00
Ajay Ramachandran
98f776fa3a Merge pull request #1387 from mchangrh/await-embed
fix embeds not being detected correctly
2022-06-30 17:39:34 -04:00
Michael C
75f426f456 fix embeds not being detected correctly
- add awaiter for key element
- refresh ID with segments if videoID is invalid
2022-06-30 16:38:11 -04:00
Ajay Ramachandran
67b510e628 Merge pull request #1370 from mchangrh/popup-connection-error
Show connectionError string if status is not 404
2022-06-24 17:22:33 -04:00
Michael C
7cc0847db1 store response status 200 2022-06-24 10:49:57 -04:00
Michael C
b92132bf47 implement #1364
show connectionError string if status is not 404
2022-06-24 01:21:19 -04:00
Ajay
cfecb9f94a Better import deduplication 2022-06-23 00:14:21 -04:00
Ajay
fc81e02026 Fix exporter test 2022-06-22 18:58:49 -04:00
Ajay
e12d5ff10a Better category name detection 2022-06-22 18:40:36 -04:00
Ajay
355572ba04 Add warning when chapter name similar to category 2022-06-22 18:10:07 -04:00
Ajay
70731e42a5 Only show import when chapter enabled 2022-06-22 14:03:58 -04:00
Ajay
023b875b0f Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-06-22 13:34:15 -04:00
Ajay
82b027159e Add UI for importing segments 2022-06-22 13:21:44 -04:00
Ajay
6b4da25847 Create source maps in dev 2022-06-22 13:21:15 -04:00
Ajay
529db4d6ca Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-06-22 13:18:49 -04:00
Ajay
d132342ffe Fix popup being out of date and auto closing
Fixes #1359
2022-06-22 13:18:48 -04:00
Ajay
c6405fc0c1 Fix rendering chapters in specific overlapping cases 2022-06-22 13:02:04 -04:00
Ajay
1f6b8f6c53 Add end button for chapters 2022-06-19 15:47:08 -04:00
Ajay Ramachandran
6bdac234b0 bump version 2022-06-19 15:22:48 -04:00
Ajay Ramachandran
337e9680b9 New Crowdin updates (#1346) 2022-06-19 15:22:30 -04:00
Ajay
986630d0a1 Save file on test failure
Fixes #1366
2022-06-18 11:54:52 -04:00
Ajay
ae23bfffe1 Fix whitelist test 2022-06-18 11:34:37 -04:00
Ajay
619683e842 Add test for whitelist 2022-06-17 19:30:53 -04:00
Ajay
1b5ba96bd9 Add full video test 2022-06-17 17:35:50 -04:00
Ajay Ramachandran
bbc5b436e0 Merge pull request #1365 from ajayyy/auto-selenium
Run Selenium tests in GitHub actions
2022-06-17 17:12:05 -04:00
Ajay
91311787df Run headless 2022-06-17 17:06:41 -04:00
Ajay
74e9a98afd Fix selenium tests 2022-06-17 16:42:59 -04:00
Ajay Ramachandran
b369dcc117 Merge pull request #1363 from mchangrh/fix-ci
Fix npm ci failing
2022-06-16 18:01:51 -04:00
Michael C
5a05e01b7d bump package-lock with npm 8.12 2022-06-16 17:48:39 -04:00
Ajay Ramachandran
8097eff9bb Merge pull request #1355 from Argn0/branch3
display "Voted!" message only momentarily
2022-06-15 13:08:02 -04:00
Ajay
c61c97ccad Fix segments with small gaps 2022-06-15 13:05:44 -04:00
Ajay
9a7baa7325 Fix typo 2022-06-15 13:02:53 -04:00
Ajay
5ac577c99b update oss attributions 2022-06-13 14:50:42 -04:00
Ajay
3f421a2fb0 Update node version in workflow 2022-06-13 13:34:39 -04:00
Ajay
e94ce0ffef Fix edit username box too big 2022-06-13 13:28:16 -04:00
Ajay
2dfcf2141f minimum width for username box 2022-06-13 13:26:33 -04:00
Ajay
47220e0abc Fix some popup buttons 2022-06-13 13:16:47 -04:00
Argn0
05eed6ee20 display "Voted!" message only momentarily 2022-06-10 06:13:57 +02:00
Ajay
9eec62d59f Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-06-09 14:02:25 -04:00
Ajay
25c04a49c1 Fix help page not closing from keybind 2022-06-09 14:02:23 -04:00
Ajay Ramachandran
efe6b0483c remove pointer cursor from your work 2022-06-07 22:47:59 -04:00
Ajay
caafba5f53 remove extra line from export 2022-06-06 22:32:02 -04:00
Ajay
32052c17f1 Add notice showing that copy happened 2022-06-06 20:41:15 -04:00
Ajay
32a3cb2cfe Forward key presses from popup 2022-06-06 17:09:34 -04:00
Ajay
d9970bf110 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-06-06 13:18:21 -04:00
Ajay
6514b41418 Fix only bracket wrapping 2022-06-06 13:18:20 -04:00
Ajay Ramachandran
e480e032f2 bump version 2022-06-05 00:09:21 -04:00
Ajay Ramachandran
995001bd91 New Crowdin updates (#1342) 2022-06-05 00:09:14 -04:00
Ajay
5545a516be Added export button 2022-06-04 02:01:12 -04:00
Ajay
0fb2d8df79 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-06-04 01:57:27 -04:00
Ajay
466152ed42 Fix popup copy to clipboard 2022-06-04 01:56:00 -04:00
Ajay
e4855f7427 Fix messed up margin on popup 2022-06-04 01:50:19 -04:00
Ajay
b28d881a1b Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-06-03 15:12:42 -04:00
Ajay
258abd5deb Fix jest errors not displaying in console 2022-06-03 15:12:35 -04:00
Ajay
b8cbdb55d5 Fix test error 2022-06-03 15:12:14 -04:00
Ajay Ramachandran
4e7975a9de bump version 2022-06-03 13:36:40 -04:00
Ajay Ramachandran
ec8d5d88e9 New translations messages.json (Chinese Traditional) (#1341) 2022-06-03 13:35:41 -04:00
Ajay
94fa649a17 Fix lint error 2022-06-03 13:34:41 -04:00
Ajay Ramachandran
71653572aa New Crowdin updates (#1332) 2022-06-03 02:21:18 -04:00
Ajay
c3cb450e92 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-06-03 02:20:30 -04:00
Ajay Ramachandran
a18f431e06 Merge pull request #1300 from PickleNik/master
Popup Styling Upgrades
2022-06-03 01:59:56 -04:00
Ajay
56f7b40585 Fix code formatting 2022-06-03 01:57:55 -04:00
Ajay
a5f263dc12 Make logo in same line in popup 2022-06-03 01:53:29 -04:00
Ajay
3415dafb02 Revert changes to options button and close button 2022-06-03 01:45:47 -04:00
Ajay
9664bbea73 Change alignment of some popup elements 2022-06-03 01:38:32 -04:00
Ajay
c7b07ba958 Fix localize function not working for title of direct children 2022-06-02 22:00:06 -04:00
Ajay
20b95887af Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/PickleNik/1300 2022-06-02 21:48:05 -04:00
Ajay Ramachandran
47d4812b92 Merge pull request #1322 from NDevTK/master
Move popup to iframe and restrict embeds
2022-06-02 21:42:19 -04:00
Ajay
6202a4d0a3 Only show close button on in page popup 2022-06-02 21:38:27 -04:00
Ajay
6b584f2111 Faster popup loading on firefox 2022-06-02 21:38:26 -04:00
Ajay
3d9221eb8d improve display while popup loads 2022-06-02 21:38:26 -04:00
Ajay
96173dd901 Fix popup communication on Firefox 2022-06-02 21:38:21 -04:00
Ajay
5820758563 Fix close button layout issues 2022-06-02 21:37:32 -04:00
NDevTK
bfadb1373a Add comment 2022-06-02 21:37:21 -04:00
NDevTK
a7956aacf9 Remove unused sendRequestToCustomServer 2022-06-02 21:37:21 -04:00
NDevTK
afee681575 Removed runThePopup 2022-06-02 21:37:14 -04:00
NDevTK
b5482b6527 Move popup to iframe 2022-06-02 21:36:54 -04:00
Ajay Ramachandran
ff87a42147 Merge pull request #1330 from bershanskiy/history
Use Navigation API when available
2022-06-02 02:30:57 -04:00
Ajay
7e5130c49a Don't wait to add listener 2022-06-02 01:43:31 -04:00
Ajay
a7b18bca87 Fix grammar 2022-05-31 18:29:46 -04:00
Ajay Ramachandran
a2ead6bcc4 Merge pull request #1333 from Argn0/debouncecolor
Debounce setting bar color
2022-05-31 13:40:07 -04:00
Ajay Ramachandran
4a2614349d Merge pull request #1338 from ajayyy/guidelines
Add initial sponsor help page
2022-05-31 13:38:27 -04:00
Ajay
67c63a09fe Delete items if exceeding storage 2022-05-31 13:31:18 -04:00
Ajay
97aea1c268 Fix icon sizes 2022-05-31 13:12:01 -04:00
Ajay
b70f50e1a0 Fix skip notice width limiting 2022-05-31 03:06:48 -04:00
Ajay
4880227320 Fix blank skip notice title when unskipping 2022-05-31 02:58:36 -04:00
Ajay
dfa21068f4 Fix buttons being offset on guidelines notice 2022-05-31 02:55:44 -04:00
Ajay
fd75a33e50 Use full details instead of learn more 2022-05-31 02:55:32 -04:00
Ajay
1409c563e2 Add guidelines for all categories 2022-05-31 02:37:29 -04:00
Ajay
588c42d579 Add initial sponsor help page 2022-05-31 00:50:32 -04:00
Ajay
d7ff6aa6a2 Add user to channel id regex 2022-05-26 22:25:16 -04:00
Ajay
44266e508b Another fix for firefox addon icon 2022-05-26 15:56:21 -04:00
Anton Bershanskiy
9b9ea39260 Use History API when available 2022-05-26 15:38:18 +03:00
Argn0
31b10f9a41 change timeout to 50ms 2022-05-25 20:27:27 +02:00
Ajay
c0f8f5e1d0 bump version 2022-05-25 14:16:45 -04:00
Argn0
0261d36a47 debounce setting bar color 2022-05-25 15:09:40 +02:00
Ajay Ramachandran
e948e1e569 Merge pull request #1331 from mchangrh/updateDependencies
update dependencies & bump minimum node version
2022-05-25 00:01:10 -04:00
Ajay
5289e62d43 Use virtual time for determining whether to skip and increase skip buffer
Potentially fix recent issue with skips not triggering
2022-05-24 23:54:05 -04:00
Ajay
eda7aac5ef Add time to log messages 2022-05-24 23:31:43 -04:00
Michael C
d9f4ab0d04 specify jest as cjs, change eslintrc to .json 2022-05-24 21:23:05 -04:00
Michael C
2665327729 update dependencies & bump minimum node version (#1329)
- bump all dependences
- add github-actions reporter to jest
- convert (most) webpack configs to ESM
  - manifest.cjs cannot be converted since there is no native .json import until node 17
2022-05-24 21:14:36 -04:00
Ajay
4738c1897f bump version 2022-05-24 14:05:13 -04:00
Ajay
cfbb194a61 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-05-24 14:04:54 -04:00
Ajay
9ad21e8ea1 Fix channel id regex 2022-05-24 14:04:53 -04:00
Ajay Ramachandran
941fc2985d Merge pull request #1238 from AlecRust/fix-unused-vars-lint
Fix @typescript-eslint/no-unused-vars lint warnings
2022-05-24 13:17:27 -04:00
Ajay Ramachandran
e2da071761 bump version 2022-05-24 13:16:04 -04:00
Ajay Ramachandran
c20c9ac64a Merge pull request #1220 from tadwohlrapp/patch-1
Fix minor typos and inconsistencies
2022-05-24 13:12:20 -04:00
Ajay Ramachandran
2d59f3825c New translations messages.json (Swedish) (#1326) 2022-05-24 13:09:34 -04:00
Ajay Ramachandran
fb8b7a7d19 Merge pull request #1327 from Argn0/branch1
Add reset settings option
2022-05-24 12:56:50 -04:00
Ajay
d992cc7a3c Don't reset everything 2022-05-24 12:55:17 -04:00
Ajay
f8fecf5174 Remove redundant explanation 2022-05-24 12:50:39 -04:00
Argn0
3007cddce9 add reset settings option 2022-05-24 10:04:56 +02:00
Ajay
2b3c812f8a Some more debug logs 2022-05-23 21:41:35 -04:00
Ajay
61535fac95 Add more logging for catching skip issues 2022-05-23 01:14:46 -04:00
Ajay
4182595436 Don't stop skipping if channel id fetch fails 2022-05-21 16:54:02 -04:00
Ajay
97e30e4001 bump version 2022-05-20 04:19:20 -04:00
Ajay
6763fd3b4b Allow more channel IDs
Help with #753
2022-05-20 04:01:50 -04:00
Ajay
a39ec76340 Fix skip to highlight option on mobile 2022-05-19 19:28:26 -04:00
Ajay
f68282decc Add more verbose logging to hidden variable 2022-05-19 19:22:59 -04:00
Ajay
160de56a71 Fix icon on android firefox 2022-05-19 13:07:44 -04:00
Ajay
621e28c7e7 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-05-17 14:44:54 -04:00
Nikita Krupin
50aaea0dd2 Merge branch 'master' into master 2022-05-04 18:56:32 -04:00
Nikita Krupin
ccc1f4cad1 popup.css organization in topological order 2022-05-03 00:05:04 -04:00
Nikita Krupin
42511cb667 popup.html code cleanup & simplification 2022-05-02 22:26:51 -04:00
Nikita Krupin
123d7af5eb mainControls better spacing 2022-05-02 21:32:54 -04:00
Nikita Krupin
44f2de42db tooltip spacing unit fixes 2022-05-02 21:28:22 -04:00
Nikita Krupin
e4dd0f1ac6 popup styling requested adjustments 2022-05-02 21:22:25 -04:00
Nikita Krupin
9fdce8e814 popup link transition fix 2022-05-01 23:07:03 -04:00
Nikita Krupin
813b2df1ac popup styling tweaks 2022-05-01 22:50:58 -04:00
Alec Rust
463ce258bf Fix @typescript-eslint/no-unused-vars lint warnings 2022-04-23 15:44:21 +01:00
Ajay
9f02bf4ce2 Show both chapter names when small chapter in big chapter 2022-04-21 14:54:57 -04:00
Ajay
6325d3539c Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-04-11 01:26:39 -04:00
Ajay Ramachandran
9477ad425c Merge branch 'master' into chapters 2022-03-12 16:56:53 -05:00
Tad Wohlrapp
a64deb2e18 Fix minor typos and inconsistencies 2022-03-08 19:57:09 +01:00
Ajay
f81cfbecfe Add functions for importing/exporting segments 2022-02-26 01:07:29 -05:00
Ajay
eb35f5c543 Don't display existing chapters twice 2022-02-24 21:53:45 -05:00
Ajay
7d8188d575 Add importing chapters for cases with unsubmitted segments 2022-02-24 02:21:22 -05:00
Ajay
00ab317a3e Fix active segments when no real segments 2022-02-24 02:06:00 -05:00
Ajay
0536d419e5 Hide YouTube chapters from popup 2022-02-23 01:39:53 -05:00
Ajay
4d5c9005ae Fix stackoverflow 2022-02-23 01:37:19 -05:00
Ajay
0513a36a9a Fix chapters getting imported multiple times 2022-02-23 01:30:07 -05:00
Ajay
1ec184048c Fix chapter sorting 2022-02-23 01:24:15 -05:00
Ajay
2b811c5ab4 Include unsubmitted in active segment label 2022-02-23 01:12:19 -05:00
Ajay
ea91701430 Improve chapter generation performance by reusing elements
Also ensure large segments don't break chapter bar
2022-02-23 01:06:08 -05:00
Ajay
9654fabc3c remove old todo 2022-02-22 21:34:27 -05:00
Ajay
2ebc5489cd Load existing chapters 2022-02-22 21:22:30 -05:00
Ajay
cf3b3c5c48 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-02-22 01:34:12 -05:00
Ajay
930bc113fe Add sort segments button 2022-02-21 11:17:58 -05:00
Ajay
7aaa28b5c2 Fix transition issue 2022-02-21 01:02:23 -05:00
Ajay
bd3976e4c6 Add option to not render as chapters 2022-02-21 00:29:13 -05:00
Ajay
4e5a883d2e Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-02-20 18:37:18 -05:00
Ajay
e1de84dce3 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-01-21 20:20:27 -05:00
Ajay
9ce714fd36 Merge branch 'chapters' of https://github.com/ajayyy/SponsorBlock into chapters 2022-01-21 20:18:43 -05:00
Ajay
46983bec24 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-01-21 20:18:42 -05:00
Ajay
9d65df84be Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-01-21 20:17:34 -05:00
Ajay
e2d56d32fe Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2022-01-16 15:58:09 -05:00
Ajay
7895b9d2c1 Fix crash from preview bar not being defined yet 2022-01-02 22:44:15 -05:00
Ajay
63f6702f86 Fix chapter bar breaking when adding new unsubmitted segments 2021-12-30 01:08:54 -05:00
Ajay
02bc554b0e Fix progress not starting at full 2021-12-30 01:05:59 -05:00
Ajay
c3933a4eee reduce returned variables 2021-12-30 00:39:03 -05:00
Ajay
68c1f780d5 Fix sometimes not rendering chapters when no existing chapters 2021-12-30 00:35:39 -05:00
Ajay
496ef87a28 Reduce issues in rendering over existing chapters by replacing walking method with direct loop 2021-12-30 00:22:13 -05:00
Ajay
22e85f715d Add initial code to support drawing when there are existing chapters 2021-12-29 02:16:49 -05:00
Ajay
1a6a07744e Don't break chapter bar when existing chapters are there 2021-12-26 01:18:55 -05:00
Ajay
4a19fececf Always set segment source 2021-12-26 00:17:49 -05:00
Ajay
322a1483df Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2021-12-25 22:14:34 -05:00
Ajay
ab1520c560 Show both categories and chapter names in hover text 2021-12-25 01:09:34 -05:00
Ajay
e4f4a10965 Fix seek bar chapter name not updating and schedule end times 2021-12-24 21:52:33 -05:00
Ajay
05153a152d Add skip button to popup for segments and chapters 2021-12-24 20:35:36 -05:00
Ajay
798fd8b3f3 Add tabs for chapters and other segments 2021-12-24 02:13:25 -05:00
Ajay Ramachandran
c38cc07e0a Fix stack overflow issue with unfinished preview segments 2021-11-07 16:09:01 -05:00
Ajay Ramachandran
af547ce745 Show category description in popup 2021-11-07 15:38:41 -05:00
Ajay Ramachandran
0d0459a3a3 Make tests have config.json 2021-11-07 15:31:08 -05:00
Ajay Ramachandran
7dfee81188 Add chapter sorting method to show small chapters in the middle of large ones 2021-11-07 15:26:00 -05:00
Ajay Ramachandran
3a2d9c0e0e Update options page for chapters 2021-11-07 01:12:01 -05:00
Ajay Ramachandran
8e022bfb28 Fix offset on popup for chapters 2021-11-07 01:06:34 -05:00
Ajay Ramachandran
a69c19581d Fix chapter bar showing as empty sometimes 2021-11-07 01:28:23 -04:00
Ajay Ramachandran
a3e67b6cde Add chapter name autocomplete 2021-11-07 01:05:32 -04:00
Ajay Ramachandran
9e6e3b023d Fix small time differences between segments causing issues 2021-11-06 21:20:36 -04:00
Ajay Ramachandran
33cfe3f5d3 Show description in hover bar 2021-11-06 19:57:46 -04:00
Ajay Ramachandran
4a2ebe4b03 Fix undefined error when making segment 2021-11-04 00:34:21 -04:00
Ajay Ramachandran
374f0992ff Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2021-11-04 00:31:29 -04:00
Ajay Ramachandran
7814493974 Fix preview bar width and remove chapter refresh check 2021-11-04 00:25:58 -04:00
Ajay Ramachandran
f5fa758ac1 Don't shrink preview bar if segment not a chapter 2021-11-02 23:33:19 -04:00
Ajay Ramachandran
517e53a2e3 Fix small segments breaking chapters 2021-11-01 21:44:39 -04:00
Ajay Ramachandran
fb3635cdf8 Fix animation when chapter bar is recreated 2021-11-01 21:18:03 -04:00
Ajay Ramachandran
a804da06f5 Update chapter bar progress right after clone 2021-11-01 21:10:43 -04:00
Ajay Ramachandran
9ed9f9b873 Only create one chapter bar, support videos with no segments and preview segments 2021-11-01 20:59:04 -04:00
Ajay Ramachandran
b4a2f31520 Render preview bar behind scrubber 2021-10-30 21:48:52 -04:00
Ajay Ramachandran
c7acb902a4 Fix issues with hover failing after hovering scrubber 2021-10-30 21:32:54 -04:00
Ajay Ramachandran
37ac5c8cbd Listen for class changes for ytp-hover-progress-light and fix left 2021-10-30 15:11:22 -04:00
Ajay Ramachandran
bf4eb8fafc Fix z-index not applying 2021-10-29 00:02:46 -04:00
Ajay Ramachandran
7c4a0628b7 Add growing chapter on hover 2021-10-28 23:57:53 -04:00
Ajay Ramachandran
2d3e293d83 Support left style changes for chapters bar and fix negative size 2021-10-28 00:38:46 -04:00
Ajay Ramachandran
6dee56dc95 Add mutation listener to update progress indicators 2021-10-27 23:21:22 -04:00
Ajay Ramachandran
4c9548b303 Add basic chapter rendering for segments 2021-10-26 20:18:08 -04:00
Ajay Ramachandran
fd69e91880 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2021-10-17 19:25:16 -04:00
Ajay Ramachandran
0f4eeb4fe9 Add chapter name option when submitting 2021-10-16 01:36:44 -04:00
Ajay Ramachandran
9a24b906f9 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2021-10-15 19:57:18 -04:00
Ajay Ramachandran
496528be65 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters 2021-10-14 21:58:37 -04:00
Ajay Ramachandran
f2c1ee4894 Use default action type from config 2021-10-14 00:03:48 -04:00
Ajay Ramachandran
acd2720372 Add messages for chapter 2021-10-13 23:50:17 -04:00
Ajay Ramachandran
e20b60979c Add initial chapter name rendering 2021-10-12 23:33:41 -04:00
128 changed files with 15298 additions and 12301 deletions

View File

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

33
.eslintrc.json Normal file
View File

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

View File

@@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
node-version: '18'
- run: npm ci
- name: Copy configuration
run: cp config.json.example config.json
@@ -30,7 +30,7 @@ jobs:
name: ChromeExtension
path: dist
- run: mkdir ./builds
- uses: montudor/action-zip@v1
- uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with:
args: zip -qq -r ./builds/ChromeExtension.zip ./dist
@@ -41,7 +41,7 @@ jobs:
with:
name: FirefoxExtension
path: dist
- uses: montudor/action-zip@v1
- uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with:
args: zip -qq -r ./builds/FirefoxExtension.zip ./dist
@@ -52,7 +52,7 @@ jobs:
with:
name: ChromeExtensionBeta
path: dist
- uses: montudor/action-zip@v1
- uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with:
args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist
@@ -62,7 +62,7 @@ jobs:
with:
name: FirefoxExtensionBeta
path: dist
- uses: montudor/action-zip@v1
- uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with:
args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist

View File

@@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
node-version: '18'
- run: npm ci
- name: Copy configuration
run: cp config.json.example config.json

View File

@@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: take the issue
uses: bdougie/take-action@main
uses: bdougie/take-action@28b86cd8d25593f037406ecbf96082db2836e928
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@@ -3,8 +3,7 @@ name: Tests
on: [push, pull_request]
jobs:
build:
test:
name: Run tests
runs-on: ubuntu-latest
@@ -13,8 +12,18 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
node-version: '18'
- run: npm ci
- run: sudo apt-get install chromium-chromedriver
- name: Copy configuration
run: cp config.json.example config.json
- name: Run tests
run: npm run test-without-building
run: npm run test
- name: Upload results on fail
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: Test Results
path: ./test-results

View File

@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
node-version: '18'
- name: Install and generate attribution
run: |
npm ci
@@ -25,7 +25,7 @@ jobs:
mv ./oss-attribution/attribution.txt ./public/oss-attribution/attribution.txt
- name: Create pull request to update list
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
with:
commit-message: Update OSS Attribution
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

View File

@@ -19,7 +19,7 @@ jobs:
run: npm run ci:invidious
- name: Create pull request to update list
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
with:
commit-message: Update Invidious List
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

3
.gitignore vendored
View File

@@ -7,4 +7,5 @@ web-ext-artifacts
dist/
tmp/
.DS_Store
ci/data.json
ci/data.json
test-results

View File

@@ -49,7 +49,7 @@ const reliableCheck = mapped
.filter(instance => instance.url.includes(instance.name))
// finally map to array
const result: string[] = reliableCheck.map(instance => instance.name)
const result: string[] = reliableCheck.map(instance => instance.name).sort()
writeFile(join(__dirname, "./invidiouslist.json"), JSON.stringify(result), (err) => {
if (err) return console.log(err);
})

View File

@@ -1 +1 @@
["yewtu.be","vid.puffyan.us","invidious.snopyta.org","inv.riverside.rocks","invidious-us.kavin.rocks","invidious.osi.kr","tube.cthd.icu","invidious.flokinet.to","yt.artemislena.eu","invidious.mutahar.rocks","invidious.esmailelbob.xyz","youtube.076.ne.jp","invidious.weblibre.org","invidious.namazso.eu","invidious.kavin.rocks"]
["inv.cthd.icu","inv.riverside.rocks","invidio.xamh.de","invidious.kavin.rocks","invidious.namazso.eu","invidious.osi.kr","invidious.snopyta.org","vid.puffyan.us","yewtu.be","youtube.076.ne.jp","yt.artemislena.eu"]

View File

@@ -2,7 +2,7 @@
"serverAddress": "https://sponsor.ajay.app",
"testingServerAddress": "https://sponsor.ajay.app/test",
"serverAddressComment": "This specifies the default SponsorBlock server to connect to",
"categoryList": ["sponsor", "selfpromo", "exclusive_access", "interaction", "poi_highlight", "intro", "outro", "preview", "filler", "music_offtopic"],
"categoryList": ["sponsor", "selfpromo", "exclusive_access", "interaction", "poi_highlight", "intro", "outro", "preview", "filler", "chapter", "music_offtopic"],
"categorySupport": {
"sponsor": ["skip", "mute", "full"],
"selfpromo": ["skip", "mute", "full"],
@@ -13,7 +13,8 @@
"preview": ["skip", "mute"],
"filler": ["skip", "mute"],
"music_offtopic": ["skip"],
"poi_highlight": ["poi"]
"poi_highlight": ["poi"],
"chapter": ["chapter"]
},
"wikiLinks": {
"sponsor": "https://wiki.sponsor.ajay.app/w/Sponsor",
@@ -27,6 +28,7 @@
"music_offtopic": "https://wiki.sponsor.ajay.app/w/Music:_Non-Music_Section",
"poi_highlight": "https://wiki.sponsor.ajay.app/w/Highlight",
"guidelines": "https://wiki.sponsor.ajay.app/w/Guidelines",
"mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment"
"mute": "https://wiki.sponsor.ajay.app/w/Mute_Segment",
"chapter": "https://wiki.sponsor.ajay.app/w/Chapter"
}
}

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
"version": "4.4.1",
"version": "5.0.2",
"default_locale": "en",
"description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app",
@@ -18,6 +18,7 @@
],
"css": [
"content.css",
"shared.css",
"./libs/Source+Sans+Pro.css",
"popup.css"
]
@@ -34,6 +35,7 @@
"icons/settings.svg",
"icons/pencil.svg",
"icons/check.svg",
"icons/check-smaller.svg",
"icons/upvote.png",
"icons/downvote.png",
"icons/thumbs_down.svg",
@@ -47,13 +49,28 @@
"icons/beep.ogg",
"icons/pause.svg",
"icons/stop.svg",
"icons/skip.svg",
"icons/heart.svg",
"icons/visible.svg",
"icons/not_visible.svg",
"icons/sort.svg",
"icons/money.svg",
"icons/segway.png",
"icons/close-smaller.svg",
"icons/right-arrow.svg",
"icons/campaign.svg",
"icons/star.svg",
"icons/lightbulb.svg",
"icons/bolt.svg",
"icons/stopwatch.svg",
"icons/music-note.svg",
"icons/import.svg",
"icons/export.svg",
"icons/PlayerInfoIconSponsorBlocker.svg",
"icons/PlayerDeleteIconSponsorBlocker.svg",
"popup.html",
"content.css"
"content.css",
"js/document.js"
],
"permissions": [
"storage",
@@ -64,7 +81,35 @@
],
"browser_action": {
"default_title": "SponsorBlock",
"default_popup": "popup.html"
"default_popup": "popup.html",
"default_icon": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/LogoSponsorBlocker64px.png",
"128": "icons/LogoSponsorBlocker128px.png"
},
"theme_icons": [
{
"light": "icons/IconSponsorBlocker16px.png",
"dark": "icons/IconSponsorBlocker16px.png",
"size": 16
},
{
"light": "icons/IconSponsorBlocker32px.png",
"dark": "icons/IconSponsorBlocker32px.png",
"size": 32
},
{
"light": "icons/LogoSponsorBlocker64px.png",
"dark": "icons/LogoSponsorBlocker64px.png",
"size": 64
},
{
"light": "icons/LogoSponsorBlocker128px.png",
"dark": "icons/LogoSponsorBlocker128px.png",
"size": 128
}
]
},
"background": {
"scripts":[

15950
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,34 +8,35 @@
"react-dom": "^17.0.2"
},
"devDependencies": {
"@types/chrome": "^0.0.180",
"@types/chrome": "^0.0.193",
"@types/firefox-webext-browser": "^94.0.1",
"@types/jest": "^27.4.1",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"@types/selenium-webdriver": "^4.0.18",
"@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.17.0",
"@typescript-eslint/parser": "^5.17.0",
"chromedriver": "^100.0.0",
"concurrently": "^7.0.0",
"copy-webpack-plugin": "^10.2.4",
"eslint": "^8.12.0",
"eslint-plugin-react": "^7.29.4",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"jest": "^27.5.1",
"@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"chromedriver": "^103.0.0",
"concurrently": "^7.3.0",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8.20.0",
"eslint-plugin-react": "^7.30.1",
"fork-ts-checker-webpack-plugin": "^7.2.13",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.0",
"rimraf": "^3.0.2",
"schema-utils": "^4.0.0",
"selenium-webdriver": "^4.1.1",
"selenium-webdriver": "^4.3.1",
"speed-measure-webpack-plugin": "^1.5.0",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"typescript": "4.6",
"web-ext": "^6.8.0",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.2",
"webpack-merge": "^4.2.2"
"ts-jest": "^28.0.7",
"ts-loader": "^9.3.1",
"ts-node": "^10.9.1",
"typescript": "4.7",
"web-ext": "^7.1.1",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-merge": "^5.8.0"
},
"scripts": {
"web-run": "npm run web-run:chrome",
@@ -65,12 +66,12 @@
"lint:fix": "eslint src --fix"
},
"engines": {
"node": ">=12.20.0"
"node": ">=16"
},
"funding": [
{
"type": "individual",
"url": "hhttps://sponsor.ajay.app/donate"
"url": "https://sponsor.ajay.app/donate"
},
{
"type": "github",

View File

@@ -164,6 +164,9 @@
"copyPublicID": {
"message": "نسخ معرف المستخدم العام"
},
"copySegmentID": {
"message": "نسخ معرف الجزء"
},
"discordAdvert": {
"message": "انضم إلى سيرفر \"ديسكورد\" الرسمي لتقديم اقتراحات وتعليقات!"
},
@@ -200,6 +203,9 @@
"showDeleteButton": {
"message": "إظهار زر \"حذف\" على مشغّل اليوتيوب"
},
"enableViewTracking": {
"message": "تمكين تتبع مرات التخطي"
},
"showNotice": {
"message": "إظهار الإشعار مرة أخرى"
},
@@ -401,12 +407,18 @@
"Donate": {
"message": "تبرع"
},
"considerDonating": {
"message": "ساعد في تمويل التطوير"
},
"hideDonationLink": {
"message": "إخفاء رابط التبرع"
},
"darkModeOptionsPage": {
"message": "الوضع الداكن في صفحة الخيارات"
},
"helpPageThanksForInstalling": {
"message": "شكرا على تثبيت SponsorBlock."
},
"Editing": {
"message": "التعديل"
},

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Показване на известие след пропускане на сегмент"
},
"showCategoryGuidelines": {
"message": "Показване на помощ за категорията"
},
"noticeVisibilityMode0": {
"message": "Известия за пропускане в пълен размер"
},
@@ -542,18 +545,39 @@
"message": "до",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Включване на преходи"
},
"generic_guideline2": {
"message": "Възпроизвежда се, сякаш нищо не е пропуснато"
},
"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_selfpromo_guideline2": {
"message": "Безплатни възгласи, които не допринасят за видеоклипа"
},
"category_selfpromo_guideline3": {
"message": "Не за продукти и стоки с корпоративен дизайн"
},
"category_exclusive_access": {
"message": "Ексклузивен достъп"
},
@@ -564,12 +588,24 @@
"message": "Този видеоклип представя продукт, услуга или място, до което е получен безплатен или субсидиран достъп",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Целият видеоклип показва нещо, до което е получен безплатен или субсидиран достъп"
},
"category_interaction": {
"message": "Напомняне за действие (абониране)"
},
"category_interaction_description": {
"message": "Когато има кратко напомняне да харесате, да се абонирате или да последвате канала по средата на съдържанието. Ако е дълго или за нещо специфично, трябва да е под „самореклама“."
},
"category_interaction_guideline1": {
"message": "Кратки напомняния за харесване, абониране или следване"
},
"category_interaction_guideline2": {
"message": "Включва непреки напомняния за коментиране"
},
"category_interaction_guideline3": {
"message": "Не за общо популяризиране, а само за призиви за действие"
},
"category_interaction_short": {
"message": "Напомняне за взаимодействие"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Антракт"
},
"category_intro_guideline1": {
"message": "Интервал без реално съдържание"
},
"category_intro_guideline2": {
"message": "Не за преходи с информация"
},
"category_outro": {
"message": "Крайни картички/Заслуги"
},
"category_outro_description": {
"message": "Заслуги или когато се показват крайните карти на YouTube. Не за заключения с информация."
},
"category_outro_guideline1": {
"message": "Без съдържание, дори ако крайните карти са на екрана"
},
"category_preview": {
"message": "Кратко резюме/Обобщение"
},
"category_preview_description": {
"message": "Бързо обобщение на предишни епизоди или преглед на това, което предстои по-късно в текущия видеоклип. Предназначен за монтирани заедно клипове, а не за речеви обобщения."
},
"category_preview_guideline1": {
"message": "Клипове, които се появяват по-късно или в бъдещ видеоклип"
},
"category_preview_guideline2": {
"message": "Резюме на предишен видеоклип"
},
"category_preview_guideline3": {
"message": "Не за части, които добавят допълнително съдържание"
},
"category_filler": {
"message": "Пълнеж/Шеги"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Пълнеж"
},
"category_filler_guideline1": {
"message": "Съпътстващи сцени само за пълнеж или хумор"
},
"category_filler_guideline2": {
"message": "Отвличане на вниманието, гафове, повторения"
},
"category_filler_guideline3": {
"message": "Не за сцени, необходими за разбиране на темата"
},
"category_music_offtopic": {
"message": "Музика: Част без музика"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Без музика"
},
"category_music_offtopic_guideline1": {
"message": "Секции, които не са в официалните издания"
},
"category_music_offtopic_guideline2": {
"message": "Немузикална част от изпълнение на живо"
},
"category_poi_highlight": {
"message": "Акцент"
},
"category_poi_highlight_description": {
"message": "Частта от видеото, която повечето хора търсят. Подобно на коментарите „Видеото започва от х“."
},
"category_poi_highlight_guideline1": {
"message": "Секция, която повечето хора търсят"
},
"category_poi_highlight_guideline2": {
"message": "Може да помогне за прескачане на миналия контекст"
},
"category_poi_highlight_guideline3": {
"message": "Може да прескочи до заглавието или миниатюрата"
},
"category_livestream_messages": {
"message": "Поточно предаване: Четене на съобщения/дарения"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Научете повече"
},
"FullDetails": {
"message": "Пълна информация"
},
"CopyDownvoteButtonInfo": {
"message": "Гласуване против и създаване на локално копие, за да го изпратите отново"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Отваряне на страницата с опции"
},
"resetToDefault": {
"message": "Възстановяване на първоначалните настройки"
},
"confirmResetToDefault": {
"message": "Наистина ли искате да възстановите всички настройки до стойностите им по подразбиране? Това не може да бъде отменено."
}
}

View File

@@ -56,7 +56,7 @@
"message": "Deixa de silenciar"
},
"paused": {
"message": "Pausat"
"message": "En pausa"
},
"manualPaused": {
"message": "S'ha aturat el temporitzador"
@@ -222,5 +222,8 @@
},
"whatChangeUserID": {
"message": "Això hauria de romandre privat: és semblant a una contrasenya i no s'hauria de compartir amb cap persona. Si algú hi té accés, poden suplantar-vos. Si esteu cercant el vostre identificador d'usuari públic, premeu la icona del portanotes a la finestra."
},
"help": {
"message": "Ajuda"
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Zobrazit upozornění po přeskočení segmentu"
},
"showCategoryGuidelines": {
"message": "Zobrazit nápovědu ke kategorii"
},
"noticeVisibilityMode0": {
"message": "Oznámení o přeskočení v plné velikosti"
},
@@ -542,18 +545,39 @@
"message": "do",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Zahrnout přechody segmentů"
},
"generic_guideline2": {
"message": "Hraje, jako kdyby nebylo nic přeskočeno"
},
"category_sponsor": {
"message": "Sponzor"
},
"category_sponsor_description": {
"message": "Placená propagace, placená doporučení a přímé reklamy. Nezahrnuje sebepropagaci nebo shout-outy uživatelů/tvůrců/webů/produktů, které se tvůrcovi líbí."
},
"category_sponsor_guideline1": {
"message": "Placené propagace"
},
"category_sponsor_guideline2": {
"message": "Není pro dary nebo vlastní merch"
},
"category_selfpromo": {
"message": "Neplacená / vlastní propagace"
},
"category_selfpromo_description": {
"message": "Podobně jako \"sponzor\" s výjimkou neplacených nebo propagačních akcí. Zahrnuje sekce o zboží, dary nebo informace o tom, s kým spolupracují."
},
"category_selfpromo_guideline1": {
"message": "Dary, členství a vlastní merch"
},
"category_selfpromo_guideline2": {
"message": "Bezplatné propagace, které nepřidávají nic k videu"
},
"category_selfpromo_guideline3": {
"message": "Není pro podnikové produkty a merch"
},
"category_exclusive_access": {
"message": "Exkluzivní přístup"
},
@@ -564,12 +588,24 @@
"message": "V tomto videu je zobrazován produkt, služba nebo místo, které autor obdržel zdarma nebo s bezplatným přístupem",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Celé video zobrazuje místo, kam autor obdržel bezplatný nebo dotovaný přístup"
},
"category_interaction": {
"message": "Připomenutí interakce (odběr)"
},
"category_interaction_description": {
"message": "Při krátké připomínce dát like, odběr nebo sledování tvůrce uprostřed obsahu. Pokud je segment dlouhý nebo zahrnuje něco specifického, mělo by být místo toho zařazeno do vlastní propagace."
},
"category_interaction_guideline1": {
"message": "Krátká připomenutí pro lajknutí, odebírání či sledování"
},
"category_interaction_guideline2": {
"message": "Zahrnuje nepřímé připomenutí komentování"
},
"category_interaction_guideline3": {
"message": "Není pro obecnou propagaci, pouze výzvy k akci"
},
"category_interaction_short": {
"message": "Připomenutí interakce"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Přestávka"
},
"category_intro_guideline1": {
"message": "Část bez skutečného obsahu"
},
"category_intro_guideline2": {
"message": "Není pro přechody s informacemi"
},
"category_outro": {
"message": "Koncové karty / titulky"
},
"category_outro_description": {
"message": "Titulky nebo když se objeví konečné karty YouTube. Není pro závěry s informacemi."
},
"category_outro_guideline1": {
"message": "Nezahrnuje obsah, i když jsou na obrazovce závěrečné karty"
},
"category_preview": {
"message": "Náhled / shrnutí"
},
"category_preview_description": {
"message": "Rychlé shrnutí předchozích epizod nebo náhled toho, co se objeví v aktuálním videu. Myšleno pro upravené sloučené klipy, ne pro mluvená shrnutí."
},
"category_preview_guideline1": {
"message": "Klipy které se objeví později nebo v budoucím videu"
},
"category_preview_guideline2": {
"message": "Shrnutí předchozího videa"
},
"category_preview_guideline3": {
"message": "Není pro sekce, které přidávají další obsah"
},
"category_filler": {
"message": "Výplň / vtipy"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Výplň"
},
"category_filler_guideline1": {
"message": "Tangenciální scény pouze pro výplň nebo humor"
},
"category_filler_guideline2": {
"message": "Rozptýlení, nepovedené scénky, opakování"
},
"category_filler_guideline3": {
"message": "Není pro scény vyžadované k pochopení tématu"
},
"category_music_offtopic": {
"message": "Hudba: nehudební sekce"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Jiné než hudba"
},
"category_music_offtopic_guideline1": {
"message": "Sekce, které nejsou v oficiálních vydáních"
},
"category_music_offtopic_guideline2": {
"message": "Nehudební sekce na živém vystoupení"
},
"category_poi_highlight": {
"message": "Zvýraznění"
},
"category_poi_highlight_description": {
"message": "Část videa, kterou hledá většina lidí. Podobné komentářům typu \"Video začíná v x\"."
},
"category_poi_highlight_guideline1": {
"message": "Sekce, kterou hledá většina lidí"
},
"category_poi_highlight_guideline2": {
"message": "Může pomoci přeskočit předchozí kontext"
},
"category_poi_highlight_guideline3": {
"message": "Může přeskočit k názvu nebo náhledu"
},
"category_livestream_messages": {
"message": "Livestream: Čtení donatů / zpráv"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Zjistit více"
},
"FullDetails": {
"message": "Úplné podrobnosti"
},
"CopyDownvoteButtonInfo": {
"message": "Zahlasuje proti a vytvoří pro vás lokální kopii pro opětovné odeslání"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Otevřít stránku s možnostmi"
},
"resetToDefault": {
"message": "Obnovit nastavení na výchozí hodnoty"
},
"confirmResetToDefault": {
"message": "Opravdu chcete obnovit všechna nastavení na jejich výchozí hodnoty? Tato akce je nevratná."
}
}

View File

@@ -186,7 +186,7 @@
"message": "Versteckt die Schaltflächen im YouTube-Videoplayer, um Segmente einzusenden."
},
"showSkipButton": {
"message": "\"Zum Highlight springen\"-Button im Youtube-Player anzeigen"
"message": "Behalte \"Zum Highlight springen\"-Knopf in der Leiste"
},
"showInfoButton": {
"message": "Zeige Info-Knopf im Youtube-Videoplayer"
@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Zeige Pop-up nach dem Überspringen eines Segments"
},
"showCategoryGuidelines": {
"message": "Zeige Kategorien-Hilfe"
},
"noticeVisibilityMode0": {
"message": "Skip-Meldung in voller größe"
},
@@ -303,7 +306,7 @@
"description": "Used to describe the section that will show you the statistics from your submissions."
},
"502": {
"message": "Der Server scheint überlastet zu sein. Probiere es in ein paar Sekunden erneut."
"message": "Der Server ist wahrescheinlich überlastet. Probiere es später nochmal."
},
"errorCode": {
"message": "Fehlermeldung: "
@@ -542,18 +545,39 @@
"message": "bis",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Fließende Übergänge einbeziehen"
},
"generic_guideline2": {
"message": "Spielt, als ob nichts übersprungen worden wäre"
},
"category_sponsor": {
"message": "Gesponserte Videosegmente"
},
"category_sponsor_description": {
"message": "Bezahlte Werbung, bezahlte Empfehlungen und direkte Werbung, nicht für Eigenwerbung, kostenlose Fremdwerbung oder Empfehlungen für Anlässe/Personen/Webseiten/Produkte."
},
"category_sponsor_guideline1": {
"message": "Bezahlte Werbeaktionen"
},
"category_sponsor_guideline2": {
"message": "Nicht für Spenden oder eigenen Merch"
},
"category_selfpromo": {
"message": "Unbezahlt/Eigenwerbung"
},
"category_selfpromo_description": {
"message": "Ähnlich wie bei \"gesponserte Videosegmente\", mit Ausnahme von unbezahlten oder Selbstpromotionen. Dies beinhaltet Merchandising (Fan-Artikel), Spenden oder Informationen darüber, mit wem für das Video zusammengearbeitet wurde."
},
"category_selfpromo_guideline1": {
"message": "Spenden, Mitgliedschaften und eigener Merch"
},
"category_selfpromo_guideline2": {
"message": "Kostenlose Erwähnungen welche nicht zum Video beitragen"
},
"category_selfpromo_guideline3": {
"message": "Nicht für Produkte und Merch von Firmen"
},
"category_exclusive_access": {
"message": "Exklusiver Zugriff"
},
@@ -564,12 +588,24 @@
"message": "Dieses Video präsentiert ein Produkt, eine Dienstleistung oder einen Ort, zu welchem sie freien oder subventionierten Zugriff erhalten haben",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Das gesamte Video zeigt etwas, zu dem man kostenlosen oder subventionierten Zugang erhalten hat"
},
"category_interaction": {
"message": "Interaktions-Erinnerungen (Abonnieren, etc.)"
},
"category_interaction_description": {
"message": "Wenn es im Video eine kurze Erinnerung gibt, den Kanal zu abonnieren oder das Video mit \"Mag ich\" zu markieren."
},
"category_interaction_guideline1": {
"message": "Kurze Erinnerungen zum Liken, Abonnieren oder Folgen"
},
"category_interaction_guideline2": {
"message": "Enthält indirekte Erinnerungen zum Kommentieren"
},
"category_interaction_guideline3": {
"message": "Nicht für allgemeine Förderung, nur Aufruf zum Handeln"
},
"category_interaction_short": {
"message": "Interaktions-Erinnerung"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Unterbrechung"
},
"category_intro_guideline1": {
"message": "Intervall ohne tatsächlichen Inhalt"
},
"category_intro_guideline2": {
"message": "Nicht für Übergänge mit Informationen"
},
"category_outro": {
"message": "Endkarten/Quellen/Anerkennungen"
},
"category_outro_description": {
"message": "Credits oder wenn die YouTube-Endkarten erscheinen. Nicht für videobeendende Schlussfolgerungen mit Informationen."
},
"category_outro_guideline1": {
"message": "Enthalten keinen Inhalt, selbst wenn Endcards zu sehen sind"
},
"category_preview": {
"message": "Vorschau/Zusammenfassung"
},
"category_preview_description": {
"message": "Kurze Zusammenfassung bisheriger Videos oder eine Vorschau auf das aktuelle Video. Für zusammengeschnittene Clips gedacht, jedoch nicht für mündliche Zusammenfassungen."
},
"category_preview_guideline1": {
"message": "Clips welche später oder in einem zukünftigen Video erscheinen"
},
"category_preview_guideline2": {
"message": "Recap eines vorherigen Videos"
},
"category_preview_guideline3": {
"message": "Nicht für Bereiche, welche zusätzliche Inhalte geben"
},
"category_filler": {
"message": "Füller/Witze"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Füller"
},
"category_filler_guideline1": {
"message": "Tangentielle Szenen nur für Füller oder Humor"
},
"category_filler_guideline2": {
"message": "Ablenkungen, Patzer, Replays"
},
"category_filler_guideline3": {
"message": "Nicht für Szenen, welche zum verstehen des Topics sind"
},
"category_music_offtopic": {
"message": "Musikvideoteile ohne Musik"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Musikvideoteile ohne Musik"
},
"category_music_offtopic_guideline1": {
"message": "Bereiche nicht in offiziellen Veröffentlichungen"
},
"category_music_offtopic_guideline2": {
"message": "Nicht-Musik in einer Live-Performance"
},
"category_poi_highlight": {
"message": "Highlight"
},
"category_poi_highlight_description": {
"message": "Der Teil des Videos, nach dem die meisten Leute suchen, ähnlich wie \"Video startet bei x\" Kommentare."
},
"category_poi_highlight_guideline1": {
"message": "Bereiche die die meisten Personen suchen"
},
"category_poi_highlight_guideline2": {
"message": "Kann helfen, vergangene Inhalte zu überspringen"
},
"category_poi_highlight_guideline3": {
"message": "Kann zum Titel oder Thumbnail überspringen"
},
"category_livestream_messages": {
"message": "In Livestreams Spenden/Nachrichten vorlesen"
},
@@ -789,7 +867,7 @@
"message": "Dauerhaft verbergen"
},
"warningChatInfo": {
"message": "Du hast eine Warnung erhalten und kannst vorübergehend keine Segmente einreichen. Uns ist nämlich aufgefallen, dass du nicht bösartige Fehler in deinen Einreichungen machst. Bitte bestätige, dass du die Regeln verstanden hast. Darauffolgend können wir die Warnung entfernen. Du kannst diesem Chat auch mit discord.gg/SponsorBlock oder matrix.to/#/#sponsor:ajay.app beitreten"
"message": "Du hast eine Warnung erhalten und kannst vorübergehend keine Segmente einreichen. Dies bedeutet, dass du Fehler gemacht hast welche nicht bösartig sind, bitte bestätige, dass du die Regeln verstanden hast, wir werden dann die Warnung entfernen. Du kannst diesem Chat auch mit discord.gg/SponsorBlock oder matrix.to/#/#sponsor:ajay.app beitreten"
},
"voteRejectedWarning": {
"message": "Abstimmung wegen einer Warnung abgelehnt. Klicke hier um einen Chat zu öffnen, oder versuch es später erneut, wenn du Zeit hast.",
@@ -832,7 +910,7 @@
"message": "Das übermitteln kann entweder im Popup durch das drücken vom \"Segment Started Jetzt\" Knopf oder im Videoplayer mit den Knöpfen im Player gemacht werden."
},
"helpPageSubmitting2": {
"message": "Mit einem Klick auf den Play-Knopf wird der Beginn eines Segments und mit dem klick auf den Stop-Knopf das ende eines Segments markiert. Du kannst mehrere Sponsoren vorbereiten, bevor du auf Absenden klickst. Das klicken des Upload-Knopfes wird die Segmente übermitteln. Das klicken des Mülleimers löscht es."
"message": "Mit einem Klick auf den Play-Button wird der Beginn eines Segments markiert, und mit einem Klick auf den Stop-Button dessen Ende. Du kannst mehrere Segmente anlegen, bevor du durch Klicken des Upload-Buttons zum Einreichen fortfährst. Ein Klick auf die Mülltonne löscht deine angelegten Segmente."
},
"Editing": {
"message": "Bearbeitung"
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Erfahre mehr"
},
"FullDetails": {
"message": "Vollständige Details"
},
"CopyDownvoteButtonInfo": {
"message": "Downvotet, und erstellt eine lokale Kopie zum erneuten Einreichen"
},
@@ -883,7 +964,7 @@
"message": "Dies wirkt sich sofort auf eigene Segmente aus"
},
"downvote": {
"message": "Negativ bewertet"
"message": "Dagegen stimmen"
},
"upvote": {
"message": "Positiv bewerten"
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Einstellungen öffnen"
},
"resetToDefault": {
"message": "Einstellungen zurücksetzen"
},
"confirmResetToDefault": {
"message": "Sind Sie sicher, dass Sie alle Einstellungen auf ihre Standardwerte zurücksetzen wollen? Dies kann nicht rückgängig gemacht werden."
}
}

View File

@@ -164,6 +164,9 @@
"copyPublicID": {
"message": "Αντιγραφή δημόσιου αναγνωριστικού χρήστη"
},
"copySegmentID": {
"message": "Αντιγραφή ταυτότητας τμήματος"
},
"discordAdvert": {
"message": "Μπείτε στον επίσημο διακομιστή μας στο Discord για ανατροφοδότηση και προτάσεις!"
},
@@ -183,7 +186,7 @@
"message": "Αυτό αποκρύπτει τα κουμπιά που εμφανίζονται στο πρόγραμμα αναπαραγωγής YouTube ώστε να υποβάλετε τμήματα προς παράλειψη."
},
"showSkipButton": {
"message": "Διατήρηση του κουμπιού παράλειψης προς την καλύτερη στιγμή στο πρόγραμμα αναπαραγωγής"
"message": "Διατήρηση του κουμπιού παράλειψης προς το κυριότερο περιεχόμενο του βίντεο στην οθόνη αναπαραγωγής"
},
"showInfoButton": {
"message": "Εμφάνιση κουμπιού «πληροφορίες» στο πρόγραμμα αναπαραγωγής YouTube"
@@ -209,6 +212,15 @@
"enableViewTrackingInPrivate": {
"message": "Ενεργοποίηση του μετρητή παραλείψεων σε καρτέλες Ιδιωτικής/Ανώνυμης περιήγησης"
},
"enableTrackDownvotes": {
"message": "Αποθήκευση καταψηφίσεων τμημάτων"
},
"whatTrackDownvotes": {
"message": "Όποια τμήματα καταψηφίζετε θα παραμένουν κρυμμένα ακόμα και μετά από ανανέωση της σελίδας"
},
"trackDownvotesWarning": {
"message": "Προειδοποίηση: Η απενεργοποίηση αυτής της επιλογής θα διαγράψει όλες τις προηγούμενες αποθηκευμένες καταψηφίσεις"
},
"enableQueryByHashPrefix": {
"message": "Αιτήματα μέσω προθέματος Hash"
},
@@ -227,6 +239,9 @@
"showSkipNotice": {
"message": "Εμφάνιση ειδοποιήσεων μετά την παράλειψη κάθε τμήματος"
},
"showCategoryGuidelines": {
"message": "Εμφάνιση βοήθειας κατηγοριών"
},
"noticeVisibilityMode0": {
"message": "Ειδοποιήσεις παράλειψης πλήρους μεγέθους"
},
@@ -262,6 +277,21 @@
"message": "Εάν δεν σας αρέσουν, πατήστε το κουμπί «Να μην ξαναεμφανιστεί».",
"description": "The second line of the message displayed after the notice was upgraded."
},
"setSkipShortcut": {
"message": "Παράλειψη τμήματος",
"description": "Keybind label"
},
"setStartSponsorShortcut": {
"message": "Έναρξη/Τέλος τμήματος",
"description": "Keybind label"
},
"setSubmitKeybind": {
"message": "Υποβολή Τμημάτων",
"description": "Keybind label"
},
"keybindDescription": {
"message": "Επιλέξτε ένα πλήκτρο πατώντας το και επιλέξτε όποιο πρόσθετο πλήκτρο ελέγχου επιθυμείτε."
},
"0": {
"message": "Λήξη χρονικού ορίου σύνδεσης. Παρακαλώ ελέγξτε ότι η σύνδεσή σας με το διαδίκτυο λειτουργεί, αλλιώς ο διακομιστής μπορεί να υπερφορτώθηκε ή να έπεσε."
},
@@ -282,14 +312,21 @@
"message": "Κωδικός σφάλματος: "
},
"skip": {
"message": "Παράκαμψη"
"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};"
},
"mute_category": {
"message": "Σίγαση {0};"
},
"skip_to_category": {
"message": "Παράλειψη προς το σημείο «{0}»;",
"description": "Used for skipping to things (Skip to Highlight)"
@@ -298,15 +335,19 @@
"message": "Παραλείφθηκε «{0}»",
"description": "Example: Sponsor Skipped"
},
"muted": {
"message": "Σίγαση {0}",
"description": "Example: Sponsor Muted"
},
"skipped_to_category": {
"message": "Παραλείφθηκε προς το σημείο «{0}»",
"description": "Used for skipping to things (Skipped to Highlight)"
},
"disableAutoSkip": {
"message": "Απενεργοποίηση Αυτόματης Παράληψης"
"message": "Απενεργοποίηση Αυτόματης Παράλειψης"
},
"enableAutoSkip": {
"message": "Ενεργοποίηση Αυτόματης Παράληψης"
"message": "Ενεργοποίηση Αυτόματης Παράλειψης"
},
"audioNotification": {
"message": "Ηχητική ειδοποίηση κατά την παράληψη"
@@ -315,7 +356,7 @@
"message": "Οι ειδοποιήσεις μετά την παράλειψη κάθε τμήματος θα αναπαράγουν ήχο. Εάν είναι ανενεργή αυτή η επιλογή εκτός αν η αυτόματη παράλειψη είναι ανενεργή."
},
"showTimeWithSkips": {
"message": "Εμφάνιση χρόνου συμπεριλαμβάνοντας τις παραλήψεις"
"message": "Εμφάνιση χρόνου συμπεριλαμβάνοντας τις παραλείψεις"
},
"showTimeWithSkipsDescription": {
"message": "Αυτός ο χρόνος εμφανίζεται σε παρενθέσεις δίπλα από τον τρέχων χρόνο που είναι κάτω από την μπάρα χρόνου. Αυτό δείχνει την συνολική διάρκεια του βίντεο πλην οποιουδήποτε τμήματος. Αυτό περιλαμβάνει και τα τμήματα που εμφανίζονται μόνο στην μπάρα χρόνου."
@@ -349,6 +390,9 @@
"changeUserID": {
"message": "Εισαγωγή/Εξαγωγή της Ταυτότητας Χρήστη σας"
},
"whatChangeUserID": {
"message": "Κρατείστε το μυστικό. Αυτό είναι σαν ένα κωδικό πρόσβασης όπου δε θα έπρεπε να μοιράζεστε με κανένα. Εάν το αποκτήσει κάποιος, μπορεί να σας υποδυθεί. Εάν ψάχνετε για τη δημόσια ταυτότητα χρήστη σας, πατήστε πάνω στο αναδυόμενο εικονίδιο πρόχειρου."
},
"setUserID": {
"message": "Ορισμός Ταυτότητας Χρήστη"
},
@@ -369,7 +413,7 @@
"message": "Υποστηριζόμενες Ιστοσελίδες: "
},
"optionsInfo": {
"message": "Ενεργοποίηση υποστήριξης «Invidious», απενεργοποίηση αυτόματης παράληψης, απόκρυψη κουμπιών και άλλα."
"message": "Ενεργοποίηση υποστήριξης «Invidious», απενεργοποίηση αυτόματης παράλειψης, απόκρυψη κουμπιών και άλλα."
},
"addInvidiousInstance": {
"message": "Προσθήκη 3ου πελάτη συνεδρίας"
@@ -407,6 +451,9 @@
"shortCheck": {
"message": "Η ακόλουθη υποβολή είναι μικρότερη από την επιλεγμένη ελάχιστη διάρκεια. Αυτό θα μπορούσε να σημαίνει ότι αυτό το τμήμα έχει ήδη υποβληθεί, και απλά αγνοείται λόγω αυτής της επιλογής. Είστε σίγουροι ότι θα θέλατε να το υποβάλετε;"
},
"liveOrPremiere": {
"message": "Η υποβολή σε μια ζωντανή μετάδοση σε εξέλιξη ή σε μια πρεμιέρα δεν επιτρέπεται. Παρακαλώ περιμένετε μέχρι να τελειώσει, στην συνέχεια ανανεώστε την σελίδα και επαληθεύστε ότι τα τμήματα είναι έγκυρα."
},
"showUploadButton": {
"message": "Εμφάνιση κουμπιού υποβολής"
},
@@ -434,6 +481,15 @@
"exportOptions": {
"message": "Εισαγωγή/Εξαγωγή όλων των ρυθμίσεων"
},
"exportOptionsCopy": {
"message": "Επεξεργασία/αντιγραφή"
},
"exportOptionsDownload": {
"message": "Αποθήκευση σε αρχείο"
},
"exportOptionsUpload": {
"message": "Φόρτωση από αρχείο"
},
"whatExportOptions": {
"message": "Αυτές είναι όλες σας οι ρυθμίσεις σε αρχείο JSON. Αυτό περιλαμβάνει και την Ταυτότητα Χρήστη, οπότε μοιραστείτε το με προσοχή."
},
@@ -482,28 +538,74 @@
"copyDebugInformationComplete": {
"message": "Οι πληροφορίες εντοπισμού σφαλμάτων έχουν αντιγραφεί στο πρόχειρο. Μη διστάσετε να αφαιρέσετε οποιαδήποτε πληροφορία που προτιμάτε να μη μοιραστείτε. Αποθηκεύστε τες σε ένα αρχείο κειμένου ή επικολλήστε στην αναφορά σφάλματός σας."
},
"keyAlreadyUsed": {
"message": "Αυτό το πλήκτρο έχει οριστεί ως συντόμευση για άλλη ενέργεια. Παρακαλώ επιλέξτε ένα άλλο πλήκτρο."
},
"to": {
"message": "έως",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Συμπερίληψη τμημάτων μεταβάσεων"
},
"generic_guideline2": {
"message": "Ροή περιεχομένου σαν να μην υπήρξε ποτέ παράλειψη"
},
"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_selfpromo_guideline2": {
"message": "Δωρεάν αναφορές που δεν προστίθενται στο βίντεο"
},
"category_selfpromo_guideline3": {
"message": "Όχι για προϊόντα ή ρουχισμό που έχει κατασκευαστεί από αλυσίδες επιχειρήσεων"
},
"category_exclusive_access": {
"message": "Αποκλειστική Πρόσβαση"
},
"category_exclusive_access_description": {
"message": "Μόνο για χαρακτηρισμό ολοκλήρου του βίντεο. Χρησιμοποιείται όταν ένα βίντεο παρουσιάζει ένα προϊόν, υπηρεσία, ή τοποθεσία αποκλειστικής ή δωρεάν πρόσβασής τους."
},
"category_exclusive_access_pill": {
"message": "Αυτό το βίντεο παρουσιάζει ένα προϊόν, υπηρεσία, ή τοποθεσία αποκλειστικής ή δωρεάν πρόσβασής τους",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Ολόκληρο το βίντεο παρουσιάζει κάτι με δωρεάν ή επιδοτούμενη πρόσβαση"
},
"category_interaction": {
"message": "Υπενθύμιση Αλληλεπίδρασης (Εγγραφή)"
},
"category_interaction_description": {
"message": "Όταν υπάρχει μια σύντομη υπενθύμιση για να προσθέσετε το βίντεο στα βίντεο που σας αρέσουν, να εγγραφείτε ή να τους ακολουθήσετε στη μέση του περιεχομένου. Εάν διαρκεί για αρκετή ώρα, τότε αυτό περιλαμβάνεται στη κατηγορία «προσωπική προώθηση»."
},
"category_interaction_guideline1": {
"message": "Σύντομες υπενθυμίσεις εγγραφής, μου αρέσει και ακολούθησης"
},
"category_interaction_guideline2": {
"message": "Περιλαμβάνει έμμεσες υπενθυμίσεις για σχόλια"
},
"category_interaction_guideline3": {
"message": "Όχι για γενικότερες προωθήσεις, μόνο για εκκλήσεις βοήθειας"
},
"category_interaction_short": {
"message": "Υπενθύμιση Αλληλεπίδρασης"
},
@@ -516,18 +618,54 @@
"category_intro_short": {
"message": "Διάλειμμα"
},
"category_intro_guideline1": {
"message": "Χρονικό διάστημα χωρίς ουσιαστικό περιεχόμενο"
},
"category_intro_guideline2": {
"message": "Όχι για μεταβάσεις που συμπεριλαμβάνουν πληροφορίες"
},
"category_outro": {
"message": "Προτεινόμενα βίντεο καναλιών/Εύσημα"
},
"category_outro_description": {
"message": "Όταν εμφανίζονται τα εύσημα ή τα προτεινόμενα βίντεο των καναλιών. Όχι, για επίλογους που περιέχουν πληροφορίες."
},
"category_outro_guideline1": {
"message": "Μη συμπερίληψη περιεχομένου, ακόμα και αν τα προτεινόμενα βίντεο βρίσκονται στην οθόνη"
},
"category_preview": {
"message": "Προεπισκόπηση/Αναθεώρηση"
},
"category_preview_description": {
"message": "Γρήγορη ανακεφαλαίωση προηγουμένων επεισοδίων, ή προεπισκόπηση του τι ακολουθεί στο τρέχων βίντεο. Εννοώντας επεξεργασία μερικών κλιπ μαζί, όχι για προφορικές περιγραφές."
},
"category_preview_guideline1": {
"message": "Κλιπ που εμφανίζονται μετά, ή σε μελλοντικά βίντεο"
},
"category_preview_guideline2": {
"message": "Ανακεφαλαίωση προηγούμενου βίντεο"
},
"category_preview_guideline3": {
"message": "Όχι για μέρη που περιλαμβάνουν πρόσθετο περιεχόμενο"
},
"category_filler": {
"message": "Σπατάλη Χρόνου/Περιττό σχόλιο"
},
"category_filler_description": {
"message": "Σκηνές εκτός θέματος προστίθενται μόνο για σπατάλη χρόνου και περιττά σχόλια τα οποία δεν απαιτούνται για να κατανοήσετε το κύριο περιεχόμενο του βίντεο. Δεν πρέπει να περιλαμβάνονται τμήματα που συμβάλουν στην κατανόηση του θέματος του βίντεο ή επιπλέον πληροφορίες."
},
"category_filler_short": {
"message": "Περιττό"
},
"category_filler_guideline1": {
"message": "Άσχετες σκηνές μόνο για σπατάλη χρόνου ή περιττά σχόλια"
},
"category_filler_guideline2": {
"message": "Αποσπάσεις, επαναλήψεις, αποτυχημένες λήψεις"
},
"category_filler_guideline3": {
"message": "Όχι για σκηνές που απαιτούνται για την κατανόηση του θέματος"
},
"category_music_offtopic": {
"message": "Μουσική: Τμήμα χωρίς μουσική"
},
@@ -537,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Χωρίς Μουσική"
},
"category_music_offtopic_guideline1": {
"message": "Μέρη τα οποία δεν έχουν κυκλοφορήσει επίσημα"
},
"category_music_offtopic_guideline2": {
"message": "Χωρίς μουσική σε ζωντανή συναυλία"
},
"category_poi_highlight": {
"message": "Καλύτερη στιγμή"
},
"category_poi_highlight_description": {
"message": "Το κομμάτι του βίντεο που ψάχνουν να δουν οι περισσότεροι άνθρωποι. Παρόμοιο με το «Το βίντεο ξεκινάει από το x σημείο» στα σχόλια."
},
"category_poi_highlight_guideline1": {
"message": "Το μέρος που ψάχνουν οι περισσότεροι"
},
"category_poi_highlight_guideline2": {
"message": "Ίσως παραλειφθεί κάτι που συμβάλει στην κατανόηση"
},
"category_poi_highlight_guideline3": {
"message": "Παράλειψη προς το περιεχόμενο όπου παρουσιάζεται στον τίτλο ή το εικονίδιο"
},
"category_livestream_messages": {
"message": "Ζωντανή μετάδοση: Δωρεές/Ανάγνωση Μηνυμάτων από δωρεές"
},
@@ -550,10 +703,10 @@
"message": "Ανάγνωση Μηνυμάτων"
},
"autoSkip": {
"message": "Αυτόματη Παράληψη"
"message": "Αυτόματη Παράλειψη"
},
"manualSkip": {
"message": "Χειροκίνητη Παράληψη"
"message": "Χειροκίνητη Παράλειψη"
},
"showOverlay": {
"message": "Εμφάνιση στην μπάρα χρόνου"
@@ -570,9 +723,19 @@
"showOverlay_POI": {
"message": "Εμφάνιση στην μπάρα χρόνου"
},
"showOverlay_full": {
"message": "Εμφάνιση Ετικέτας"
},
"autoSkipOnMusicVideos": {
"message": "Αυτόματη παράλειψη όλων των τμημάτων όταν είναι «Χωρίς μουσική»"
},
"muteSegments": {
"message": "Επιτρέψτε τμήματα που κάνουν σίγαση αντί για παράλειψη"
},
"fullVideoSegments": {
"message": "Εμφάνιση εικονιδίου όταν όλο το βίντεο είναι διαφημιστικό εξολοκλήρου",
"description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo"
},
"previewColor": {
"message": "Χρώμα Μη Υποβληθέντων",
"description": "Referring to submissions that have not been sent to the server yet."
@@ -609,6 +772,9 @@
"message": "Για να υποβάλετε τμήματα της «{0}» κατηγορίας, πρέπει να την ενεργοποιήσετε στις ρυθμίσεις. Θα μεταφερθείτε στις ρυθμίσεις τώρα.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"poiOnlyOneSegment": {
"message": "Προσοχή: Αυτός ο τύπος τμήματος μπορεί να υπάρξει μόνο μια φορά. Εάν υποβάλετε πολλαπλά τότε εμφανίζεται ένα από τα τμήματα στην τύχη."
},
"youMustSelectACategory": {
"message": "Πρέπει να επιλέξετε Κατηγορία για όλα τα τμήματα που υποβάλετε!"
},
@@ -621,6 +787,9 @@
"hiddenDueToDuration": {
"message": "κρυφό: πολύ κοντό"
},
"manuallyHidden": {
"message": "χειροκίνητη απόκρυψη"
},
"channelDataNotFound": {
"description": "This error appears in an alert when they try to whitelist a channel and the extension is unable to determine what channel they are looking at.",
"message": "Η ταυτότητα καναλιού δεν έχει φορτώσει ακόμα. Εάν χρησιμοποιείτε ενσωματωμένο βίντεο, δοκιμάστε αντιθέτως να το ανοίξετε στο YouTube. Μπορεί επίσης να ευθύνονται αλλαγές στη διάταξη του YouTube, εάν το πιστεύετε, γράψτε ένα σχόλιο εδώ:"
@@ -655,6 +824,9 @@
"downvoteDescription": {
"message": "Λάθος Συγχρονισμός"
},
"incorrectCategory": {
"message": "Αλλαγή κατηγορίας"
},
"nonMusicCategoryOnMusic": {
"message": "Αυτό το βίντεο έχει κατηγοριοποιηθεί ως μουσική. Είσαστε σίγουροι ότι έχει χορηγία; Εάν είναι ένα τμήμα «Χωρίς μουσική», ανοίξτε τις επιλογές της επέκτασης και ενεργοποιήστε αυτήν την κατηγορία. Μετά μπορείτε, να υποβάλετε αυτό το τμήμα ως «Χωρίς μουσική», αντί για χορηγία. Παρακαλώ διαβάστε τις οδηγίες, εάν είστε μπερδεμένοι."
},
@@ -681,6 +853,12 @@
"message": "Το' χω",
"description": "Used as the button to dismiss a tooltip"
},
"fullVideoTooltipWarning": {
"message": "Αυτό το τμήμα είναι μεγάλο. Αν όλο το βίντεο έχει να κάνει με ένα θέμα, τότε αλλάξτε την επιλογή από \"Παράλειψη\" σε \"Όλο το βίντεο\". Δείτε τις οδηγίες για περισσότερες πληροφορίες."
},
"categoryPillTitleText": {
"message": "Ολόκληρο το βίντεο έχει χαρακτηριστεί ως αυτής της κατηγορίας οπότε είναι αυστηρά ενσωματωμένη ώστε να μπορείτε να το διαφοροποιήσετε σε τμήματα"
},
"experiementOptOut": {
"message": "Αποχή από μελλοντικά πειράματα",
"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."
@@ -688,6 +866,9 @@
"hideForever": {
"message": "Απόκρυψη για πάντα"
},
"warningChatInfo": {
"message": "Λάβατε προειδοποίηση οπότε δεν μπορείτε να υποβάλετε τμήματα προσωρινά. Αυτό σημαίνει ότι παρατηρήσαμε ότι κάνατε κάποια λάθη των πρωτάριδων και όχι κάτι ύποπτο, παρακαλώ επιβεβαιώστε ότι κατανοείτε τους κανόνες και θα σας αφαιρέσουμε την προειδοποίηση. Μπορείτε και να μας στείλετε μήνυμα στο discord.gg/SponsorBlock ή στο matrix.to/#/#sponsor:ajay.app"
},
"voteRejectedWarning": {
"message": "Η ψήφος απορρίφθηκε λόγο μιας προειδοποίησης. Πατήστε για να ανοίξετε μια συνομιλία ώστε να το διορθώσετε, ή επιστρέψτε όταν έχετε περισσότερο χρόνο.",
"description": "This is an integrated chat panel that will appearing allowing them to talk to the Discord/Matrix chat without leaving their browser."
@@ -695,9 +876,15 @@
"Donate": {
"message": "Δωρεά"
},
"considerDonating": {
"message": "Χρηματική βοήθεια για την ανάπτυξή μας"
},
"hideDonationLink": {
"message": "Απόκρυψη Συνδέσμου Δωρεάς"
},
"darkModeOptionsPage": {
"message": "Σκουρόχρωμη Λειτουργία στη σελίδα Ρυθμίσεις"
},
"helpPageThanksForInstalling": {
"message": "Ευχαριστούμε που εγκαταστήσατε το SponsorBlock."
},
@@ -731,18 +918,121 @@
"helpPageEditing1": {
"message": "Αν κάνατε κάτι λάθος, μπορείτε να διορθώσετε ή να διαγράψετε τα τμήματα σας πατώντας το εικονίδιο πάνω βελάκι."
},
"helpPageTooSlow": {
"message": "Το να υποβάλεις τμήματα παίρνει χρόνο, υπάρχει ποιο γρήγορος τρόπος;"
},
"helpPageTooSlow1": {
"message": "Συντομεύσεις που μπορείτε να χρησιμοποιήσετε εάν το επιθυμείτε. Πατήστε την αγγλική άνω τελεία «;» για να δείξετε την αρχή/το τέλος ενός τμήματος χορηγίας και πατήστε την απόστροφο για υποβολή. Αυτές οι συντομεύσεις αλλάζουν στις ρυθμίσεις. Εάν δεν χρησιμοποιείτε το σύστημα πληκτρολόγησης «QWERTY», θα ήταν καλύτερο τα τις αλλάξετε."
},
"helpPageCopyOfDatabase": {
"message": "Μπορώ να λάβω ένα αντίγραφο της βάσης δεδομένων; Τι θα συμβεί αν η επέκταση εξαφανιστεί;"
},
"helpPageCopyOfDatabase1": {
"message": "Η βάση δεδομένων είναι δημόσια διαθέσιμη στη σελίδα"
},
"helpPageCopyOfDatabase2": {
"message": "Ο πηγαίος κώδικας είναι δημόσια διαθέσιμος. Έτσι ώστε, ακόμα και αν συμβεί κάτι σε εμάς, οι υποβολές σας δε θα χαθούν."
},
"helpPageNews": {
"message": "Νέα και πως δημιουργήθηκε η επέκταση"
},
"helpPageSourceCode": {
"message": "Πού μπορώ να βρω τον πηγαίο κώδικα;"
},
"Credits": {
"message": "Εύσημα"
},
"LearnMore": {
"message": "Μάθετε περισσότερα"
},
"FullDetails": {
"message": "Πλήρεις Λεπτομέρειες"
},
"CopyDownvoteButtonInfo": {
"message": "Καταψήφιση και δημιουργία τοπικού αντίγραφου για επανάληψη υποβολής"
},
"OpenCategoryWikiPage": {
"message": "Άνοιγμα σελίδας wiki της κατηγορίας."
},
"CopyAndDownvote": {
"message": "Αντίγραφο και καταψήφιση"
},
"ContinueVoting": {
"message": "Συνεχίστε την ψηφοφορία"
},
"ChangeCategoryTooltip": {
"message": "Αυτό θα εφαρμοστεί απευθείας στα τμήματά σας"
},
"downvote": {
"message": "Αρνητική ψήφος"
},
"upvote": {
"message": "Θετική ψήφος"
},
"hideSegment": {
"message": "Απόκρυψη τμήματος"
},
"SponsorTimeEditScrollNewFeature": {
"message": "Χρήση της ροδέλας ποντικιού καθώς είναι πάνω από το παράθυρο επεξεργασίας για να αλλάξετε γρηγορότερα τον χρόνο. Συνδυασμοί όπως το ctrl ή το shift χρησιμοποιούνται για να τελειοποιήσετε τις αλλαγές."
},
"categoryPillNewFeature": {
"message": "Νέο! Δείτε ποτέ ένα βίντεο αποτελείται εξολοκλήρου από προσωπικές προωθήσεις ή χορηγίες"
},
"dayAbbreviation": {
"message": "ημ",
"description": "100d"
},
"hourAbbreviation": {
"message": "ω",
"description": "100h"
},
"optionsTabBehavior": {
"message": "Συμπεριφορά",
"message": "Συμπεριφορά παραλ.",
"description": "Appears in Options as a tab header for options related to categories and skipping behavior. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabInterface": {
"message": "Διεπαφή",
"description": "Appears in Options as a tab header for options related to GUI and sounds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabKeyBinds": {
"message": "Συντομεύσεις πλήκτρων",
"description": "Appears in Options as a tab header for keybinds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabBackup": {
"message": "Αντίγραφο/Επαναφορά",
"description": "Appears in Options as a tab header for options related to saving/restoring your settings. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabAdvanced": {
"message": "Διάφορα",
"description": "Appears in Options as a tab header for advanced/niche options. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"noticeVisibilityLabel": {
"message": "Εμφάνιση ειδοποίησης παράλειψης",
"description": "Option label"
},
"unbind": {
"message": "Αφαίρεση",
"description": "Unbind keyboard shortcut"
},
"notSet": {
"message": "Δεν έχει οριστεί"
},
"change": {
"message": "Αλλαγή"
},
"youtubeKeybindWarning": {
"message": "Αυτή η συντόμευση χρησιμοποιείται από το YouTube. Σίγουρα θέλετε να την χρησιμοποιήσετε;"
},
"betaServerWarning": {
"message": "Ο Δοκιμαστικός Διακομιστής είναι ενεργός!"
},
"openOptionsPage": {
"message": "Άνοιγμα σελίδας ρυθμίσεων"
},
"resetToDefault": {
"message": "Επαναφορά των προκαθορισμένων ρυθμίσεων"
},
"confirmResetToDefault": {
"message": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε όλες τις ρυθμίσεις στις προεπιλεγμένες τιμές τους; Αυτό δεν μπορεί να αναιρεθεί."
}
}

View File

@@ -25,6 +25,16 @@
"Segments": {
"message": "segments"
},
"SegmentsCap": {
"message": "Segments"
},
"Chapters": {
"message": "Chapters"
},
"renderAsChapters": {
"message": "Render segments as chapters",
"description": "Refers to drawing segments on the YouTube seek bar as split up chapters, similar to the existing chapter system"
},
"upvoteButtonInfo": {
"message": "Upvote this submission"
},
@@ -101,7 +111,7 @@
"message": "It seems the server is down. Contact the dev immediately."
},
"connectionError": {
"message": "A connection error has occured. Error code: "
"message": "A connection error has occurred. Error code: "
},
"clearTimes": {
"message": "Clear Segments"
@@ -115,6 +125,9 @@
"SubmitTimes": {
"message": "Submit Segments"
},
"sortSegments": {
"message": "Sort Segments"
},
"submitCheck": {
"message": "Are you sure you want to submit this?"
},
@@ -239,6 +252,9 @@
"showSkipNotice": {
"message": "Show Notice After A Segment Is Skipped"
},
"showCategoryGuidelines": {
"message": "Show Category Help"
},
"noticeVisibilityMode0": {
"message": "Full Size Skip Notices"
},
@@ -286,6 +302,14 @@
"message": "Submit segments",
"description": "Keybind label"
},
"nextChapterKeybind": {
"message": "Next chapter",
"description": "Keybind label"
},
"previousChapterKeybind": {
"message": "Previous chapter",
"description": "Keybind label"
},
"keybindDescription": {
"message": "Select a key by typing it and choose any modifier keys you wish to use."
},
@@ -356,7 +380,7 @@
"message": "Show Time With Skips Removed"
},
"showTimeWithSkipsDescription": {
"message": "This time appears in brackets next to the current time on below the seekbar. This shows the total video duration minus any segments. This includes segments marked as only \"Show In Seekbar\"."
"message": "This time appears in brackets next to the current time on below the Seek Bar. This shows the total video duration minus any segments. This includes segments marked as only \"Show In Seek Bar\"."
},
"youHaveSkipped": {
"message": "You've skipped "
@@ -410,7 +434,7 @@
"message": "Supported Sites: "
},
"optionsInfo": {
"message": "Enable Invidious support, disable autoskip, hide buttons and more."
"message": "Enable Invidious support, disable auto skip, hide buttons and more."
},
"addInvidiousInstance": {
"message": "Add 3rd-Party Client Instance"
@@ -439,6 +463,12 @@
"minDurationDescription": {
"message": "Segments shorter than the set value will not be skipped or show in the player."
},
"enableManualSkipOnFullVideo": {
"message": "Use manual skip when a full video label exists"
},
"whatManualSkipOnFullVideo": {
"message": "For people who want to watch the video uninterrupted if it is fully sponsored or self promotion."
},
"skipNoticeDuration": {
"message": "Skip notice duration (seconds):"
},
@@ -499,7 +529,7 @@
"incorrectlyFormattedOptions": {
"message": "This JSON is not formatted correctly. Your options have not been changed."
},
"confirmNoticeTitle" : {
"confirmNoticeTitle": {
"message": "Submit Segment"
},
"submit": {
@@ -542,18 +572,43 @@
"message": "to",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"CopiedExclamation": {
"message": "Copied!",
"description": "Used after something has been copied to the clipboard. Example: 'Copied!'"
},
"generic_guideline1": {
"message": "Include segue transitions"
},
"generic_guideline2": {
"message": "Plays as if nothing was skipped"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shoutouts to causes/creators/websites/products they like."
},
"category_sponsor_guideline1": {
"message": "Paid promotions"
},
"category_sponsor_guideline2": {
"message": "Not for donations or custom merch"
},
"category_selfpromo": {
"message": "Unpaid/Self Promotion"
},
"category_selfpromo_description": {
"message": "Similar to \"sponsor\" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with."
},
"category_selfpromo_guideline1": {
"message": "Donations, memberships and custom merch"
},
"category_selfpromo_guideline2": {
"message": "Free shoutouts that don't add to the video"
},
"category_selfpromo_guideline3": {
"message": "Not for corporate designed products and merch"
},
"category_exclusive_access": {
"message": "Exclusive Access"
},
@@ -564,12 +619,24 @@
"message": "This video showcases a product, service or location that they've received free or subsidized access to",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Entire video showcases something with free or subsidized access"
},
"category_interaction": {
"message": "Interaction Reminder (Subscribe)"
},
"category_interaction_description": {
"message": "When there is a short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should be under self promotion instead."
},
"category_interaction_guideline1": {
"message": "Short reminders to like, subscribe or follow"
},
"category_interaction_guideline2": {
"message": "Includes indirect reminders to comment"
},
"category_interaction_guideline3": {
"message": "Not for general promotion, only calls to action"
},
"category_interaction_short": {
"message": "Interaction Reminder"
},
@@ -582,17 +649,35 @@
"category_intro_short": {
"message": "Intermission"
},
"category_intro_guideline1": {
"message": "Interval without actual content"
},
"category_intro_guideline2": {
"message": "Not for transitions with information"
},
"category_outro": {
"message": "Endcards/Credits"
},
"category_outro_description": {
"message": "Credits or when the YouTube endcards appear. Not for conclusions with information."
},
"category_outro_guideline1": {
"message": "Don't include content, even if endcards are on screen"
},
"category_preview": {
"message": "Preview/Recap"
},
"category_preview_description": {
"message": "Quick recap of previous episodes, or a preview of what's coming up later in the current video. Meant for edited together clips, not for spoken summaries."
"message": "Collection of clips that show what is coming up in in this video or other videos in a series where all information is repeated later in the video."
},
"category_preview_guideline1": {
"message": "Clips that appear later, or in a future video"
},
"category_preview_guideline2": {
"message": "Recap of a previous video"
},
"category_preview_guideline3": {
"message": "Not for sections that add additional content"
},
"category_filler": {
"message": "Filler Tangent/Jokes"
@@ -603,6 +688,15 @@
"category_filler_short": {
"message": "Filler"
},
"category_filler_guideline1": {
"message": "Tangential scenes only for filler or humor"
},
"category_filler_guideline2": {
"message": "Distractions, bloopers, replays"
},
"category_filler_guideline3": {
"message": "Not for scenes required to understand the topic"
},
"category_music_offtopic": {
"message": "Music: Non-Music Section"
},
@@ -612,12 +706,42 @@
"category_music_offtopic_short": {
"message": "Non-Music"
},
"category_music_offtopic_guideline1": {
"message": "Sections not in official releases"
},
"category_music_offtopic_guideline2": {
"message": "Non-music in a live performance"
},
"category_poi_highlight": {
"message": "Highlight"
},
"category_poi_highlight_description": {
"message": "The part of the video that most people are looking for. Similar to \"Video starts at x\" comments."
},
"category_poi_highlight_guideline1": {
"message": "Section most people are looking for"
},
"category_poi_highlight_guideline2": {
"message": "Can skip context"
},
"category_poi_highlight_guideline3": {
"message": "Can skip to the title or thumbnail"
},
"category_chapter": {
"message": "Chapter"
},
"category_chapter_description": {
"message": "Custom named chapters describing major sections of a video."
},
"category_chapter_guideline1": {
"message": "Don't mention sponsor brand names"
},
"category_chapter_guideline2": {
"message": "Use larger chapters for general sections"
},
"category_chapter_guideline3": {
"message": "Smaller chapters can be placed inside larger ones"
},
"category_livestream_messages": {
"message": "Livestream: Donation/Message Readings"
},
@@ -648,6 +772,9 @@
"showOverlay_full": {
"message": "Show Label"
},
"showOverlay_chapter": {
"message": "Show Chapters"
},
"autoSkipOnMusicVideos": {
"message": "Auto skip all segments when there is a non-music segment"
},
@@ -703,6 +830,10 @@
"bracketEnd": {
"message": "(End)"
},
"End": {
"message": "End",
"description": "Button that skips to the end of a segment"
},
"hiddenDueToDownvote": {
"message": "hidden: downvote"
},
@@ -716,11 +847,8 @@
"description": "This error appears in an alert when they try to whitelist a channel and the extension is unable to determine what channel they are looking at.",
"message": "Channel ID is not loaded yet. If you are using an embedded video, try using the YouTube homepage instead. This could also be caused by changes in the YouTube layout, if you think so, make a comment here:"
},
"videoInfoFetchFailed": {
"message": "It seems that something is blocking SponsorBlock's ability to get video data. Please see https://github.com/ajayyy/SponsorBlock/issues/741 for more info."
},
"youtubePermissionRequest": {
"message": "It seems that SponsorBlock is unable to reach the YouTube API. To fix this, accept the permission prompt that will appear next, wait a few seconds, and then reload the page."
"invidiousPermissionRefresh": {
"message": "The browser has revoked the permission needed to function on Invidious and other 3rd-party sites. Please click the button below to reactivate this permission."
},
"acceptPermission": {
"message": "Accept permission"
@@ -746,6 +874,13 @@
"downvoteDescription": {
"message": "Incorrect/Wrong Timing"
},
"incorrectVote": {
"message": "Incorrect"
},
"harmfulVote": {
"message": "Harmful",
"description": "Used for chapter segments when the text is harmful/offensive to remove it faster"
},
"incorrectCategory": {
"message": "Change Category"
},
@@ -781,6 +916,9 @@
"categoryPillTitleText": {
"message": "This entire video is labeled as this category and is too tightly integrated to be able to separate"
},
"chapterNameTooltipWarning": {
"message": "One of your chapter names is similar to a category. You should use categories when possible instead."
},
"experiementOptOut": {
"message": "Opt-out of all future experiments",
"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."
@@ -789,11 +927,19 @@
"message": "Hide forever"
},
"warningChatInfo": {
"message": "You got a warning and cannot submit segments temporarily. This means that we noticed you were making some common mistakes that are not malicious, please just confirm that you understand the rules and we will remove the warning. You can also join this chat using discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app"
"message": "We noticed you were making some common mistakes that are not malicious"
},
"voteRejectedWarning": {
"message": "Vote rejected due to a warning. Click to open a chat to resolve it, or come back later when you have time.",
"description": "This is an integrated chat panel that will appearing allowing them to talk to the Discord/Matrix chat without leaving their browser."
"warningTitle": {
"message": "You got a warning"
},
"questionButton": {
"message": "I have a question"
},
"warningConfirmButton": {
"message": "I understand the reason"
},
"warningError": {
"message": "Error when trying to acknowledge warning:"
},
"Donate": {
"message": "Donate"
@@ -867,7 +1013,10 @@
"LearnMore": {
"message": "Learn More"
},
"CopyDownvoteButtonInfo": {
"FullDetails": {
"message": "Full Details"
},
"CopyDownvoteButtonInfo": {
"message": "Downvotes and creates a local copy for you to resubmit"
},
"OpenCategoryWikiPage": {
@@ -947,5 +1096,123 @@
},
"openOptionsPage": {
"message": "Open options page"
},
"resetToDefault": {
"message": "Reset settings to default"
},
"confirmResetToDefault": {
"message": "Are you sure you want to reset all settings to their default values? This cannot be undone."
},
"exportSegments": {
"message": "Export segments"
},
"importSegments": {
"message": "Import segments"
},
"Import": {
"message": "Import",
"description": "Button to initiate importing segments. Appears under the textbox where they paste in the data"
},
"redeemSuccess": {
"message": "Redeem Successful!"
},
"redeemFailed": {
"message": "License key is invalid"
},
"hideUpsells": {
"message": "Hide options not available without extra payment"
},
"chooseACountry": {
"message": "Choose a country"
},
"noDiscount": {
"message": "You do not qualify for a discount"
},
"discountLink": {
"message": "Discount Link (See the pink price)"
},
"selectYourCountry": {
"message": "Select your country"
},
"alreadyDonated": {
"message": "If you've donated any amount before now, you may redeem free access by emailing:",
"description": "After the colon is an email address"
},
"cantAfford": {
"message": "If you can't afford to purchase a license, click {here} to see if you are eligible for a discount",
"description": "Keep the curly braces. The word 'here' should be translated as well."
},
"patreonSignIn": {
"message": "Sign in with Patreon"
},
"redeem": {
"message": "Redeem"
},
"joinOnPatreon": {
"message": "Subscribe on Patreon"
},
"oneTimePurchase": {
"message": "One Time Purchase"
},
"enterLicenseKey": {
"message": "Enter License Key"
},
"chaptersPage1": {
"message": "SponsorBlock crowd-sourced chapters feature is only available to people who purchase a license, or for people who are granted access for free due their past contributions"
},
"unsubmittedSegmentCounts": {
"message": "You currently have {0} on {1}",
"description": "Example: You currently have 12 unsubmitted segments on 5 videos"
},
"unsubmittedSegmentCountsZero": {
"message": "You currently have no unsubmitted segments",
"description": "Replaces 'unsubmittedSegmentCounts' string when there are no unsubmitted segments"
},
"unsubmittedSegmentsSingular": {
"message": "unsubmitted segment",
"description": "Example: You currently have 1 *unsubmitted segment* on 1 video"
},
"unsubmittedSegmentsPlural": {
"message": "unsubmitted segments",
"description": "Example: You currently have 12 *unsubmitted segments* on 5 videos"
},
"videosSingular": {
"message": "video",
"description": "Example: You currently have 3 unsubmitted segments on 1 *video*"
},
"videosPlural": {
"message": "videos",
"description": "Example: You currently have 12 unsubmitted segments on 5 *videos*"
},
"clearUnsubmittedSegments": {
"message": "Clear all segments",
"description": "Label for a button in settings"
},
"clearUnsubmittedSegmentsConfirm": {
"message": "Are you sure you want to clear all your unsubmitted segments?",
"description": "Confirmation message for the Clear unsubmitted segments button"
},
"showUnsubmittedSegments": {
"message": "Show segments",
"description": "Show/hide button for the unsubmitted segments list"
},
"hideUnsubmittedSegments": {
"message": "Hide segments",
"description": "Show/hide button for the unsubmitted segments list"
},
"videoID": {
"message": "Video ID",
"description": "Header of the unsubmitted segments list"
},
"segmentCount": {
"message": "Segment Count",
"description": "Header of the unsubmitted segments list"
},
"actions": {
"message": "Actions",
"description": "Header of the unsubmitted segments list"
},
"exportSegmentsAsURL": {
"message": "Share as URL"
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Mostrar aviso después de que se omita un segmento"
},
"showCategoryGuidelines": {
"message": "Mostrar Ayuda de la Categoría"
},
"noticeVisibilityMode0": {
"message": "Avisos de Omisión de Tamaño Completo"
},
@@ -542,11 +545,23 @@
"message": "a",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Incluye transiciones entre segmentos"
},
"generic_guideline2": {
"message": "Se reproduce como si no se hubiera omitido nada"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Promoción pagada, recomendaciones pagadas y anuncios directos. No para promoción propia o anuncios gratuitos a causas/creadores/sitios web/productos que les gusten."
"message": "Promoción pagada, recomendaciones pagadas y anuncios directos. No para promoción propia o agradecimientos gratuitos a causas/creadores/sitios web/productos que les gusten."
},
"category_sponsor_guideline1": {
"message": "Promociones pagadas"
},
"category_sponsor_guideline2": {
"message": "No para donaciones o mercancía personalizada"
},
"category_selfpromo": {
"message": "Promoción Propia/No Remunerada"
@@ -554,6 +569,15 @@
"category_selfpromo_description": {
"message": "Similar a \"sponsor\", excepto que para la promoción propia o no remunerada. Esto incluye secciones sobre mercancía, donaciones o información sobre con quiénes colaboraron."
},
"category_selfpromo_guideline1": {
"message": "Donaciones, membresías y mercancía personalizada"
},
"category_selfpromo_guideline2": {
"message": "Agradecimientos gratuitos que no contribuyen al video"
},
"category_selfpromo_guideline3": {
"message": "No para productos ni mercancía diseñados por empresas"
},
"category_exclusive_access": {
"message": "Acceso Exclusivo"
},
@@ -564,11 +588,23 @@
"message": "Este video exhibe un producto, servicio o ubicación al que han recibido acceso gratuito o subsidiado",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Todo el video muestra algo con acceso gratuito o subsidiado"
},
"category_interaction": {
"message": "Recordatorio de Interacción (Suscribir)"
},
"category_interaction_description": {
"message": "Cuando hay un breve recordatorio para dar like, suscribirse o seguirlos en medio del contenido. Si es largo o sobre algo específico, debería estar bajo autopromoción en su lugar."
"message": "Cuando hay un breve recordatorio para dar me gusta, suscribirse o seguirlos en medio del contenido. Si es largo o sobre algo específico, debería estar bajo promoción propia en su lugar."
},
"category_interaction_guideline1": {
"message": "Breve recordatorio para dar me gusta, suscribirse o seguir"
},
"category_interaction_guideline2": {
"message": "Incluye recordatorios indirectos para comentar"
},
"category_interaction_guideline3": {
"message": "No para promoción general, solo llamadas a la acción"
},
"category_interaction_short": {
"message": "Recordatorio de Interacción"
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Intermisión"
},
"category_intro_guideline1": {
"message": "Intervalo sin contenido real"
},
"category_intro_guideline2": {
"message": "No para transiciones con información"
},
"category_outro": {
"message": "Tarjetas/Créditos"
},
"category_outro_description": {
"message": "Créditos o cuando aparecen las tarjetas finales de YouTube. No para conclusiones con información."
},
"category_outro_guideline1": {
"message": "No incluyas contenido, aun si las tarjetas finales están en pantalla"
},
"category_preview": {
"message": "Vista previa/Recapitulación"
},
"category_preview_description": {
"message": "Recapitulación rápida de los episodios anteriores, o una vista previa de lo que va a ocurrir más adelante en el vídeo actual. Está pensado para clips editados juntos, no para resúmenes hablados."
},
"category_preview_guideline1": {
"message": "Clips que aparecen más tarde o en un video futuro"
},
"category_preview_guideline2": {
"message": "Recapitulación de un video anterior"
},
"category_preview_guideline3": {
"message": "No para secciones que añaden contenido adicional"
},
"category_filler": {
"message": "Tangentes de Relleno/Chistes"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Relleno"
},
"category_filler_guideline1": {
"message": "Escenas tangenciales solo de relleno o humor"
},
"category_filler_guideline2": {
"message": "Distracciones, bloopers, repeticiones"
},
"category_filler_guideline3": {
"message": "No para escenas requeridas para entender el tema"
},
"category_music_offtopic": {
"message": "Música: Sección sin musica"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Sin Música"
},
"category_music_offtopic_guideline1": {
"message": "Secciones que no están en versiones oficiales"
},
"category_music_offtopic_guideline2": {
"message": "Secciones sin música en un espectáculo en vivo"
},
"category_poi_highlight": {
"message": "Destacado"
},
"category_poi_highlight_description": {
"message": "La parte del video que la mayoría de gente está buscando. Similar a los comentarios que dicen \"El video comienza en x\"."
},
"category_poi_highlight_guideline1": {
"message": "La sección que la mayoría de personas están buscando"
},
"category_poi_highlight_guideline2": {
"message": "Puede omitir contexto"
},
"category_poi_highlight_guideline3": {
"message": "Puede saltar al título o miniatura"
},
"category_livestream_messages": {
"message": "Directo: Lecturas de donaciones y mensajes"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Aprenda Más"
},
"FullDetails": {
"message": "Detalles Completos"
},
"CopyDownvoteButtonInfo": {
"message": "Vota negativamente y crea una copia local para que la puedas volver a enviar"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Abrir página de opciones"
},
"resetToDefault": {
"message": "Restablecer los ajustes por defecto"
},
"confirmResetToDefault": {
"message": "¿Estás seguro de que quieres restablecer todos los ajustes a sus valores por defecto? Esto no se puede deshacer."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Kuva segmendi vahelejätmisel teatis"
},
"showCategoryGuidelines": {
"message": "Kuva kategooria abi"
},
"noticeVisibilityMode0": {
"message": "Täissuuruses vahelejätmise teavitused"
},
@@ -533,18 +536,39 @@
"message": "kuni",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Kasuta sujuvaid üleminekuid"
},
"generic_guideline2": {
"message": "Mängib nii, nagu midagi poleks vahele jäetud"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Tasulised promod, tasulised viited ja otsesed reklaamid. Pole mõeldud enesepromo või tasuta petitsioonide/autorite/veebilehtede/toodete mainimiste puhul."
},
"category_sponsor_guideline1": {
"message": "Tasulised kampaaniad"
},
"category_sponsor_guideline2": {
"message": "Mitte annetuste ja oma loodud kauba jaoks"
},
"category_selfpromo": {
"message": "Tasumata/enesepromo"
},
"category_selfpromo_description": {
"message": "Sarnaneb \"sponsorile\", ent on mõeldud tasumata või enesepromo jaoks. Selle alla kuuluvad jaotised oma müüdava kauba, annetuste ja koostööpartnerite kohta."
},
"category_selfpromo_guideline1": {
"message": "Annetused, liikmesused ja oma loodud kaup"
},
"category_selfpromo_guideline2": {
"message": "Tasuta mainimised, mis ei ole video sisuks olulised"
},
"category_selfpromo_guideline3": {
"message": "Mitte ettevõtete disainitud toodete ja kauba jaoks"
},
"category_exclusive_access": {
"message": "Eksklusiivne ligipääs"
},
@@ -555,12 +579,24 @@
"message": "See video esitleb toodet, teenust või asukohta, millele isik on tasuta või toetusega ligipääsu saanud",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Kogu video kuvab midagi tasuta või toetusega ligipääsuga"
},
"category_interaction": {
"message": "Tegutsemise meeldetuletus (kanali tellimine)"
},
"category_interaction_description": {
"message": "Lühike sisukeskne meeldetuletus anda videole meeldib, tellida kanalit või jälgida autorit. Kui see on pikk või millegi kindlaga seotud, peaks see olema enesepromo all."
},
"category_interaction_guideline1": {
"message": "Lühikesed meeldetuletused vajutada \"meeldib\", \"telli\" või \"jälgi\""
},
"category_interaction_guideline2": {
"message": "Sisaldab mitteotseseid meeldetuletusi kommenteerimiseks"
},
"category_interaction_guideline3": {
"message": "Mitte üldise promo jaoks, vaid üleskutsed tegevustele"
},
"category_interaction_short": {
"message": "Tegutsemise meeldetuletus"
},
@@ -573,18 +609,36 @@
"category_intro_short": {
"message": "Vaheaeg"
},
"category_intro_guideline1": {
"message": "Intervall ilma tegeliku sisuta"
},
"category_intro_guideline2": {
"message": "Mitte informatsiooniga üleminekute jaoks"
},
"category_outro": {
"message": "Lõpukaardid/-tiitrid"
},
"category_outro_description": {
"message": "Tiitrid või YouTube'i lõpukaardid. Pole mõeldud informatsiooniga järelduste jaoks."
},
"category_outro_guideline1": {
"message": "Ära kaasa sisu, isegi kui lõputiitrid on ekraanil"
},
"category_preview": {
"message": "Eelvaade/kokkuvõte"
},
"category_preview_description": {
"message": "Kiire kokkuvõte eelmistest episoodidest või eelvaade videos hiljem tulevast. Mõeldud valmis töödeldud klippide, mitte suuliste kokkuvõtete jaoks."
},
"category_preview_guideline1": {
"message": "Klipid, mida kuvatakse hiljem või tulevases videos"
},
"category_preview_guideline2": {
"message": "Eelmise video kokkuvõte"
},
"category_preview_guideline3": {
"message": "Mitte jaotistele, mis lisavad täiendavat sisu"
},
"category_filler": {
"message": "Täitesisu/naljad"
},
@@ -594,6 +648,15 @@
"category_filler_short": {
"message": "Täide"
},
"category_filler_guideline1": {
"message": "Sisuga mitteseonduvad täite- või huumoristseenid"
},
"category_filler_guideline2": {
"message": "Tähelepanu hajutajad, saatevead, kordused"
},
"category_filler_guideline3": {
"message": "Mitte stseenid, mida on teema mõistmiseks vaja vaadata"
},
"category_music_offtopic": {
"message": "Muusika: mitte-muusika jaotis"
},
@@ -603,12 +666,27 @@
"category_music_offtopic_short": {
"message": "Mitte-muusika"
},
"category_music_offtopic_guideline1": {
"message": "Jaotised, mida avalikes väljalasetes pole"
},
"category_music_offtopic_guideline2": {
"message": "Mitte-muusika kontserdil"
},
"category_poi_highlight": {
"message": "Esiletõst"
},
"category_poi_highlight_description": {
"message": "Video osa, mida enamus inimesed otsivad. Sarnaneb kommentaaridele stiilis \"video algab ajal x\"."
},
"category_poi_highlight_guideline1": {
"message": "Jaotis, mida inimesed enim otsivad"
},
"category_poi_highlight_guideline2": {
"message": "Aitab konteksti vahele jätta"
},
"category_poi_highlight_guideline3": {
"message": "Aitab pealkirja või pisipildi sisuni viia"
},
"category_livestream_messages": {
"message": "Otseülekanne: annetuste ja sõnumite lugemine"
},
@@ -834,6 +912,9 @@
"LearnMore": {
"message": "Lisateave"
},
"FullDetails": {
"message": "Täielikud üksikasjad"
},
"CopyDownvoteButtonInfo": {
"message": "Annab vastuhääle ja loob kohaliku koopia, mida saad uuesti saata"
},
@@ -906,10 +987,19 @@
"change": {
"message": "Muuda"
},
"youtubeKeybindWarning": {
"message": "See on YouTube'i sisseehitatud otsetee. Kas soovid kindlasti seda kasutada?"
},
"betaServerWarning": {
"message": "BEETAserver on lubatud!"
},
"openOptionsPage": {
"message": "Ava valikute leht"
},
"resetToDefault": {
"message": "Lähtesta sätted vaikeväärtustele"
},
"confirmResetToDefault": {
"message": "Kas soovid kindlasti kõik sätted vaikeväärtustele lähtestada? Seda ei saa tagasi võtta."
}
}

View File

@@ -44,7 +44,7 @@
"message": "Älä näytä koskaan"
},
"hitGoBack": {
"message": "Paina 'älä ohita' palatakasesi kohtaan, jossa olit."
"message": "Paina 'älä ohita' palataksesi kohtaan, jossa olit."
},
"unskip": {
"message": "Älä ohita"
@@ -180,7 +180,7 @@
"message": "Näytä painikkeet YouTuben soittimessa"
},
"hideButtons": {
"message": "Piilota painikkeet YouTuben soittimesta"
"message": "Piilota painikkeet YouTuben soittimessa"
},
"hideButtonsDescription": {
"message": "Tämä piilottaa YouTuben soittimessa näkyvät, ohitettavien aikojen lähettämiseen käytetyt painikkeet."
@@ -198,7 +198,7 @@
"message": "Piilota info-painike automaattisesti"
},
"hideDeleteButton": {
"message": "Piilota poista-painike YouTuben soittimesta"
"message": "Piilota poista-painike YouTuben soittimessa"
},
"showDeleteButton": {
"message": "Näytä poista-painike YouTuben soittimessa"
@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Näytä ilmoitus ohitetun osion jälkeen"
},
"showCategoryGuidelines": {
"message": "Näytä kategorian ohje"
},
"noticeVisibilityMode0": {
"message": "Täysikokoiset ohitusilmoitukset"
},
@@ -542,11 +545,23 @@
"message": "-",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Sisällytä siirtymät osioon"
},
"generic_guideline2": {
"message": "Video toistuu ikään kuin mitään ei olisi ohitettu"
},
"category_sponsor": {
"message": "Sponsori"
},
"category_sponsor_description": {
"message": "Maksettu mainostus, maksetut viittaukset ja suorat mainokset. Ei itsensä mainostukselle tai huikkauksille kampanjoista/luojista/nettisivuista/tuotteista, joista he pitävät."
"message": "Maksettu mainostus, maksetut viittaukset ja suorat mainokset. Ei itsensä mainostukselle tai ilmaisille maininnoille kampanjoista/luojista/nettisivuista/tuotteista, joista he pitävät."
},
"category_sponsor_guideline1": {
"message": "Maksetut mainostukset"
},
"category_sponsor_guideline2": {
"message": "Ei lahjoituksille tai omalle kauppatavaralle"
},
"category_selfpromo": {
"message": "Maksamaton/Itsensä mainostus"
@@ -554,6 +569,15 @@
"category_selfpromo_description": {
"message": "Samankaltainen \"sponsorin\" kanssa, mutta maksamattomalle tai itsensä mainostukselle. Tämä sisältää osioita kauppatavarasta, lahjoituksista tai tietoa siitä, kenen kanssa he ovat tehneet yhteistyötä."
},
"category_selfpromo_guideline1": {
"message": "Lahjoitukset, jäsenyydet ja oma kauppatavara"
},
"category_selfpromo_guideline2": {
"message": "Ilmaiset maininnat, jotka eivät tuo videolle lisäarvoa"
},
"category_selfpromo_guideline3": {
"message": "Ei yritysten luomille tuotteille tai kauppatavaralle"
},
"category_exclusive_access": {
"message": "Yksinoikeudellinen ensikatsaus"
},
@@ -564,12 +588,24 @@
"message": "Tämä video esittelee tuotteen, palvelun tai sijainnin, johon he ovat saaneet ilmaisen tai tuetun käyttöoikeuden",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Koko video esittelee jotakin, jonka käyttöoikeus on saatu veloituksetta tai tuetusti"
},
"category_interaction": {
"message": "Vuorovaikutusmuistutus (tilaaminen)"
},
"category_interaction_description": {
"message": "Lyhyt muistutus tykätä, tilata tai seurata sisällön keskellä. Jos se on pitkä tai koskee jotain tiettyä asiaa, tulisi se merkitä mielummin itsensä mainostamiseksi."
},
"category_interaction_guideline1": {
"message": "Lyhyet muistutukset tykätä, tilata tai seurata"
},
"category_interaction_guideline2": {
"message": "Sisältää epäsuorat muistutukset kommenttien jättämisestä"
},
"category_interaction_guideline3": {
"message": "Ei yleiselle mainostukselle, vain toimintapyynnöille"
},
"category_interaction_short": {
"message": "Vuorovaikutusmuistutus"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Tauko"
},
"category_intro_guideline1": {
"message": "Tauko ilman varsinaista sisältöä"
},
"category_intro_guideline2": {
"message": "Ei tietoa sisältäville siirtymille"
},
"category_outro": {
"message": "Loppukortit/-tekstit"
},
"category_outro_description": {
"message": "Lopputekstit tai kun YouTuben loppukortit tulevat näkyviin. Ei lopetuksille joissa on tietoa."
},
"category_outro_guideline1": {
"message": "Älä sisällytä sisältöä, vaikka loppukortit olisivatkin näytöllä"
},
"category_preview": {
"message": "Esikatselu/Kertaus"
},
"category_preview_description": {
"message": "Nopea kertaus aiemmista jaksoista, tai esikatselu siitä, mitä on tulossa myöhemmin nykyisessä videossa. Tarkoitettu yhteen editoituja klippejä varten, ei puhutuille yhteenvedoille."
},
"category_preview_guideline1": {
"message": "Myöhemmin tai tulevassa videossa ilmestyvät klipit"
},
"category_preview_guideline2": {
"message": "Kertaus edellisestä videosta"
},
"category_preview_guideline3": {
"message": "Ei osioille, jotka lisäävät uutta sisältöä"
},
"category_filler": {
"message": "Epäolennainen täytesisältö/Vitsit"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Täytesisältö"
},
"category_filler_guideline1": {
"message": "Täytteeksi tai huumoriksi lisätyt toissijaiset kohtaukset"
},
"category_filler_guideline2": {
"message": "Häiriöt, blooperit, uusinnat"
},
"category_filler_guideline3": {
"message": "Ei aiheen ymmärtämiseen tarvittavia kohtauksia varten"
},
"category_music_offtopic": {
"message": "Musiikki: Musiikiton osa"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Musiikiton"
},
"category_music_offtopic_guideline1": {
"message": "Osat, jotka eivät ole virallisissa julkaisuissa"
},
"category_music_offtopic_guideline2": {
"message": "Musiikittomat kohdat live-esityksissä"
},
"category_poi_highlight": {
"message": "Kohokohta"
},
"category_poi_highlight_description": {
"message": "Kohta videossa, mitä suurin osa ihmisistä etsivät.\nSamankaltainen \"Video alkaa kohdassa x\" kommenttien kanssa."
},
"category_poi_highlight_guideline1": {
"message": "Kohta mitä useimmat ihmiset etsivät"
},
"category_poi_highlight_guideline2": {
"message": "Voi ohittaa kontekstin"
},
"category_poi_highlight_guideline3": {
"message": "Voi ohittaa otsikon tai pikkukuvan viittaamaan kohtaan"
},
"category_livestream_messages": {
"message": "Livestream: lahjoitusten/viestien lukeminen"
},
@@ -779,7 +857,7 @@
"message": "Tämä osio on suuri. Jos koko video koskee yhtä aihetta, vaihda \"Ohita\" valintaan \"Koko video\". Katso lisätietoja säännöistä."
},
"categoryPillTitleText": {
"message": "Koko video on merkitty tällä luokituksella ja se on liian tiiviisti integroitu eroteltavaksi"
"message": "Koko video on merkitty tällä kategorialla ja on erotettavaksi liian tiiviisti integroitu"
},
"experiementOptOut": {
"message": "Jättäydy pois kaikista tulevista kokeiluista",
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Opi lisää"
},
"FullDetails": {
"message": "Täydet tiedot"
},
"CopyDownvoteButtonInfo": {
"message": "Äänestää alas ja luo paikallisen kopion, jonka voit lähettää uudelleen"
},
@@ -883,10 +964,10 @@
"message": "Tämä vaikuttaa sinun osioihin välittömästi"
},
"downvote": {
"message": "Äänestä puolesta"
"message": "Äänestä alas"
},
"upvote": {
"message": "Äänestä vastaan"
"message": "Äänestä ylös"
},
"hideSegment": {
"message": "Piilota osio"
@@ -930,7 +1011,7 @@
"description": "Option label"
},
"unbind": {
"message": "Poista valinta",
"message": "Poista kytkös",
"description": "Unbind keyboard shortcut"
},
"notSet": {
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Avaa asetussivu"
},
"resetToDefault": {
"message": "Palauta oletusasetukset"
},
"confirmResetToDefault": {
"message": "Haluatko varmasti palauttaa kaikki asetukset oletusarvoihin? Tätä ei voi perua."
}
}

View File

@@ -1 +1,118 @@
{}
{
"fullName": {
"message": "SponsorBlock para YouTube - I-skip ang mga Sponsorships",
"description": "Name of the extension."
},
"Description": {
"message": "I-skip ang mga sponsorships, subscription begging at marami pa sa mga YouTube videos. I-report ang mga sponsor sa videos na napapanood mo upang makatipid sa oras ng iba.",
"description": "Description of the extension."
},
"429": {
"message": "Masyadong kang maraming beses na nagsumite ng sponsor times para sa video na ito, sigurado ka na marami yan?"
},
"409": {
"message": "Naisumite na ito noon"
},
"channelWhitelisted": {
"message": "Whitelisted na ang channel na ito!"
},
"Segment": {
"message": "segment"
},
"Segments": {
"message": "segments"
},
"upvoteButtonInfo": {
"message": "I-upvote ang submission na ito"
},
"reportButtonTitle": {
"message": "I-report"
},
"reportButtonInfo": {
"message": "I-report ang submission na ito bilang mali."
},
"Dismiss": {
"message": "I-dismiss"
},
"Loading": {
"message": "Loading..."
},
"Hide": {
"message": "Hindi ipakita"
},
"hitGoBack": {
"message": "Pindutin ang \"unskip\" para bumalik ka sa iyong dating posisyon sa video."
},
"unskip": {
"message": "I-unskip"
},
"reskip": {
"message": "I-reskip"
},
"unmute": {
"message": "I-unmute"
},
"paused": {
"message": "Naka-pause na"
},
"manualPaused": {
"message": "Tigilan ang timer"
},
"confirmMSG": {
"message": "Para sa pag-edit o tanggalin ng mga individual values, pindutin ang info button o ang extension icon sa kanang sulok sa itaas."
},
"clearThis": {
"message": "Sigurado ka bang gusto mong tanggalin ito?"
},
"Unknown": {
"message": "May error na nagkaroon sa pagsusumite sa iyong sponsor times, subukang muli mamaya."
},
"sponsorFound": {
"message": "May mga segments sa database para sa video na ito!"
},
"sponsor404": {
"message": "Walang nakitang segment"
},
"sponsorStart": {
"message": "Simula ang Segment Ngayon"
},
"sponsorEnd": {
"message": "Nagtatapos ang Segment Ngayon"
},
"sponsorCancel": {
"message": "Kanselahin ang Paggawa ng Segment"
},
"noVideoID": {
"message": "Walang YouTube video na nakita.\nKung mali ito, i-refresh ang tab mo."
},
"refreshSegments": {
"message": "I-refresh ang mga segments"
},
"success": {
"message": "Tagumpay na!"
},
"voted": {
"message": "Bumoto na!"
},
"connectionError": {
"message": "Nagkaroon ng error sa koneksyon. Error code: "
},
"clearTimes": {
"message": "I-clear ang mga segments"
},
"openPopup": {
"message": "I-buksan ang SponsorBlock popup"
},
"closePopup": {
"message": "Isara ang Popup"
},
"SubmitTimes": {
"message": "I-submit ang mga segments"
},
"submitCheck": {
"message": "Sigurado ka bang gusto mong isumite ito?"
},
"whitelistChannel": {
"message": "I-whitelist itong channel"
}
}

View File

@@ -86,7 +86,7 @@
"message": "Annuler la création de segment"
},
"noVideoID": {
"message": "Aucune vidéo YouTube trouvée.\nActualisez l'onglet si il est censé y en avoir une."
"message": "Aucune vidéo YouTube trouvée.\nActualisez l'onglet s'il s'agit d'une erreur."
},
"refreshSegments": {
"message": "Rafraîchir les segments"
@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Notifier après qu'un segment ait été sauté"
},
"showCategoryGuidelines": {
"message": "Affiche l'aide de la catégorie"
},
"noticeVisibilityMode0": {
"message": "Notifications de passage"
},
@@ -542,17 +545,38 @@
"message": "à",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Inclure les transitions entre les segments"
},
"generic_guideline2": {
"message": "Jouer comme si rien n'avait été passé"
},
"category_sponsor": {
"message": "Message sponsorisé"
},
"category_sponsor_description": {
"message": "Promotion rémunérée, parrainage rémunéré et publicité directe. Pas pour l'autopromotion ou les présentations gratuites de causes, de créateurs, de sites web ou de produits qu'ils aiment."
},
"category_sponsor_guideline1": {
"message": "Promotions rémunérées"
},
"category_sponsor_guideline2": {
"message": "Pas pour les dons ou les produits dérivés"
},
"category_selfpromo": {
"message": "Non rémunéré/autopromotion"
},
"category_selfpromo_description": {
"message": "Semblable au \"sponsor\", excepté pour la promotion non rémunérée ou l'auto-promotion. Cela inclut les marchandises, les dons et les informations sur leurs collaborateurs."
"message": "Semblable aux \"messages commerciaux\", excepté pour la promotion non rémunérée ou l'autopromotion. Cela inclut les marchandises, les dons et les informations sur leurs collaborateurs."
},
"category_selfpromo_guideline1": {
"message": "Dons, abonnements (payant) et produits dérivés"
},
"category_selfpromo_guideline2": {
"message": "Remerciements gratuits qui n'apportent aucune information à la vidéo"
},
"category_selfpromo_guideline3": {
"message": "Pas pour des produits dérivés fait par des marques"
},
"category_exclusive_access": {
"message": "Accès exclusif"
@@ -564,12 +588,24 @@
"message": "Cette vidéo présente un produit, un service ou un lieu pour lequel un accès gratuit ou subventionné a été reçu",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Toute la vidéo présente quelque chose dont le créateur a eu un accès gratuit ou subventionné"
},
"category_interaction": {
"message": "Rappel d'interaction (abonnement)"
},
"category_interaction_description": {
"message": "Lorsqu'il y a un bref rappel pour aimer, s'abonner ou les suivre parmi le contenu. Si le message est long ou porte sur quelque chose de spécifique, cela devrait plutôt être classé comme une autopromotion."
},
"category_interaction_guideline1": {
"message": "Rappels courts à like, s'abonner ou suivre"
},
"category_interaction_guideline2": {
"message": "Inclut des rappels indirects à commenter"
},
"category_interaction_guideline3": {
"message": "Pas pour la promotion générale, seulement les appels à l'interaction"
},
"category_interaction_short": {
"message": "Rappel d'interaction"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Entracte"
},
"category_intro_guideline1": {
"message": "Intervalle sans contenu réel"
},
"category_intro_guideline2": {
"message": "Pas pour les transitions avec des informations"
},
"category_outro": {
"message": "Générique de fin"
},
"category_outro_description": {
"message": "Crédits ou écrans de fin YouTube. Pas pour les conclusions contenant des informations."
},
"category_outro_guideline1": {
"message": "Ne dois pas inclure de contenu, même si les écrans de fin sont apparus"
},
"category_preview": {
"message": "Aperçu/Résumé"
},
"category_preview_description": {
"message": "Résumé rapide des épisodes précédents, ou aperçu de ce qui se passera plus tard dans la vidéo en cours. Pour les plans collectifs édités, pas pour les résumés parlés."
},
"category_preview_guideline1": {
"message": "Clips apparaissant plus tard ou dans une prochaine vidéo"
},
"category_preview_guideline2": {
"message": "Récapitulatif d'une vidéo précédente"
},
"category_preview_guideline3": {
"message": "Pas pour les sections qui ajoutent du contenu supplémentaire"
},
"category_filler": {
"message": "Digressions/Blagues"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Remplissage"
},
"category_filler_guideline1": {
"message": "Scènes digressives uniquement pour le remplissage ou l'humour"
},
"category_filler_guideline2": {
"message": "Distractions, bêtisiers, replays"
},
"category_filler_guideline3": {
"message": "Pas pour les scènes requises pour comprendre le sujet"
},
"category_music_offtopic": {
"message": "Musique : Segment non musical"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Hors musique"
},
"category_music_offtopic_guideline1": {
"message": "Sections qui ne sont pas dans la musique officielle"
},
"category_music_offtopic_guideline2": {
"message": "Pas de musique pendant les concerts en direct"
},
"category_poi_highlight": {
"message": "Point essentiel"
},
"category_poi_highlight_description": {
"message": "La partie de la vidéo que la plupart des gens veulent voir. Similaire à \"la vidéo commence à x mins\"."
},
"category_poi_highlight_guideline1": {
"message": "Section la plus regardée"
},
"category_poi_highlight_guideline2": {
"message": "Peut sauter le contexte"
},
"category_poi_highlight_guideline3": {
"message": "Peut passer au sujet cité dans le titre ou la miniature"
},
"category_livestream_messages": {
"message": "Stream : lecture de dons et messages"
},
@@ -832,7 +910,7 @@
"message": "La soumission peut être effectuée dans le popup en appuyant sur le bouton \"le segment commence maintenant\" ou dans le lecteur vidéo avec les boutons du lecteur."
},
"helpPageSubmitting2": {
"message": "Cliquer sur le bouton de lecture indique le début d'un segment et cliquer sur l'icône d'arrêt indique la fin. Vous pouvez préparer plusieurs sponsors avant d'appuyer sur Soumettre. Cliquer sur le bouton de soumission enverra le segment. Cliquer sur le poubelle supprimera vos segments."
"message": "Cliquer sur le bouton de lecture indique le début d'un segment et cliquer sur l'icône d'arrêt indique sa fin. Vous pouvez préparer plusieurs messages commerciaux avant d'appuyer sur Soumettre. Cliquer sur le bouton Téléverser soumettra vos segments. Cliquer sur la poubelle supprimera vos segments."
},
"Editing": {
"message": "Édition en cours"
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "En savoir plus"
},
"FullDetails": {
"message": "Tous les détails"
},
"CopyDownvoteButtonInfo": {
"message": "Vote contre et crée une copie locale à soumettre à nouveau"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Ouvrir la page d'options"
},
"resetToDefault": {
"message": "Rétablir les paramètres par défaut"
},
"confirmResetToDefault": {
"message": "Êtes-vous sûr de vouloir rétablir tous les paramètres ? Cette action ne peut pas être annulée."
}
}

View File

@@ -164,6 +164,9 @@
"copyPublicID": {
"message": "Kopiraj javni UserID"
},
"copySegmentID": {
"message": "Kopiraj ID odsječka"
},
"discordAdvert": {
"message": "Pridružite nam se u službenoj Discord zajednici i pošaljite svoje prijedloge i povrate informacije!"
},
@@ -214,6 +217,21 @@
"message": "Izvorni kod",
"description": "Used on Firefox Store Page"
},
"setSkipShortcut": {
"message": "Preskoči odsječak",
"description": "Keybind label"
},
"setStartSponsorShortcut": {
"message": "Pokreni/prekini odsječak",
"description": "Keybind label"
},
"setSubmitKeybind": {
"message": "Pošalji odsječke",
"description": "Keybind label"
},
"keybindDescription": {
"message": "Odaberi tipku upisom tipke i odaberi modifikatorske tipke koje želiš koristiti."
},
"0": {
"message": "Veza istekla. Provjerite svoju internetski vezu. Ako vaš internet radi, poslužitelj je vjerojatno preopterećen ili nedostupan."
},
@@ -236,6 +254,13 @@
"skip": {
"message": "Preskoči"
},
"mute": {
"message": "Isključi zvuk"
},
"full": {
"message": "Cijeli video",
"description": "Used for the name of the option to label an entire video as sponsor or self promotion."
},
"skip_category": {
"message": "Preskočiti {0}?"
},
@@ -246,6 +271,14 @@
"message": "Preskočiti na {0}?",
"description": "Used for skipping to things (Skip to Highlight)"
},
"skipped": {
"message": "Preskočeno: {0}",
"description": "Example: Sponsor Skipped"
},
"muted": {
"message": "Isključen zvuk: {0}",
"description": "Example: Sponsor Muted"
},
"skipped_to_category": {
"message": "Preskočeno na {0}",
"description": "Used for skipping to things (Skipped to Highlight)"
@@ -256,6 +289,12 @@
"enableAutoSkip": {
"message": "Aktiviraj automatsko preskakanje"
},
"audioNotification": {
"message": "Zvučna obavijest pri preskakanju"
},
"audioNotificationDescription": {
"message": "Zvučna obavijest o preskakanju reproducirat će zvuk kad god se odsječak preskoči. Ako je deaktivirano (ili je automatsko preskakanje deaktivirano), zvuk se neće reproducirati."
},
"minLower": {
"message": "min"
},
@@ -271,6 +310,9 @@
"statusReminder": {
"message": "Provjerite status.sponsor.ajay.app za status poslužitelja."
},
"changeUserID": {
"message": "Uvezi/Izvezi svoj korisnički ID"
},
"setUserID": {
"message": "Postavi UserID"
},
@@ -313,6 +355,9 @@
"skipNoticeDurationDescription": {
"message": "Obavijest o preskakanju ostat će na zaslonu barem ovoliko dugo. Za ručno preskakanje može biti duže vidljivo."
},
"showUploadButton": {
"message": "Prikaži gumb za prijenos"
},
"customServerAddress": {
"message": "Adresa SponsorBlock poslužitelja"
},
@@ -322,9 +367,21 @@
"reset": {
"message": "Resetiraj"
},
"mobileUpdateInfo": {
"message": "Sada se podržava m.youtube.com"
},
"exportOptions": {
"message": "Uvezi/Izvezi sve postavke"
},
"exportOptionsCopy": {
"message": "Uredi/kopiraj"
},
"exportOptionsDownload": {
"message": "Spremi u datoteku"
},
"exportOptionsUpload": {
"message": "Učitaj iz datoteke"
},
"setOptions": {
"message": "Postavi opcije"
},
@@ -371,6 +428,12 @@
"category_selfpromo_description": {
"message": "Slično kao „sponzor”, osim za neplaćenu promociju i samopromociju. To uključuje odlomke marketinga, donacija ili informacija o tome, s kim su surađivali."
},
"category_exclusive_access": {
"message": "Eksluzivni pristup"
},
"category_exclusive_access_description": {
"message": "Samo za označavanje cijelih videa. Koristi se kad video prikazuje proizvod, uslugu ili lokaciju za koje su dobili besplatan ili subvencionirani pristup."
},
"category_interaction": {
"message": "Podsjetnik interakcije (Pretplati se)"
},
@@ -419,6 +482,18 @@
"manualSkip": {
"message": "Ručno preskakanje"
},
"disable": {
"message": "Deaktiviraj"
},
"autoSkip_POI": {
"message": "Automatski skoči na početak"
},
"manualSkip_POI": {
"message": "Pitaj kad se učitava video"
},
"showOverlay_full": {
"message": "Prikaži oznaku"
},
"muteSegments": {
"message": "Dopustite isječke koji isključuju zvuk umjesto da ga preskaču"
},
@@ -436,6 +511,9 @@
"enableTestingServer": {
"message": "Omogućite poslužitelj za beta testiranje"
},
"testingServerWarning": {
"message": "Svi podnesci i glasovi NEĆE BROJATI u glavnom poslužitelju dok se povezuješ s testnim poslužiteljem. Obavezno deaktiviraj ovo kada želiš slati podneske."
},
"bracketNow": {
"message": "(sada)"
},
@@ -446,7 +524,7 @@
"message": "Odaberi kategoriju"
},
"enableThisCategoryFirst": {
"message": "Da biste poslali segmente s kategorijom \"{0}\", morate je omogućiti u postavkama. Sada ćete biti preusmjereni na postavke.",
"message": "Za slanje odsječaka kategorije „{0}, moraš je aktivirati u postavkama. Sada ćemo te preusmjeriti na postavke.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
@@ -455,6 +533,9 @@
"bracketEnd": {
"message": "(kraj)"
},
"manuallyHidden": {
"message": "ručno skriveno"
},
"downvoteDescription": {
"message": "Neispravno/krivo vrijeme"
},
@@ -480,12 +561,28 @@
"categoryUpdate2": {
"message": "Otvori opcije za preskakanje uvoda, kraja, proizvoda itd."
},
"help": {
"message": "Pomoć"
},
"GotIt": {
"message": "Razumijem",
"description": "Used as the button to dismiss a tooltip"
},
"categoryPillTitleText": {
"message": "Ovaj cijeli video označen je kao ova kategorija i previše je integriran da bi se mogao odvojiti"
},
"hideForever": {
"message": "Sakrij zauvijek"
},
"Donate": {
"message": "Doniraj"
},
"helpPageThanksForInstalling": {
"message": "Hvala na instaliranju SponsorBlocka."
},
"helpPageFeatureDisclaimer": {
"message": "Mnoge funkcije su standardno deaktivirane. Ako želiš preskočiti uvode, završne dijelove, koristiti Invidious, itd., aktiviraj ih niže dolje. Također možeš sakriti/prikazati elemente korisničkog sučelja."
},
"Submitting": {
"message": "Slanje"
},
@@ -518,5 +615,61 @@
},
"ChangeCategoryTooltip": {
"message": "Ovo će se odmah primijeniti na vaše isječke"
},
"hideSegment": {
"message": "Sakri odsječak"
},
"dayAbbreviation": {
"message": "d",
"description": "100d"
},
"hourAbbreviation": {
"message": "h",
"description": "100h"
},
"optionsTabBehavior": {
"message": "Ponašanje",
"description": "Appears in Options as a tab header for options related to categories and skipping behavior. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabInterface": {
"message": "Sučelje",
"description": "Appears in Options as a tab header for options related to GUI and sounds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabKeyBinds": {
"message": "Tipkovni prečaci",
"description": "Appears in Options as a tab header for keybinds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabBackup": {
"message": "Sigurnosna kopija/Obnova",
"description": "Appears in Options as a tab header for options related to saving/restoring your settings. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabAdvanced": {
"message": "Razno",
"description": "Appears in Options as a tab header for advanced/niche options. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"noticeVisibilityLabel": {
"message": "Izgled napomene preskakanja",
"description": "Option label"
},
"notSet": {
"message": "Nije postavljeno"
},
"change": {
"message": "Promijeni"
},
"youtubeKeybindWarning": {
"message": "Ovo je ugrađeni YouTube prečac. Stvarno ga želiš koristiti?"
},
"betaServerWarning": {
"message": "BETA poslužitelj je aktiviran!"
},
"openOptionsPage": {
"message": "Otvori stranicu opcija"
},
"resetToDefault": {
"message": "Vrati standardne postavke"
},
"confirmResetToDefault": {
"message": "Stvarno želiš vratiti sve postavke na standardne vrijednosti? To je nepovratna radnja."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Jelezzen, ha egy szegmens át lett ugorva"
},
"showCategoryGuidelines": {
"message": "Kategória útmutató megjelenítése"
},
"noticeVisibilityMode0": {
"message": "Teljes méretű átugrási értesítők"
},
@@ -542,11 +545,23 @@
"message": "",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Tartalmazza a felkonferálást/átvezetőket is"
},
"generic_guideline2": {
"message": "Olyan a lejátszás, mintha semmi sem lett volna átugorva"
},
"category_sponsor": {
"message": "Szponzor"
},
"category_sponsor_description": {
"message": "Fizetett promóció, vagy közvetlen reklám. Nem önpromóció vagy ingyenes ajánlat (shoutout) emberekről/termékekről/weboldalakról amik tetszenek nekik."
"message": "Fizetett promóció, vagy közvetlen reklám. Nem önpromóció, vagy ingyenes említése ügyeknek/tartalomkészítőknek/weboldalaknak/termékeknek amik tetszenek nekik."
},
"category_sponsor_guideline1": {
"message": "Fizetett promóciók"
},
"category_sponsor_guideline2": {
"message": "Nem adományok vagy saját termékek"
},
"category_selfpromo": {
"message": "Nem fizetett/önpromóció"
@@ -554,6 +569,15 @@
"category_selfpromo_description": {
"message": "Hasonló a szponzorhoz, de nem fizetett, vagy önpromóció. Beletartozik a saját ruhaáru, adományok, vagy infó arról, hogy kivel működtek együtt."
},
"category_selfpromo_guideline1": {
"message": "Adományok, tagságok és saját termékek"
},
"category_selfpromo_guideline2": {
"message": "Ingyenes köszönetnyilvánítások/említések, amik nem adnak hozzá a videóhoz"
},
"category_selfpromo_guideline3": {
"message": "Nem cégek által tervezett termékekhez"
},
"category_exclusive_access": {
"message": "Exkluzív hozzáférés"
},
@@ -564,12 +588,24 @@
"message": "Ez a videó olyan terméket, szolgáltatást vagy helyszínt mutat be, amihez ingyenes vagy kedvezményes hozzáférést kaptak",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "A teljes videó olyan dolgot mutat be, amihez ingyenes vagy kedvezményes hozzáférést kaptak"
},
"category_interaction": {
"message": "Emlékeztető (Feliratkozás)"
},
"category_interaction_description": {
"message": "Egy rövid emlékeztető arról, hogy likeoljunk, iratkozzunk fel, vagy kövessük a tartalom közben. Ha hosszabb szakasz, vagy egy adott témáról van, inkább az önpromóció alá tartozik."
},
"category_interaction_guideline1": {
"message": "Rövid emlékeztető lájkolásra, feliratkozásra, vagy követésre"
},
"category_interaction_guideline2": {
"message": "Beletartoznak a közvetett emlékeztetők kommentelésre"
},
"category_interaction_guideline3": {
"message": "Nem általános promóciók, csak felhívások cselekvésre"
},
"category_interaction_short": {
"message": "Emlékeztető"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Megszakítás"
},
"category_intro_guideline1": {
"message": "Tényleges tartalom nélküli szakasz"
},
"category_intro_guideline2": {
"message": "Nem információt is tartalmazó átmenetekhez"
},
"category_outro": {
"message": "Záróképernyő/ Stáblista"
},
"category_outro_description": {
"message": "Stáblista, vagy amikor megjelennek a YouTube zárókártyák. Nem használandó információt tartalmazó következtetésekkor."
},
"category_outro_guideline1": {
"message": "Ne legyen benne tartalom, akkor sem, ha a zárókártyák már a képernyőn vannak"
},
"category_preview": {
"message": "Előzetes/Ismétlés"
},
"category_preview_description": {
"message": "Az előző részekben történtek rövid ismétlése, vagy a videó további tartalmának előzetese. Összevágott jelenetekhez, nem szóbeli összegzéshez."
},
"category_preview_guideline1": {
"message": "Klipek amik megjelennek később, vagy egy jövőbeli videóban"
},
"category_preview_guideline2": {
"message": "Korábbi videó összefoglalója"
},
"category_preview_guideline3": {
"message": "Nem olyan részekhez, amik új tartalmat adnak a videóhoz"
},
"category_filler": {
"message": "Témától eltérő töltelék/viccek"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Töltelék"
},
"category_filler_guideline1": {
"message": "Témától eltérő jelenetek tölteléknek, vagy humornak"
},
"category_filler_guideline2": {
"message": "Figyelemelterelődés, bakik, újrajátszások"
},
"category_filler_guideline3": {
"message": "Nem olyan jelenetek, amik a téma megértéséhez szükségesek"
},
"category_music_offtopic": {
"message": "Zene: nem-zene szegmens"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Nem-Zene"
},
"category_music_offtopic_guideline1": {
"message": "Olyan részek, amik nincsenek a hivatalos kiadásokban"
},
"category_music_offtopic_guideline2": {
"message": "Nem zene egy élő előadásban"
},
"category_poi_highlight": {
"message": "Kiemelés"
},
"category_poi_highlight_description": {
"message": "A videónak az a része, amelyiket a legtöbb ember látni szeretne. Hasonlít az \"A videó x percnél kezdődik\" jellegű hozzászólásokhoz."
},
"category_poi_highlight_guideline1": {
"message": "A rész, amit a legtöbb ember keres"
},
"category_poi_highlight_guideline2": {
"message": "Segíthet a kontextus átugrásában"
},
"category_poi_highlight_guideline3": {
"message": "A címhez, vagy indexképhez ugorhat"
},
"category_livestream_messages": {
"message": "Élő adás: Adomány / üzenet olvasások"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Tudj meg többet"
},
"FullDetails": {
"message": "Teljes részletek"
},
"CopyDownvoteButtonInfo": {
"message": "Leszavazza és készít egy helyi másolatot, amit beküldhetsz"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Beállítások megnyitása"
},
"resetToDefault": {
"message": "Beállítások visszaállítása alapértelmezettre"
},
"confirmResetToDefault": {
"message": "Biztosan vissza szeretnéd állítani az összes beállítást az alapértelmezett értékekre? A műveletet nem lehet visszavonni."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Tampilkan pemberitahuan setelah melewati segmen"
},
"showCategoryGuidelines": {
"message": "Tampilkan Bantuan Kategori"
},
"noticeVisibilityMode0": {
"message": "Lewati maklumat ukuran penuh"
},
@@ -542,18 +545,39 @@
"message": "sampai",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Tampilkan transisi segue"
},
"generic_guideline2": {
"message": "Memainkan sebagai tidak apa pun yang dilewati"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Promosi dibayar, tautan dibayar dan iklan langsung. Tidak untuk promosi diri sendiri atau dukungan gratis untuk gerakan/kreator/situs/produk yang mereka suka."
},
"category_sponsor_guideline1": {
"message": "Promosi berbayar"
},
"category_sponsor_guideline2": {
"message": "Bukan untuk donasi atau barang dagangan kustom"
},
"category_selfpromo": {
"message": "Promosi Diri Sendiri/Tidak Dibayar"
},
"category_selfpromo_description": {
"message": "Mirip dengan \"sponsor\" kecuali ini tidak dibayar atau promosi diri sendiri. Ini termasuk merchandise, donasi, atau informasi tentang siapa yang berkolaborasi dengan mereka."
},
"category_selfpromo_guideline1": {
"message": "Donasi, langganan, dan barang dagangan kustom"
},
"category_selfpromo_guideline2": {
"message": "Sebutan gratis yang tidak menambahkan apa pun ke videonya"
},
"category_selfpromo_guideline3": {
"message": "Bukan untuk produk dan barang dagangan yang didesain oleh koperasi"
},
"category_exclusive_access": {
"message": "Akses Eksklusif"
},
@@ -564,12 +588,24 @@
"message": "Video ini mempromosikan sebuah produk, layanan atau lokasi yang diterima secara gratis atau mendapatkan izin",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Seluruh video pertunjukkan sesuatu dengan akses gratis atau berbayar"
},
"category_interaction": {
"message": "Pengingat Interaksi (Berlangganan)"
},
"category_interaction_description": {
"message": "Saat ada pengingat singkat untuk meminta suka, berlangganan atau mengikuti mereka di tengah konten. Jika panjang atau tentang sesuatu yang spesifik, sebaiknya pakai kategori promosi diri sendiri."
},
"category_interaction_guideline1": {
"message": "Pengingat pendek untuk menyukai video, berlangganan, atau ikuti"
},
"category_interaction_guideline2": {
"message": "Menampilkan pengingat tidak langsung untuk berkomentar"
},
"category_interaction_guideline3": {
"message": "Bukan untuk promosi umum, hanya bilang untuk melakukan tindakan"
},
"category_interaction_short": {
"message": "Pengingat Interaksi"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Jeda"
},
"category_intro_guideline1": {
"message": "Interval tanpa konten aslinya"
},
"category_intro_guideline2": {
"message": "Bukan untuk transisi dengan informasi"
},
"category_outro": {
"message": "Kartu Akhir/Kredit"
},
"category_outro_description": {
"message": "Kredit atau saat kartu akhir YouTube muncul. Tidak untuk kesimpulan dengan informasi."
},
"category_outro_guideline1": {
"message": "Jangan tampilkan konten, bahkan jika kartu akhir ada di layar"
},
"category_preview": {
"message": "Pratinjau/Rekap"
},
"category_preview_description": {
"message": "Rekapan singkat dari episode sebelumnya, atau pratinjau tentang apa yang akan terjadi nanti di video. Dimaksudkan untuk klip bersama yang di edit, bukan ringkasan yang diucapkan."
},
"category_preview_guideline1": {
"message": "Klip yang ditampilkan nanti, atau di video di masa mendatang"
},
"category_preview_guideline2": {
"message": "Rekap dari video sebelumnya"
},
"category_preview_guideline3": {
"message": "Bukan untuk bagian yang menambahkan konten tambahan"
},
"category_filler": {
"message": "Pengisi Tak berkaitan/Lawakan"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Isian"
},
"category_filler_guideline1": {
"message": "Scene tangen hanya untuk isian atau humor"
},
"category_filler_guideline2": {
"message": "Gangguan, blooper, replay"
},
"category_filler_guideline3": {
"message": "Bukan untuk scene yang dibutuhkan untuk mengerti topik"
},
"category_music_offtopic": {
"message": "Musik: Bagian Non-Musik"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Non-Musik"
},
"category_music_offtopic_guideline1": {
"message": "Bagian bukan di rilis resmi"
},
"category_music_offtopic_guideline2": {
"message": "Non-musik di pertunjukkan langsung"
},
"category_poi_highlight": {
"message": "Sorotan"
},
"category_poi_highlight_description": {
"message": "Bagian video yang banyak orang lihat. Sama untuk komentar \"Video dimulai di x\"."
},
"category_poi_highlight_guideline1": {
"message": "Bagian banyak orang yang mencari"
},
"category_poi_highlight_guideline2": {
"message": "Dapat melewati topik"
},
"category_poi_highlight_guideline3": {
"message": "Dapat melewati ke judul atau thumbnail"
},
"category_livestream_messages": {
"message": "Livestream: Baca Pesan/Donasi"
},
@@ -789,7 +867,7 @@
"message": "Sembunyikan selamanya"
},
"warningChatInfo": {
"message": "Anda mendapatkan peringatan dan tidak bisa mengirim segmen sementara. Ini dikarenakan kami melihat kamu melakukan beberapa kesalahan yang umum, mohon konfirmasi bahwa kamu mengerti perundangan dan kami akan hapus peringatan. Kamu bisa bergabung ke obrolan menggunakan discord.gg/SponsorBlock atau matrix.io/#/#sponsor:ajay.app"
"message": "Anda mendapatkan peringatan dan tidak bisa mengirim segmen sementara. Ini dikarenakan kami melihat Anda melakukan beberapa kesalahan yang umum, mohon konfirmasi bahwa Anda mengerti perundangan dan kami akan hapus peringatan. Anda dapat bergabung ke obrolan menggunakan discord.gg/SponsorBlock atau matrix.io/#/#sponsor:ajay.app"
},
"voteRejectedWarning": {
"message": "Suara ditolak karena peringatan. Klik untuk buka obrolan untuk menyelesaikannya, atau kembali beberapa saat lagi ketika ada waktu.",
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Pelajari Lebih Lanjut"
},
"FullDetails": {
"message": "Detail Penuh"
},
"CopyDownvoteButtonInfo": {
"message": "Menurunkan suara dan membuat salinan lokal untuk Anda kirim ulang"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Buka laman opsi"
},
"resetToDefault": {
"message": "Atur ulang pengaturan ke bawaan"
},
"confirmResetToDefault": {
"message": "Apakah Anda yakin ingin mengatur ulang semua pengaturan ke nilai bawaan?\nTindakan ini tidak dapat diurungkan."
}
}

View File

@@ -164,6 +164,9 @@
"copyPublicID": {
"message": "Copia UserID Pubblico"
},
"copySegmentID": {
"message": "Copia ID Segmento"
},
"discordAdvert": {
"message": "Entra nel server Discord ufficiale per darci suggerimenti e feedback!"
},
@@ -183,7 +186,7 @@
"message": "Nasconde i pulsanti che appaiono sul video per inviare i segmenti da nascondere."
},
"showSkipButton": {
"message": "Mantieni Salta Per Evidenziare il Pulsante Sul Lettore"
"message": "Mantieni l'Highlight del Video sulla Barra del Video"
},
"showInfoButton": {
"message": "Mostra il pulsante delle informazioni sopra al video"
@@ -213,7 +216,7 @@
"message": "Memorizza i voti negativi del segmento"
},
"whatTrackDownvotes": {
"message": "Qualsiasi segmento voti negativamente rimarrà nascosto anche dopo aver ricaricato"
"message": "Segmenti votati negativamente rimarranno nascosti anche dopo aver ricaricato la pagina"
},
"trackDownvotesWarning": {
"message": "Attenzione: Disabilitarlo eliminerà tutti i voti negativi precedentemente memorizzati"
@@ -236,6 +239,9 @@
"showSkipNotice": {
"message": "Mostra Avviso Dopo Aver Saltato un Segmento"
},
"showCategoryGuidelines": {
"message": "Mostra Aiuto della Categoria"
},
"noticeVisibilityMode0": {
"message": "Salta Avvisi di Dimensioni Complete"
},
@@ -276,7 +282,7 @@
"description": "Keybind label"
},
"setStartSponsorShortcut": {
"message": "Inizio/Fine segmento",
"message": "Inizia/Finisci segmento",
"description": "Keybind label"
},
"setSubmitKeybind": {
@@ -284,7 +290,7 @@
"description": "Keybind label"
},
"keybindDescription": {
"message": "Selezionare una chiave digitandola e scegliere qualsiasi tasto modificatore che si desidera utilizzare."
"message": "Seleziona un tasto digitandolo e scegli qualsiasi tasto modificatore che desideri utilizzare."
},
"0": {
"message": "Timeout della connessione. Controlla la tua connessione a Internet. Se internet sta funzionando, il server è probabilmente sovraccarico oppure giù."
@@ -322,7 +328,7 @@
"message": "Silenziare {0}?"
},
"skip_to_category": {
"message": "Saltare a {0}?",
"message": "Salta a {0}?",
"description": "Used for skipping to things (Skip to Highlight)"
},
"skipped": {
@@ -539,18 +545,39 @@
"message": "a",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Includi transizioni"
},
"generic_guideline2": {
"message": "Riproduci come se nulla fosse stato saltato"
},
"category_sponsor": {
"message": "Sponsorizzazione"
},
"category_sponsor_description": {
"message": "Promozione a pagamento, referral a pagamento e pubblicità diretta. Non per auto-promozione o ringraziamenti gratuiti a cause/creatori/siti web/ prodotti di loro gradimento."
},
"category_sponsor_guideline1": {
"message": "Promozioni a pagamento"
},
"category_sponsor_guideline2": {
"message": "Non per donazioni o merchandise personalizzato"
},
"category_selfpromo": {
"message": "Promozione non pagata/Autopromozione"
},
"category_selfpromo_description": {
"message": "Simile alle \"sponsorizzazioni\" tranne che per promozioni non pagate o autopromozioni. Ciò include sezioni riguardanti vendita di merce, donazioni o informazioni in merito a collaboratori."
},
"category_selfpromo_guideline1": {
"message": "Donazioni, abbonamenti e merce personalizzata"
},
"category_selfpromo_guideline2": {
"message": "Shoutout non pagati che non aggiungono nulla al video"
},
"category_selfpromo_guideline3": {
"message": "Non per prodotti progettati da aziende e merce"
},
"category_exclusive_access": {
"message": "Accesso Esclusivo"
},
@@ -561,23 +588,41 @@
"message": "Questo video mostra un prodotto, un servizio o un posto che hanno ricevuto gratuitamente o a cui hanno avuto un accesso sovvenzionato",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "L'intero video mostra qualcosa con accesso gratuito o sovvenzionato"
},
"category_interaction": {
"message": "Promemoria d'Interazione (Iscrizione)"
},
"category_interaction_description": {
"message": "Quando nel punto centrale del contenuto è presente un breve promemoria per aggiunta di mi piace, iscrizione o seguito. Se dovesse risultare esteso o riguardare qualcosa di specifico, potrebbe essere un'autopromozione."
"message": "Quando nel punto centrale del contenuto è presente un breve promemoria per like, iscrizione o follow. Se dovesse risultare esteso o riguardante qualcosa di specifico, potrebbe essere auto-promozione."
},
"category_interaction_guideline1": {
"message": "Brevi promemoria per mi piace, iscrizioni o follow"
},
"category_interaction_guideline2": {
"message": "Include promemoria indiretti al commento"
},
"category_interaction_guideline3": {
"message": "Non per promozione generale, solo chiamata all'azione"
},
"category_interaction_short": {
"message": "Promemoria di Interazione"
"message": "Promemoria d'Interazione"
},
"category_intro": {
"message": "Animazione Interruzione/Introduzione"
"message": "Intermezzo/Intro Animata"
},
"category_intro_description": {
"message": "Un intervallo senza contenuto effettivo. Potrebbe essere una pausa, una schermata statica, un'animazione ripetuta. Non dovrebbe essere usato per transizioni contenenti informazioni."
},
"category_intro_short": {
"message": "Interruzione"
"message": "Intermezzo"
},
"category_intro_guideline1": {
"message": "Intervallo senza contenuto effettivo"
},
"category_intro_guideline2": {
"message": "Non per transizioni con informazioni"
},
"category_outro": {
"message": "Conclusioni/Titoli di Coda"
@@ -585,12 +630,24 @@
"category_outro_description": {
"message": "I titoli di coda o quando vengono mostrate annotazioni a fine video su YouTube. Non per conclusioni provviste di informazioni."
},
"category_outro_guideline1": {
"message": "Non include contenuti, anche se le schede finali sono a schermo"
},
"category_preview": {
"message": "Anteprima/Riepilogo"
},
"category_preview_description": {
"message": "Riepilogo rapido degli episodi precedenti, o un'anteprima di ciò che sta arrivando più tardi nel video attuale. Inteso per clip, non per riassunti a voce."
},
"category_preview_guideline1": {
"message": "Clip che appaiono più tardi in questo video, oppure in un video futuro"
},
"category_preview_guideline2": {
"message": "Riepilogo o riassunto di un video precedente"
},
"category_preview_guideline3": {
"message": "Non per sezioni che aggiungono contenuti in più"
},
"category_filler": {
"message": "Riempitivi irrilevanti/Battute"
},
@@ -598,7 +655,16 @@
"message": "Le scene riempitive sono aggiunte solo per riempire o per umorismo che non sono richieste per comprendere il contenuto principale del video. Questo non dovrebbe includere segmenti che forniscono contesto o dettagli di sfondo."
},
"category_filler_short": {
"message": "Riempimento"
"message": "Filler"
},
"category_filler_guideline1": {
"message": "Scene non correlate usate solo per filler o umorismo"
},
"category_filler_guideline2": {
"message": "Distrazioni, blooper, replay"
},
"category_filler_guideline3": {
"message": "Non per scene necessarie a capire l'argomento"
},
"category_music_offtopic": {
"message": "Musica: Sezione Non-Musicale"
@@ -609,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Non-Musicale"
},
"category_music_offtopic_guideline1": {
"message": "Sezioni non presenti nelle release ufficiali"
},
"category_music_offtopic_guideline2": {
"message": "Sezioni senza musica in una performance dal vivo"
},
"category_poi_highlight": {
"message": "Evidenzia"
"message": "Highlight"
},
"category_poi_highlight_description": {
"message": "La parte del video che gran parte delle persone stanno cercando. Simile ai commenti \"Il video inizia a x\"."
},
"category_poi_highlight_guideline1": {
"message": "La parte che la maggior parte delle persone sta cercando"
},
"category_poi_highlight_guideline2": {
"message": "Può ignorare il contesto"
},
"category_poi_highlight_guideline3": {
"message": "Può portare al titolo o alla miniatura del video"
},
"category_livestream_messages": {
"message": "Livestream: Donazione/Letture dei Messaggi"
},
@@ -735,7 +816,7 @@
"message": "Forza controllo canale prima di andare avanti"
},
"whatForceChannelCheck": {
"message": "Per impostazione predefinita, si salteranno subito i segmenti prima che si sappia anche che canale è. Per impostazione predefinita, alcuni segmenti all'inizio del video potrebbero essere saltati sui canali sulla whitelist. Abilitare questa opzione impedirà questo, ma fare saltare tutti hanno un leggero ritardo in quanto ottenere il channelID può richiedere un certo tempo. Questo ritardo potrebbe essere invisibile se si dispone di internet veloce."
"message": "Di default, verranno saltati i segmenti subito, anche prima che si sappia il canale. Di default, alcuni segmenti all'inizio del video potrebbero essere saltati sui canali nella whitelist. L'attivazione di questa opzione eviterà che ciò accada, ma ogni salto avrà un leggero ritardo in quanto ottenere l'ID del canale può richiedere un certo tempo. Questo ritardo potrebbe essere impercettibile se si dispone di una connessione internet veloce."
},
"forceChannelCheckPopup": {
"message": "Considera l'Attivazione dell'opzione \"Forza la Verifica del Canale Prima del Salto\""
@@ -864,6 +945,9 @@
"LearnMore": {
"message": "Scopri di Più"
},
"FullDetails": {
"message": "Visualizza Dettagli Completi"
},
"CopyDownvoteButtonInfo": {
"message": "Vota negativamente e crea una copia locale da reinviare"
},
@@ -879,6 +963,15 @@
"ChangeCategoryTooltip": {
"message": "Questo si applicherà istantaneamente ai tuoi segmenti"
},
"downvote": {
"message": "Voto negativo"
},
"upvote": {
"message": "Voto positivo"
},
"hideSegment": {
"message": "Nascondi segmento"
},
"SponsorTimeEditScrollNewFeature": {
"message": "Usa la rotellina del mouse passando sulla casella di modifica per regolare rapidamente il tempo. Le combinazioni dei tasti ctrl o shift sono utilizzabili per perfezionare le modifiche."
},
@@ -935,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Apri la pagina delle opzioni"
},
"resetToDefault": {
"message": "Ripristina le impostazioni predefinite"
},
"confirmResetToDefault": {
"message": "Sei sicuro di voler reimpostare tutte le impostazioni ai valori predefiniti? Questo non può essere annullato."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "구간을 건너뛴 후 알림 표시"
},
"showCategoryGuidelines": {
"message": "카테고리 도움말 표시"
},
"noticeVisibilityMode0": {
"message": "건너뛰기 알림 크게"
},
@@ -542,18 +545,39 @@
"message": "-",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "연속적인 전환 포함"
},
"generic_guideline2": {
"message": "건너뛰지 않은 것처럼 자연스럽게"
},
"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_selfpromo_guideline2": {
"message": "동영상과 무관한 무료 홍보"
},
"category_selfpromo_guideline3": {
"message": "기업 제품 및 상품은 해당되지 않음"
},
"category_exclusive_access": {
"message": "협찬"
},
@@ -564,12 +588,24 @@
"message": "본 동영상은 무료/유료 협찬을 받은 제품, 서비스, 장소를 소개합니다",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "전체 동영상이 유/무료 협찬을 받은 대상을 소개함"
},
"category_interaction": {
"message": "상호 작용 알림 (구독)"
},
"category_interaction_description": {
"message": "컨텐츠 중앙의 좋아요, 구독이나 팔로우에 대한 짧은 설명이 뜨는 경우입니다. 길거나 특정적인 거라면 자가 홍보에 해당됩니다."
},
"category_interaction_guideline1": {
"message": "좋아요, 구독, 팔로우를 요청하는 구간"
},
"category_interaction_guideline2": {
"message": "간접적인 댓글 작성 유도도 포함"
},
"category_interaction_guideline3": {
"message": "일반적인 홍보는 해당되지 않음, 행동을 요청하는 경우만"
},
"category_interaction_short": {
"message": "상호 작용 알림"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "휴식 시간"
},
"category_intro_guideline1": {
"message": "실제 콘텐츠가 없는 구간"
},
"category_intro_guideline2": {
"message": "정보가 포함된 전환은 해당되지 않음"
},
"category_outro": {
"message": "최종 화면 / 크레딧"
},
"category_outro_description": {
"message": "엔딩 크레딧이나 최종 화면이 나타나는 구간입니다. 단순히 결론을 말하는 부분은 여기에 포함되지 않습니다."
},
"category_outro_guideline1": {
"message": "최종 화면 카드가 표시되더라도, 콘텐츠가 포함되도록 하지 말 것"
},
"category_preview": {
"message": "미리보기/요약"
},
"category_preview_description": {
"message": "이전 에피소드를 간략히 요약하거나 현재 동영상에서 나중에 나올 내용을 예고해줍니다. 음성 요약이 아니라 편집된 동영상을 통한 요약입니다."
},
"category_preview_guideline1": {
"message": "다음 동영상이나 이후 구간에 나타나는 클립"
},
"category_preview_guideline2": {
"message": "이전 동영상 요약"
},
"category_preview_guideline3": {
"message": "추가적인 콘텐츠가 들어가는 구간은 해당되지 않음"
},
"category_filler": {
"message": "쓸데없는 잡담/농담"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "잡담"
},
"category_filler_guideline1": {
"message": "잡담이나 유머를 구사하고 주제에서 벗어난 장면"
},
"category_filler_guideline2": {
"message": "집중을 방해하는 구간, 실수, 리플레이"
},
"category_filler_guideline3": {
"message": "주제를 이해하는 데 필요한 장면은 해당되지 않음"
},
"category_music_offtopic": {
"message": "음악이 아닌 구간"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "음악이 아닌 구간"
},
"category_music_offtopic_guideline1": {
"message": "정식 음악에는 없는 구간"
},
"category_music_offtopic_guideline2": {
"message": "실시간 공연에서 음악이 아닌 부분"
},
"category_poi_highlight": {
"message": "하이라이트"
},
"category_poi_highlight_description": {
"message": "대부분의 사람들이 찾는 동영상의 파트를 말합니다. \"바쁘신 분들은...\" 댓글과 유사합니다."
},
"category_poi_highlight_guideline1": {
"message": "대부분의 사람들이 찾는 구간"
},
"category_poi_highlight_guideline2": {
"message": "맥락을 건너뛰어도 됨"
},
"category_poi_highlight_guideline3": {
"message": "제목, 썸네일 구간으로 건너뛰어도 됨"
},
"category_livestream_messages": {
"message": "라이브스트림: 후원/메시지 읽기"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "더보기"
},
"FullDetails": {
"message": "전체 자세한 정보"
},
"CopyDownvoteButtonInfo": {
"message": "비추천에 투표한 뒤 다시 제출할 수 있도록 미제출 사본을 생성합니다"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "설정 페이지 열기"
},
"resetToDefault": {
"message": "기본 설정으로 초기화"
},
"confirmResetToDefault": {
"message": "정말로 모든 설정을 기본값으로 초기화하시겠습니까? 되돌릴 수 없습니다."
}
}

View File

@@ -1 +1,25 @@
{}
{
"fullName": {
"message": "SponsorBlock priekš YouTube - Izlaid sponsorus",
"description": "Name of the extension."
},
"Description": {
"message": "Izlaidiet sponsorus, abonēšanas lūgumus un vairāk, skatoties YouTube video. Ziņojiet par sponsoriem video, kurus jūs skatāties, lai ietaupītu citu laiku.",
"description": "Description of the extension."
},
"400": {
"message": "Serveris ziņo, ka šis pieprasījums ir nederīgs"
},
"429": {
"message": "Jūs esat aizsūtījis pārāk daudz sponsoru laika sprīžus šim video; vai esat pārliecināts, ka šeit ir tik daudz?"
},
"409": {
"message": "Šis jau ir ticis aizsūtīts iepriekš"
},
"channelWhitelisted": {
"message": "Kanāls iekļauts baltajā sarakstā!"
},
"Segment": {
"message": "segments"
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Melding weergeven nadat een segment is overgeslagen"
},
"showCategoryGuidelines": {
"message": "Categorie-hulp weergeven"
},
"noticeVisibilityMode0": {
"message": "Volledige grootte overslaan-meldingen"
},
@@ -542,18 +545,39 @@
"message": "tot",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Inclusief vlotte overgangen"
},
"generic_guideline2": {
"message": "Speelt af alsof niets overgeslagen werd"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Betaalde promotie, betaalde aanbevelingen en directe reclame. Niet voor zelfpromotie of gratis uitroepen naar zaken/makers/websites/producten waar ze van houden."
},
"category_sponsor_guideline1": {
"message": "Betaalde promoties"
},
"category_sponsor_guideline2": {
"message": "Niet voor donaties of aangepaste koopwaar"
},
"category_selfpromo": {
"message": "Onbetaalde promotie of zelfpromotie"
},
"category_selfpromo_description": {
"message": "Vergelijkbaar met \"sponsor\", behalve voor onbetaalde of zelfpromotie. Dit is inclusief secties over koopwaar, donaties of informatie over met wie ze hebben samengewerkt."
},
"category_selfpromo_guideline1": {
"message": "Donaties, lidmaatschappen en aangepaste koopwaar"
},
"category_selfpromo_guideline2": {
"message": "Gratis uitroepen die niet aan de video toevoegen"
},
"category_selfpromo_guideline3": {
"message": "Niet voor bedrijfsontworpen producten en koopwaar"
},
"category_exclusive_access": {
"message": "Exclusieve toegang"
},
@@ -564,12 +588,24 @@
"message": "Deze video toont een product, dienst of locatie waartoe men gratis of gesubsidieerd toegang heeft gekregen",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Volledige video laat iets zien met gratis of gesubsidieerde toegang"
},
"category_interaction": {
"message": "Interactieherinnering (abonneren)"
},
"category_interaction_description": {
"message": "Als er een korte herinnering is om ze leuk te vinden, u te abonneren of ze te volgen in het midden van de inhoud. Als het lang is of over iets specifieks gaat, moet het in plaats daarvan onder zelfpromotie vallen."
},
"category_interaction_guideline1": {
"message": "Korte herinneringen om leuk te vinden, te abonneren of te volgen"
},
"category_interaction_guideline2": {
"message": "Bevat indirecte herinneringen voor commentaar"
},
"category_interaction_guideline3": {
"message": "Niet voor algemene promotie, roept alleen op tot actie"
},
"category_interaction_short": {
"message": "Interactieherinnering"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Onderbreking"
},
"category_intro_guideline1": {
"message": "Interval zonder werkelijke inhoud"
},
"category_intro_guideline2": {
"message": "Niet voor overgangen met informatie"
},
"category_outro": {
"message": "Eindkaarten/aftiteling"
},
"category_outro_description": {
"message": "Aftiteling of wanneer de YouTube-eindkaarten verschijnen. Niet voor conclusies met informatie."
},
"category_outro_guideline1": {
"message": "Inhoud niet toevoegen, zelfs niet als eindkaarten op het scherm staan"
},
"category_preview": {
"message": "Voorbeeld/samenvatting"
},
"category_preview_description": {
"message": "Snelle samenvatting van vorige afleveringen of een voorbeeld van wat er later komt in de huidige video. Bedoeld voor samengevoegde clips, niet voor gesproken samenvattingen."
},
"category_preview_guideline1": {
"message": "Clips die later of in een toekomstige video verschijnen"
},
"category_preview_guideline2": {
"message": "Samenvatting van een vorige video"
},
"category_preview_guideline3": {
"message": "Niet voor secties die extra inhoud toevoegen"
},
"category_filler": {
"message": "Opvulling zijspoor/humor"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Opvulling"
},
"category_filler_guideline1": {
"message": "Zijspoor-scènes alleen voor opvulling of humor"
},
"category_filler_guideline2": {
"message": "Afleidingen, bloopers, herhalingen"
},
"category_filler_guideline3": {
"message": "Niet voor scènes vereist om het onderwerp te begrijpen"
},
"category_music_offtopic": {
"message": "Muziek: sectie niet-muziek"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Niet-muziek"
},
"category_music_offtopic_guideline1": {
"message": "Secties niet in officiële releases"
},
"category_music_offtopic_guideline2": {
"message": "Niet-muziek in een live optreden"
},
"category_poi_highlight": {
"message": "Hoogtepunt"
},
"category_poi_highlight_description": {
"message": "Het deel van de video waar de meeste mensen naar op zoek zijn. Gelijkaardig aan \"video begint bij x\"-opmerkingen."
},
"category_poi_highlight_guideline1": {
"message": "Sectie die de meeste mensen zoeken"
},
"category_poi_highlight_guideline2": {
"message": "Kan context overslaan"
},
"category_poi_highlight_guideline3": {
"message": "Kan naar titel of pictogram overslaan"
},
"category_livestream_messages": {
"message": "Livestream: donaties/lezen van berichten"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Meer informatie"
},
"FullDetails": {
"message": "Volledige details"
},
"CopyDownvoteButtonInfo": {
"message": "Doet een tegenstem en maakt een lokale kopie aan die u opnieuw kunt indienen"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Pagina met opties openen"
},
"resetToDefault": {
"message": "Instellingen terugzetten op standaard"
},
"confirmResetToDefault": {
"message": "Weet u zeker dat u alle instellingen wilt terugzetten naar hun standaardwaarden? Dit kan niet ongedaan worden gemaakt."
}
}

View File

@@ -11,7 +11,7 @@
"message": "Serwer odpowiedział, że to zapytanie jest niepoprawne"
},
"429": {
"message": "Zgłosiłeś za dużo segmentów sponsora dla tego jednego filmu. Jesteś pewien, że jest ich tak dużo?"
"message": "Zgłoszono za dużo segmentów sponsora dla tego jednego filmu. Czy na pewno jest ich tak dużo?"
},
"409": {
"message": "To już zostało wysłane wcześniej"
@@ -53,7 +53,7 @@
"message": "Pomiń"
},
"unmute": {
"message": "Odcisz"
"message": "Anuluj wyciszenie"
},
"paused": {
"message": "Zatrzymany"
@@ -239,20 +239,23 @@
"showSkipNotice": {
"message": "Pokaż informację po pominięciu segmentu"
},
"showCategoryGuidelines": {
"message": "Pokaż pomoc kategorii"
},
"noticeVisibilityMode0": {
"message": "Pełnowymiarowe powiadomienia o przewinięciu"
},
"noticeVisibilityMode1": {
"message": "Małe powiadomienia o automatycznym przewijaniu"
"message": "Małe powiadomienia o automatycznym przewinięciu"
},
"noticeVisibilityMode2": {
"message": "Małe powiadomienia o przewinięciu"
},
"noticeVisibilityMode3": {
"message": "Znikające powiadomienia o automatycznym przewijaniu"
"message": "Półprzezroczyste powiadomienie o automatycznym przewinięciu"
},
"noticeVisibilityMode4": {
"message": "Znikające powiadomienia o przewijaniu"
"message": "Półprzezroczyste powiadomienie dla wszystkich przewinięć"
},
"longDescription": {
"message": "SponsorBlock pozwala pomijać sponsorów, intra, outra, przypomnienia o subskrypcjach i inne irytujące fragmenty filmów na YouTube. SponsorBlock jest opartym na crowdsourcingu rozszerzeniem do przeglądarki, które pozwala każdemu zgłosić początek i koniec segmentów sponsorowanych oraz innych segmentów w filmach na YouTube. Kiedy ktoś już zamieści te informacje, wszyscy pozostali z tym rozszerzeniem będą pomijać segment sponsorowany. Możesz również pomijać fragmenty teledysków bez muzyki.",
@@ -322,7 +325,7 @@
"message": "{0} — pominąć?"
},
"mute_category": {
"message": "Wyciszyć {0}?"
"message": "{0} — wyciszyć?"
},
"skip_to_category": {
"message": "Przejść do {0}?",
@@ -449,7 +452,7 @@
"message": "Ten segment jest krótszy od ustawionego przez Ciebie minimalnego czasu trwania. Może to oznaczać, że ktoś już to zamieścił, ale nie widzisz tego przez to ustawienie. Czy na pewno chcesz to zamieścić?"
},
"liveOrPremiere": {
"message": "Przesyłanie na aktywnej transmisji na żywo lub premierze jest niedozwolone. Poczekaj aż ona się zakończy, a następnie odśwież stronę i sprawdź, czy te segmenty są nadal prawidłowe."
"message": "Przesyłanie segmentów podczas transmisji na żywo lub trwającej premiery jest niedozwolone. Poczekaj, aż się zakończy, a następnie odśwież stronę i sprawdź, czy te segmenty są nadal prawidłowe."
},
"showUploadButton": {
"message": "Pokaż przycisk wysyłania"
@@ -464,7 +467,7 @@
"message": "Zapisz"
},
"reset": {
"message": "Reset"
"message": "Resetuj"
},
"customAddressError": {
"message": "Ten adres nie jest w prawidłowej formie. Upewnij się, że http:// lub https:// znajduje się na początku i nie ma końcowych ukośników."
@@ -536,40 +539,73 @@
"message": "Informacje do debugowania zostały skopiowane do schowka. Możesz usunąć dane, których nie chcesz udostępniać. Zapisz je w pliku tekstowym albo wklej do raportu podczas zgłaszania błędu."
},
"keyAlreadyUsed": {
"message": "Ten skrót jest przypisany do innej akcji. Proszę wybrać inny."
"message": "Ten skrót jest przypisany do innej czynności. Wybierz inny."
},
"to": {
"message": "do",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Zawiera płynne przejścia"
},
"generic_guideline2": {
"message": "Pominięcie bez zauważalnego przeskoku"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Płatna promocja, płatne rekomendacje oraz bezpośrednie reklamy. Nie do autopromocji ani darmowych wyrazów uznania dla kwestii/twórców/stron/produktów, które im się podobają."
},
"category_sponsor_guideline1": {
"message": "Segmenty sponsorowane"
},
"category_sponsor_guideline2": {
"message": "Nie dla donateów lub merchu"
},
"category_selfpromo": {
"message": "Nieopłacona/Własna promocja"
},
"category_selfpromo_description": {
"message": "Podobnie jak \"sponsor\", ale nieodpłatnie bądź w ramach promocji własnej. Obejmuje to sekcje o własnych produktach, donacjach czy informacje o tym, z kim współpracowali."
},
"category_selfpromo_guideline1": {
"message": "Dotacje, płatne członkostwo i merch"
},
"category_selfpromo_guideline2": {
"message": "Szybkie przypomnienia, które nie wnoszą nic do filmu"
},
"category_selfpromo_guideline3": {
"message": "Nie dla produktów zaprojektowanych przez duże firmy"
},
"category_exclusive_access": {
"message": "Ekskluzywny dostęp"
"message": "Dostęp na wyłączność"
},
"category_exclusive_access_description": {
"message": "Tylko do oznaczania całych filmów. Używane, gdy wideo wyświetla produkt, usługę lub lokalizację, do których otrzymali darmowy lub subsydiowany dostęp."
"message": "Tylko do oznaczania całych filmów. Używane, gdy materiał wideo przedstawia produkt, usługę lub miejsce, do którego dostęp został otrzymany bezpłatnie lub dzięki dofinansowaniu."
},
"category_exclusive_access_pill": {
"message": "Ten film pokazuje produkt, usługę lub lokalizację, do których otrzymali darmowy lub subsydiowany dostęp",
"message": "Ten materiał wideo przedstawia produkt, usługę lub miejsce, do którego dostęp został otrzymany bezpłatnie lub dzięki dofinansowaniu",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Cały film jest poświęcony czemuś z darmowym lub płatnym dostępem"
},
"category_interaction": {
"message": "Przypomnienie o interakcji (Subskrybuj)"
},
"category_interaction_description": {
"message": "Gdy ma miejsce krótkie przypomnienie, by lajkować, subskrybować lub śledzić ich w trakcie kontentu. Jeśli trwa to długo lub dotyczy czegoś konkretnego, powinno być zamiast tego jako promocja własna."
},
"category_interaction_guideline1": {
"message": "Krótkie przypomnienia, by polubić lub zasubskrybować"
},
"category_interaction_guideline2": {
"message": "Zawiera niebezpośrednie zachęcanie do komentowania"
},
"category_interaction_guideline3": {
"message": "Nie dla ogólnej promocji, tylko zaproszenia do działania"
},
"category_interaction_short": {
"message": "Przypomnienie o interakcji"
},
@@ -582,20 +618,38 @@
"category_intro_short": {
"message": "Przerwa"
},
"category_intro_guideline1": {
"message": "Przerwy bez rzeczywistej zawartości"
},
"category_intro_guideline2": {
"message": "Nie dla przejść zawierających informacje"
},
"category_outro": {
"message": "Ekran końcowy/Napisy"
},
"category_outro_description": {
"message": "Napisy końcowe lub gdy pojawia się ekran końcowy. Nie do konkluzji zawierających informacje."
},
"category_outro_guideline1": {
"message": "Nie zawiera treści, nawet jeśli na ekranie są karty końcowe"
},
"category_preview": {
"message": "Zapowiedź/Podsumowanie"
},
"category_preview_description": {
"message": "Szybkie podsumowanie poprzednich odcinków lub podgląd tego, co pojawia się później w bieżącym filmie. Dotyczy zmontowanych klipów, a nie ustnych podsumowań."
},
"category_preview_guideline1": {
"message": "Klipy, które pojawiają się później lub w następnym filmie"
},
"category_preview_guideline2": {
"message": "Podsumowanie poprzedniego filmu"
},
"category_preview_guideline3": {
"message": "Nie dla sekcji, które zawierają potrzebne informacje"
},
"category_filler": {
"message": "Wypełniacz Nietematyczny/Żart"
"message": "Wypełniacz nietematyczny/żart"
},
"category_filler_description": {
"message": "Sceny nietematyczne dodawane tylko jako wypełniacz lub dla humoru, które nie są wymagane do zrozumienia głównej treści filmu. Nie powinno to obejmować segmentów zawierających informacje kontekstowe lub szczegółowe."
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Wypełniacz"
},
"category_filler_guideline1": {
"message": "Przerywniki lub sceny czysto humorystyczne"
},
"category_filler_guideline2": {
"message": "Rozpraszacze, wpadki, powtórki"
},
"category_filler_guideline3": {
"message": "Nie nadaje się do scen wymaganych do zrozumienia tematu"
},
"category_music_offtopic": {
"message": "Muzyka: Sekcja niemuzyczna"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Bez muzyki"
},
"category_music_offtopic_guideline1": {
"message": "Sekcje, których nie ma w oficjalnych wydaniach"
},
"category_music_offtopic_guideline2": {
"message": "Niemuzyczna część wystąpienia na żywo"
},
"category_poi_highlight": {
"message": "Wyróżnione"
},
"category_poi_highlight_description": {
"message": "Część filmu, która interesuje większość osób. Podobne do komentarzy typu „Filmik zaczyna się od x”."
},
"category_poi_highlight_guideline1": {
"message": "Część filmu, której szuka większość osób"
},
"category_poi_highlight_guideline2": {
"message": "Może pomóc pominąć kontekst"
},
"category_poi_highlight_guideline3": {
"message": "Może pominąć do karty tytułowej lub miniaturki"
},
"category_livestream_messages": {
"message": "Transmisja live: Dotacja/Czytanie wiadomości"
},
@@ -659,7 +737,7 @@
"description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo"
},
"previewColor": {
"message": "Nieprzesłany kolor",
"message": "Kolor nieprzesłanego segmentu",
"description": "Referring to submissions that have not been sent to the server yet."
},
"seekBarColor": {
@@ -695,7 +773,7 @@
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"poiOnlyOneSegment": {
"message": "Ostrzeżenie: Ten typ segmentu, może być maksymalnie jeden. Przesyłanie kilku na raz spowoduje, że pojawi się losowy."
"message": "Ostrzeżenie: Ten typ segmentu może być maksymalnie jeden. Przesyłanie kilku na raz spowoduje, że pojawi się losowy."
},
"youMustSelectACategory": {
"message": "Musisz wybrać kategorię dla każdego segmentu, który zamieszczasz!"
@@ -709,6 +787,9 @@
"hiddenDueToDuration": {
"message": "ukryty: zbyt krótki"
},
"manuallyHidden": {
"message": "ręcznie ukryty"
},
"channelDataNotFound": {
"description": "This error appears in an alert when they try to whitelist a channel and the extension is unable to determine what channel they are looking at.",
"message": "ID kanału nie zostało jeszcze załadowane. Jeśli używasz embeddowanego filmu, spróbuj użyć strony głównej YouTube'a. Może to być również spowodowane zmianami w layout'cie YouTube'a, jeśli myślisz, że to przez to, dodaj swój komentarz tutaj:"
@@ -793,13 +874,13 @@
"description": "This is an integrated chat panel that will appearing allowing them to talk to the Discord/Matrix chat without leaving their browser."
},
"Donate": {
"message": "Dotacje"
"message": "Wesprzyj nas"
},
"considerDonating": {
"message": "Wesprzyj"
"message": "Wesprzyj rozwój wtyczki"
},
"hideDonationLink": {
"message": "Ukryj Link Do Dotacji"
"message": "Ukryj link do darowizny"
},
"darkModeOptionsPage": {
"message": "Tryb ciemny na stronie opcji"
@@ -823,10 +904,10 @@
"message": "Za każdym razem, gdy pominiesz segment, otrzymasz powiadomienie. Jeśli moment nie wydaje się być poprawny, kliknij łapkę w dół! Możesz również głosować w okienku pop-up."
},
"Submitting": {
"message": "Wysyłanie"
"message": "Przesyłanie"
},
"helpPageSubmitting1": {
"message": "Wysyłanie może być wykonane w wyskakującym okienku, poprzez kliknięcie przycisku \"Początek segmentu\" lub za pomocą przycisków na odtwarzaczu wideo."
"message": "Zgłoszenia można dokonać w wyskakującym okienku, naciskając przyciskPoczątek segmentu”, lub za pomocą przycisków w odtwarzaczu wideo."
},
"helpPageSubmitting2": {
"message": "Kliknięcie przycisku odtwarzania wskazuje początek segmentu a kliknięcie ikony stop wskazuje koniec segmentu. Możesz przygotować wielu segmentów przed wysłaniem. Aby wysłać kliknij przycisk potwierdzający, a aby usunąć - na śmietnik."
@@ -864,6 +945,9 @@
"LearnMore": {
"message": "Dowiedz się więcej"
},
"FullDetails": {
"message": "Pełne szczegóły"
},
"CopyDownvoteButtonInfo": {
"message": "Daje łapkę w dół i tworzy lokalną kopię, abyś mógł przesłać poprawioną wersję"
},
@@ -879,6 +963,12 @@
"ChangeCategoryTooltip": {
"message": "To natychmiastowo zostanie zastosowane do twoich segmentów"
},
"downvote": {
"message": "Głos przeciw"
},
"upvote": {
"message": "Głos za"
},
"hideSegment": {
"message": "Ukryj segment"
},
@@ -917,7 +1007,7 @@
"description": "Appears in Options as a tab header for advanced/niche options. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"noticeVisibilityLabel": {
"message": "Pomiń wygląd wpisu",
"message": "Wygląd okna pomijania",
"description": "Option label"
},
"unbind": {
@@ -935,5 +1025,14 @@
},
"betaServerWarning": {
"message": "Serwer BETA jest włączony!"
},
"openOptionsPage": {
"message": "Otwórz stronę ustawień"
},
"resetToDefault": {
"message": "Resetuj do ustawień domyślnych"
},
"confirmResetToDefault": {
"message": "Czy na pewno chcesz przywrócić wszystkie ustawienia do ich domyślnych wartości? Tego nie można cofnąć."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Mostrar Aviso Após Um Segmento Ser Ignorado"
},
"showCategoryGuidelines": {
"message": "Mostrar Categoria de Ajuda"
},
"noticeVisibilityMode0": {
"message": "Avisos de Ignorar em Tamanho Inteiro"
},
@@ -947,5 +950,11 @@
},
"openOptionsPage": {
"message": "Abrir página de opções"
},
"resetToDefault": {
"message": "Redefinir configurações para o padrão"
},
"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."
}
}

View File

@@ -17,7 +17,7 @@
"message": "Isso já foi enviado antes"
},
"channelWhitelisted": {
"message": "Canal adicionado à whitelist!"
"message": "Canal adicionado à lista de autorizados!"
},
"Segment": {
"message": "segmento"
@@ -159,10 +159,13 @@
"message": "Nome de Utilizador"
},
"setUsername": {
"message": "Criar nomde de utilizador"
"message": "Criar nome de utilizador"
},
"copyPublicID": {
"message": "Copiar UserID Publico"
"message": "Copiar ID blico de utilizador"
},
"copySegmentID": {
"message": "Copiar ID do segmento"
},
"discordAdvert": {
"message": "Junte-se ao discord oficial para sugerir dicas e sugestões!"
@@ -224,15 +227,280 @@
"whatQueryByHashPrefix": {
"message": "Em vez de solicitar segmentos do servidor usando o ID do Vídeo, são enviados os primeiros 4 caracteres do hash do ID do Vídeo. Este servidor enviará de volta dados para todos os vídeos com hashes semelhantes."
},
"enableRefetchWhenNotFound": {
"message": "Recuperar segmentos em novos vídeos"
},
"whatRefetchWhenNotFound": {
"message": "Se o vídeo for novo e nenhum segmento for encontrado, continuaremos a monitorizar enquanto assiste."
},
"showNotice": {
"message": "Mostrar notificação outra vez"
},
"showSkipNotice": {
"message": "Mostrar aviso após um segmento ser ignorado"
},
"noticeVisibilityMode0": {
"message": "Avisos de ignorar em tamanho inteiro"
},
"noticeVisibilityMode1": {
"message": "Avisos pequenos quando ignorado automaticamente"
},
"noticeVisibilityMode2": {
"message": "Todos os avisos de ignorar em tamanho pequeno"
},
"website": {
"message": "Site",
"description": "Used on Firefox Store Page"
},
"sourceCode": {
"message": "Código fonte",
"message": "Código-fonte",
"description": "Used on Firefox Store Page"
},
"errorCode": {
"message": "Código de erro: "
},
"skip": {
"message": "Saltar"
},
"mute": {
"message": "Silenciar"
},
"full": {
"message": "Vídeo completo",
"description": "Used for the name of the option to label an entire video as sponsor or self promotion."
},
"skip_category": {
"message": "Saltar {0}?"
},
"mute_category": {
"message": "Silenciar {0}?"
},
"skip_to_category": {
"message": "Avançar para {0}?",
"description": "Used for skipping to things (Skip to Highlight)"
},
"skipped": {
"message": "{0} ignorado",
"description": "Example: Sponsor Skipped"
},
"muted": {
"message": "{0} silenciado",
"description": "Example: Sponsor Muted"
},
"minLower": {
"message": "minuto"
},
"minsLower": {
"message": "minutos"
},
"hourLower": {
"message": "hora"
},
"hoursLower": {
"message": "horas"
},
"youHaveSavedTime": {
"message": "Poupou pessoas de",
"description": "You've saved people from 887,362 segments (236d 15h 5.3 minutes of their lives)."
},
"youHaveSavedTimeEnd": {
"message": " das suas vidas",
"description": "You've saved people from 887,362 segments (236d 15h 5.3 minutes of their lives)."
},
"setUserID": {
"message": "Definir ID de utilizador"
},
"userIDChangeWarning": {
"message": "Atenção: A alteração do ID de utilizador é permanente. Tem certeza que a deseja? Certifique-se de fazer uma cópia de segurança do seu ID antigo por precaução."
},
"areYouSureReset": {
"message": "Tem certeza que deseja redefinir?"
},
"mobileUpdateInfo": {
"message": "m.youtube.com é agora suportado"
},
"exportOptions": {
"message": "Importar/Exportar todas as opções"
},
"exportOptionsCopy": {
"message": "Editar/copiar"
},
"exportOptionsDownload": {
"message": "Guardar num ficheiro"
},
"exportOptionsUpload": {
"message": "Carregar a partir de ficheiro"
},
"setOptions": {
"message": "Definir opções"
},
"submit": {
"message": "Enviar"
},
"cancel": {
"message": "Cancelar"
},
"delete": {
"message": "Eliminar"
},
"preview": {
"message": "Pré-visualizar"
},
"inspect": {
"message": "Inspecionar"
},
"edit": {
"message": "Editar"
},
"copyDebugInformation": {
"message": "Copiar informações de depuração para a área de transferência"
},
"copyDebugInformationFailed": {
"message": "Falha ao copiar para a área de transferência"
},
"to": {
"message": "até",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline2": {
"message": "Reproduz como se nada tivesse sido ignorado"
},
"category_sponsor": {
"message": "Patrocinador"
},
"category_exclusive_access": {
"message": "Acesso exclusivo"
},
"category_interaction_short": {
"message": "Lembrete de interação"
},
"category_livestream_messages_short": {
"message": "Leitura de mensagens"
},
"autoSkip": {
"message": "Saltar automaticamente"
},
"manualSkip": {
"message": "Saltar manualmente"
},
"disable": {
"message": "Desativar"
},
"category": {
"message": "Categoria"
},
"bracketNow": {
"message": "(agora)"
},
"moreCategories": {
"message": "Mais categorias"
},
"chooseACategory": {
"message": "Escolher uma categoria"
},
"bracketEnd": {
"message": "(fim)"
},
"manuallyHidden": {
"message": "ocultado manualmente"
},
"incorrectCategory": {
"message": "Alterar categoria"
},
"guidelines": {
"message": "Orientações"
},
"readTheGuidelines": {
"message": "Leia as nossas orientações!!",
"description": "Show the first time they submit or if they are \"high risk\""
},
"darkModeOptionsPage": {
"message": "Modo escuro na página de opções"
},
"helpPageThanksForInstalling": {
"message": "Obrigado por instalar o SponsorBlock."
},
"helpPageReviewOptions": {
"message": "Por favor, reveja as opções abaixo"
},
"helpPageHowSkippingWorks": {
"message": "Como a função saltar funciona"
},
"Submitting": {
"message": "A enviar"
},
"Editing": {
"message": "A editar"
},
"helpPageTooSlow": {
"message": "Está demasiado lento"
},
"Credits": {
"message": "Créditos"
},
"LearnMore": {
"message": "Saber mais"
},
"FullDetails": {
"message": "Todos os detalhes"
},
"CopyAndDownvote": {
"message": "Copiar e dar voto negativo"
},
"ContinueVoting": {
"message": "Continuar a votar"
},
"downvote": {
"message": "Voto negativo"
},
"upvote": {
"message": "Voto positivo"
},
"hideSegment": {
"message": "Ocultar segmento"
},
"dayAbbreviation": {
"message": "d",
"description": "100d"
},
"hourAbbreviation": {
"message": "h",
"description": "100h"
},
"optionsTabBehavior": {
"message": "Comportamento",
"description": "Appears in Options as a tab header for options related to categories and skipping behavior. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabInterface": {
"message": "Interface",
"description": "Appears in Options as a tab header for options related to GUI and sounds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabKeyBinds": {
"message": "Atalhos de teclado",
"description": "Appears in Options as a tab header for keybinds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabBackup": {
"message": "Cópia de seg./restauro",
"description": "Appears in Options as a tab header for options related to saving/restoring your settings. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabAdvanced": {
"message": "Diversos",
"description": "Appears in Options as a tab header for advanced/niche options. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"unbind": {
"message": "Desvincular",
"description": "Unbind keyboard shortcut"
},
"notSet": {
"message": "Não definido"
},
"change": {
"message": "Alterar"
},
"betaServerWarning": {
"message": "O servidor BETA está ativado!"
},
"openOptionsPage": {
"message": "Abrir página de opções"
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Показывать уведомление после пропуска сегмента"
},
"showCategoryGuidelines": {
"message": "Показать справку для категории"
},
"noticeVisibilityMode0": {
"message": "Полноразмерные уведомления о пропусках"
},
@@ -542,18 +545,39 @@
"message": "до",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Содержит плавный переход от одной темы к другой"
},
"generic_guideline2": {
"message": "Пропуск должен произойти незаметно"
},
"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_selfpromo_guideline2": {
"message": "Рекомендации, не добавляющие контекста видео"
},
"category_selfpromo_guideline3": {
"message": "Не для корпоративной продукции"
},
"category_exclusive_access": {
"message": "Эксклюзивный доступ"
},
@@ -564,12 +588,24 @@
"message": "Это видео демонстрирует продукт, сервис или местоположение, к которому автор получил бесплатный или проспонсированный доступ",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Всё видео - демонстрация чего-либо, к чему авторам был дан эксклюзивный доступ"
},
"category_interaction": {
"message": "Напоминание о взаимодействии (подписка)"
},
"category_interaction_description": {
"message": "Когда есть краткое напоминание поставить лайк, подписаться на канал или в соцсетях в середине содержимого. Если эта вставка длительная или о чём-то конкретном, она должна классифицироваться как самореклама."
},
"category_interaction_guideline1": {
"message": "Краткие напоминания поставить лайк, подписаться или нажать на колокольчик"
},
"category_interaction_guideline2": {
"message": "Включает косвенные предложения оставить комментарий"
},
"category_interaction_guideline3": {
"message": "Не для рекламы, только призывы к действию"
},
"category_interaction_short": {
"message": "Напоминание о взаимодействии"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Заставка"
},
"category_intro_guideline1": {
"message": "Сегмент без контента"
},
"category_intro_guideline2": {
"message": "Не для переходных сегментов с информацией"
},
"category_outro": {
"message": "Конечная заставка/титры"
},
"category_outro_description": {
"message": "Титры или время появления конечных заставок YouTube. Не для подведения итогов сказанного в видео."
},
"category_outro_guideline1": {
"message": "Не для сегментов с контентом, даже если на видео показываются конечные заставки"
},
"category_preview": {
"message": "Предпросмотр/краткое содержание"
},
"category_preview_description": {
"message": "Краткое содержание предыдущих эпизодов или предварительный просмотр того, что будет в данном видео. Предназначено для сегментов, смонтированных из кусков видео, а не для устных пересказов."
},
"category_preview_guideline1": {
"message": "Фрагменты, которые появляются позже или в будущем видео"
},
"category_preview_guideline2": {
"message": "Пересказ предыдущего видео"
},
"category_preview_guideline3": {
"message": "Не для сегментов с дополнительным контентом"
},
"category_filler": {
"message": "Заполнение отвлечёнными темами/шутками"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Заполнитель"
},
"category_filler_guideline1": {
"message": "Сцены с отвлечёнными темами или шутками"
},
"category_filler_guideline2": {
"message": "Разговоры не по теме, неудачные дубли, повторы"
},
"category_filler_guideline3": {
"message": "Не для сегментов, необходимых для понимания темы"
},
"category_music_offtopic": {
"message": "Музыка: Сегмент без музыки"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Без музыки"
},
"category_music_offtopic_guideline1": {
"message": "Сегменты, не включённые в официальные релизы"
},
"category_music_offtopic_guideline2": {
"message": "Сегменты без музыки во время живого выступления"
},
"category_poi_highlight": {
"message": "Важное"
},
"category_poi_highlight_description": {
"message": "Часть видео, которую ищет большинство людей. По сути заменяет комментарии типа \"Видео начинается с x:xx\"."
},
"category_poi_highlight_guideline1": {
"message": "Момент, который будет интересен большинству людей"
},
"category_poi_highlight_guideline2": {
"message": "Может помочь пропустить контекст"
},
"category_poi_highlight_guideline3": {
"message": "Может пропустить до объекта заголовка или превью"
},
"category_livestream_messages": {
"message": "Прямые трансляции: пожертвование/чтение сообщения"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Узнать больше"
},
"FullDetails": {
"message": "Полная информация"
},
"CopyDownvoteButtonInfo": {
"message": "Голосует против и создаёт локальную копию сегмента для повторной отправки"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Открыть страницу настроек"
},
"resetToDefault": {
"message": "Восстановить настройки по умолчанию"
},
"confirmResetToDefault": {
"message": "Вы уверены, что вы хотите восстановить настройки по умолчанию? Это действие не может быть отменено."
}
}

View File

@@ -230,6 +230,9 @@
"showSkipNotice": {
"message": "Zobraziť upozornenie pri preskočení segmentu"
},
"showCategoryGuidelines": {
"message": "Zobraziť pomocníka ku kategórii"
},
"noticeVisibilityMode0": {
"message": "Veľké upozornenia o preskočení"
},
@@ -506,18 +509,39 @@
"message": "do",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Zahŕňa plynulé prechody"
},
"generic_guideline2": {
"message": "Prehrať tak, ako keby nebolo nič preskočené"
},
"category_sponsor": {
"message": "Sponzor"
},
"category_sponsor_description": {
"message": "Platená propagácia, platené odporúčania a priame reklamy. Neplatí pre vlastnú propagáciu alebo neplatenú propagáciu dobročinností/tvorcov/webových stránok/produktov, ktoré sa im páčia."
},
"category_sponsor_guideline1": {
"message": "Platené promo"
},
"category_sponsor_guideline2": {
"message": "Nie pre dary a vlastný merch"
},
"category_selfpromo": {
"message": "Neplatená/Vlastná propagácia"
},
"category_selfpromo_description": {
"message": "Podobné ako sponzor, okrem neplatenej alebo vlastnej propagácie. Patria sem sekcie týkajúce sa merchu, donatov alebo informácií o tom, s kým spolupracovali."
},
"category_selfpromo_guideline1": {
"message": "Dary, členstvo a vlastný merch"
},
"category_selfpromo_guideline2": {
"message": "Obsah mimo témy, ktorý nepridáva žiadne informácie"
},
"category_selfpromo_guideline3": {
"message": "Nie pre firemne navrhnuté produkty a merch"
},
"category_exclusive_access": {
"message": "Exkluzívny Prístup"
},
@@ -528,12 +552,24 @@
"message": "Toto video predstavuje produkt, službu alebo miesto, ku ktorým získali bezplatný alebo dotovaný prístup",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Celé video predstavuje niečo, kde sa dá získať voľný alebo zľavnený prístup"
},
"category_interaction": {
"message": "Pripomienka interakcie (Prihlásiť sa na odber)"
},
"category_interaction_description": {
"message": "Krátka výzva dať like, prihlásiť sa na odber alebo sledovať ich v strede obsahu. Ak je to dlhé alebo sa týka niečoho konkrétneho, malo by to radšej byť v ramci vlastnej propagácie."
},
"category_interaction_guideline1": {
"message": "Krátke pripomienky na \"Páči sa mi to\", Odber a Sledovanie"
},
"category_interaction_guideline2": {
"message": "Zahŕňa nepriame pripomienky na komentovanie"
},
"category_interaction_guideline3": {
"message": "Nie pre všeobecné promo, iba výzvy k akcii"
},
"category_interaction_short": {
"message": "Pripomienka interakcie"
},
@@ -546,24 +582,51 @@
"category_intro_short": {
"message": "Prerušenie"
},
"category_intro_guideline1": {
"message": "Interval bez samotného obsahu"
},
"category_intro_guideline2": {
"message": "Nie pre prechody bez informácie"
},
"category_outro": {
"message": "Koncové karty / titulky"
},
"category_outro_description": {
"message": "Kredity alebo keď sa zobrazia YouTube koncové karty. Neplatí pre zhrnutia s informáciami."
},
"category_outro_guideline1": {
"message": "Nezahrňte obsah, aj keby už boli vidieť koncové karty"
},
"category_preview": {
"message": "Ukážka/Rekapitulácia"
},
"category_preview_description": {
"message": "Rýchla rekapitulácia predošlej epizódy alebo ukážka toho, čo bude nasledovať neskôr v aktuálnom videu. Myslené pre zostrihané videá, nie pre hovorený súhrn."
},
"category_preview_guideline1": {
"message": "Ukážky, ktoré sa zobrazia neskôr alebo v budúcom videu"
},
"category_preview_guideline2": {
"message": "Zhrnutie predošlého videa"
},
"category_preview_guideline3": {
"message": "Nie pre sekcie, ktoré majú dodatočný obsah"
},
"category_filler_description": {
"message": "Odbočky mimo tému pridané len pre zábavu, nepotrebné pre pochopenie hlavného obsahu videa. Nemalo by zahŕňať segmenty, ktoré vysvetľujú kontext alebo vedľajšie detaily."
},
"category_filler_short": {
"message": "Odbočka"
},
"category_filler_guideline1": {
"message": "Nepodstatné scény slúžiace len ako výplň alebo humor"
},
"category_filler_guideline2": {
"message": "Nepodarené a opakované zábery"
},
"category_filler_guideline3": {
"message": "Nie pre scény vyžadované pre pochopenie témy"
},
"category_music_offtopic": {
"message": "Hudba: časť bez hudby"
},
@@ -573,12 +636,27 @@
"category_music_offtopic_short": {
"message": "Bez hudby"
},
"category_music_offtopic_guideline1": {
"message": "Sekcie, ktoré nie sú v oficiálnom vydaní"
},
"category_music_offtopic_guideline2": {
"message": "Sekcia bez hudby v živom vystúpení"
},
"category_poi_highlight": {
"message": "Hlavný obsah videa"
},
"category_poi_highlight_description": {
"message": "Tá časť videa, ktorú ľudia vyhľadávajú. Podobné komentárom \"Video začína v čase x\"."
},
"category_poi_highlight_guideline1": {
"message": "Sekcia, ktorú vyhľadáva väčšina ľudí"
},
"category_poi_highlight_guideline2": {
"message": "Môže pomôcť preskočiť predošlý obsah"
},
"category_poi_highlight_guideline3": {
"message": "Môže preskočiť na hlavný obsah alebo náhľad"
},
"category_livestream_messages": {
"message": "Živé vysielanie: oznamy a dary"
},
@@ -819,6 +897,9 @@
"LearnMore": {
"message": "Zistiť viac"
},
"FullDetails": {
"message": "Úplné podrobnosti"
},
"CopyDownvoteButtonInfo": {
"message": "Dá palec dole a vytvorí kópiu, aby ste mohli segment znova odoslať"
},

View File

@@ -186,7 +186,7 @@
"message": "Detta döljer knapparna på YouTube-spelaren som du kan skicka in segment med som ska hoppas över."
},
"showSkipButton": {
"message": "Behåll knappen hoppa till markerat på spelaren"
"message": "Behåll knappen hoppa till höjdpunkt på spelaren"
},
"showInfoButton": {
"message": "Visa Infoknapp På YouTube-spelaren"
@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Visa ett meddelande efter att ett segment har hoppats över"
},
"showCategoryGuidelines": {
"message": "Visa kategorihjälp"
},
"noticeVisibilityMode0": {
"message": "Hoppa över-meddelanden i fullstorlek"
},
@@ -542,18 +545,39 @@
"message": "till",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Inkludera segmentövergångar"
},
"generic_guideline2": {
"message": "Spelar som om ingenting hoppades över"
},
"category_sponsor": {
"message": "Sponsormeddelande"
},
"category_sponsor_description": {
"message": "Betald marknadsföring, betalda hänvisningar och direktannonser, men inte till självbefodran eller gratis shoutouts till skapare/webbplatser/produkter de gillar."
},
"category_sponsor_guideline1": {
"message": "Betalda kampanjer"
},
"category_sponsor_guideline2": {
"message": "Inte för donationer eller anpassade varor"
},
"category_selfpromo": {
"message": "Obetald/självbefodran"
},
"category_selfpromo_description": {
"message": "Som \"sponsormeddelande\" men med undantag för obetald eller självkampanj. Detta inkluderar avsnitt om varor, donationer eller information om vem de samarbetade med."
},
"category_selfpromo_guideline1": {
"message": "Donationer, medlemskap och anpassade varor"
},
"category_selfpromo_guideline2": {
"message": "Gratis shoutouts som inte läggs till i videon"
},
"category_selfpromo_guideline3": {
"message": "Inte för företagsdesignade produkter och varor"
},
"category_exclusive_access": {
"message": "Exklusiv tillgång"
},
@@ -564,12 +588,24 @@
"message": "Denna video visar en produkt, tjänst eller plats som de har fått gratis eller subventionerad tillgång till",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Hela videon visar något med gratis eller subventionerad tillgång"
},
"category_interaction": {
"message": "Interaktionspåminnelse (Prenumerera)"
},
"category_interaction_description": {
"message": "När där är en kort påminnelse att gilla, prenumerera eller följa dem. Om det är långt eller om det gäller något specifikt bör det istället vara under självbefodran."
},
"category_interaction_guideline1": {
"message": "Korta påminnelser om att gilla, prenumerera eller följa"
},
"category_interaction_guideline2": {
"message": "Inkluderar indirekta påminnelser att kommentera"
},
"category_interaction_guideline3": {
"message": "Inte för allmän marknadsföring, endast för uppmaningar"
},
"category_interaction_short": {
"message": "Interaktionspåminnelse"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "Uppehåll"
},
"category_intro_guideline1": {
"message": "Intervall utan egentligt innehåll"
},
"category_intro_guideline2": {
"message": "Inte för övergångar med information"
},
"category_outro": {
"message": "Slutkort/Credits"
},
"category_outro_description": {
"message": "Credits eller när YouTube-slutkort visas. Inte för slut med information."
},
"category_outro_guideline1": {
"message": "Inkludera inte innehåll, även om slutkort visas på skärmen"
},
"category_preview": {
"message": "Förhandsgranska/sammanfatta"
},
"category_preview_description": {
"message": "Snabb sammanfattning av tidigare avsnitt eller en förhandsvisning av vad som kommer upp senare i den aktuella videon. Avsett för redigerade klipp, inte för sammanfattningar."
},
"category_preview_guideline1": {
"message": "Klipp som visas senare, eller i en framtida video"
},
"category_preview_guideline2": {
"message": "Sammanfattning av en tidigare video"
},
"category_preview_guideline3": {
"message": "Inte för sektioner som lägger till ytterligare innehåll"
},
"category_filler": {
"message": "Ämnesavvikelse/Skämt"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "Utfyllnad"
},
"category_filler_guideline1": {
"message": "Tangentiella scener endast för utfyllnad eller humor"
},
"category_filler_guideline2": {
"message": "Distraktioner, bloopers, repriser"
},
"category_filler_guideline3": {
"message": "Inte för scener som krävs för att förstå ämnet"
},
"category_music_offtopic": {
"message": "Musik: Icke-musikavsnitt"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Icke-musik"
},
"category_music_offtopic_guideline1": {
"message": "Avsnitt som inte finns i officiella utgåvor"
},
"category_music_offtopic_guideline2": {
"message": "Icke-musik i ett liveframträdande"
},
"category_poi_highlight": {
"message": "Markera"
"message": "Höjdpunkt"
},
"category_poi_highlight_description": {
"message": "Den del av videon som de flesta letar efter. Liknande kommentarer \"Video börjar på x\"."
},
"category_poi_highlight_guideline1": {
"message": "Avsnitt som de flesta personer letar efter"
},
"category_poi_highlight_guideline2": {
"message": "Kan hoppa över sammanhang"
},
"category_poi_highlight_guideline3": {
"message": "Kan hoppa över till titeln eller miniatyrbilden"
},
"category_livestream_messages": {
"message": "Liveström: Donations-/meddelandeavläsningar"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "Läs mer"
},
"FullDetails": {
"message": "Fullständiga detaljer"
},
"CopyDownvoteButtonInfo": {
"message": "Rösta ner och skapar en lokal kopia för dig att skicka in igen"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "Öppna alternativsidan"
},
"resetToDefault": {
"message": "Återställ inställningar till standard"
},
"confirmResetToDefault": {
"message": "Är du säker på att du vill återställa alla inställningar till deras standardvärden? Detta kan inte ångras."
}
}

View File

@@ -17,7 +17,7 @@
"message": "Bu daha önce zaten gönderilmiş"
},
"channelWhitelisted": {
"message": "Kanal beyazlistede!"
"message": "Kanala izin verildi!"
},
"Segment": {
"message": "kısım"
@@ -164,6 +164,9 @@
"copyPublicID": {
"message": "Herkese Açık Kullanıcı Kimliğini Kopyala"
},
"copySegmentID": {
"message": "Segment Kimliğini Kopyala"
},
"discordAdvert": {
"message": "Öneri ve geri bildirimleriniz için resmi Discord serverımıza katılın!"
},
@@ -236,6 +239,9 @@
"showSkipNotice": {
"message": "Bir Kısmı Atladıktan Sonra Uyarı Göster"
},
"showCategoryGuidelines": {
"message": "Kategori Yardımını Göster"
},
"noticeVisibilityMode0": {
"message": "Tam Boyutlu Atlama Bildirimleri"
},
@@ -539,18 +545,39 @@
"message": "'e",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "Segue geçişlerini dahil et"
},
"generic_guideline2": {
"message": "Hiçbir şey atlanmamış gibi oynuyor"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Ücretli tanıtım, ücretli yönlendirmeler ve doğrudan reklamlar. Kendini pazarlayan veya beğendiği içerik üreticilerine/sitelere/ürünlere atıfta bulunanlar için değil."
},
"category_sponsor_guideline1": {
"message": "Ücretli promosyonlar"
},
"category_sponsor_guideline2": {
"message": "Bağışlar veya özel ürünler için değil"
},
"category_selfpromo": {
"message": "Karşılıksız/Kendi Reklamı"
},
"category_selfpromo_description": {
"message": "\"Sponsor\" seçeneğinden farkı para karşılığı olmaması veya kendi reklamını yapmasıdır. Buna kendi markalı ürünlerini satmak, bağış toplamak ve videoda işbirliği yaptığı kimselerden bahsetmek dahildir."
},
"category_selfpromo_guideline1": {
"message": "Bağışlar, üyelikler ve özel ürünler"
},
"category_selfpromo_guideline2": {
"message": "Videoya eklenmeyen ücretsiz konuşmalar"
},
"category_selfpromo_guideline3": {
"message": "Kurumsal tasarım ürünleri ve malları için değil"
},
"category_exclusive_access": {
"message": "Özel Erişim"
},
@@ -561,12 +588,24 @@
"message": "Bu video; yayıncıya özel ücretle veya ücretsiz sunulan bir ürünün, hizmetin veya bir yerin reklamını yapıyor",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Tüm video, ücretsiz veya sübvansiyonlu erişime sahip bir şeyi sergiliyor"
},
"category_interaction": {
"message": "Etkileşim Hatırlatıcısı (Abonelik)"
},
"category_interaction_description": {
"message": "Videonun ortasında beğenmek, abone olmak veya takip etmek için kısa bir hatırlatma olan kısımdır. Eğer süresi uzunsa veya belirli bir şey hakkındaysa, kendi reklamını yapan kategorisi seçilmelidir."
},
"category_interaction_guideline1": {
"message": "Beğenmek, abone olmak veya takip etmek için kısa hatırlatıcılar"
},
"category_interaction_guideline2": {
"message": "Yorum yapmak için dolaylı hatırlatıcılar içerir"
},
"category_interaction_guideline3": {
"message": "Genel tanıtım için değil, yalnızca harekete geçirici mesajlar"
},
"category_interaction_short": {
"message": "Etkileşim Hatırlatıcısı"
},
@@ -579,18 +618,36 @@
"category_intro_short": {
"message": "Aralık"
},
"category_intro_guideline1": {
"message": "Gerçek içerik olmayan aralık"
},
"category_intro_guideline2": {
"message": "Bilgi içeren geçişler için değil"
},
"category_outro": {
"message": "Bitiş Ekranı/Jenerik"
},
"category_outro_description": {
"message": "Videoda emeği geçenlerin veya video sonunda çıkan kartların gösterildiği kısımlar. Bilgilendirici sona sahip videolar için değil."
},
"category_outro_guideline1": {
"message": "Bitiş kartları ekranda olsa bile içerik eklemeyin"
},
"category_preview": {
"message": "Ön İzleme/Özet"
},
"category_preview_description": {
"message": "Önceki bölümlerin bir özeti veya geçerli videonun içeriğine yönelik bir ön izleme. Bu özellik birleştirilmiş klipler içindir, konuşarak anlatılan özetleri kapsamaz."
},
"category_preview_guideline1": {
"message": "Daha sonra veya gelecekteki bir videoda görünen klipler"
},
"category_preview_guideline2": {
"message": "Bir önceki videonun özeti"
},
"category_preview_guideline3": {
"message": "Ek içerik ekleyen bölümler için değil"
},
"category_filler": {
"message": "Konuyla Alakasız / Şaka"
},
@@ -600,6 +657,15 @@
"category_filler_short": {
"message": "Alakasız Konu"
},
"category_filler_guideline1": {
"message": "Yalnızca dolgu veya mizah için teğet sahneler"
},
"category_filler_guideline2": {
"message": "Dikkat dağıtıcı şeyler, hatalar, tekrarlar"
},
"category_filler_guideline3": {
"message": "Konuyu anlamak için gerekli sahneler için değil"
},
"category_music_offtopic": {
"message": "Müzik: Müzik Olmayan Bölüm"
},
@@ -609,12 +675,27 @@
"category_music_offtopic_short": {
"message": "Müzik Olmayan Bölüm"
},
"category_music_offtopic_guideline1": {
"message": "Resmi sürümlerde olmayan bölümler"
},
"category_music_offtopic_guideline2": {
"message": "Canlı performansta müzik dışı"
},
"category_poi_highlight": {
"message": "Vurgu"
},
"category_poi_highlight_description": {
"message": "Videoda, çoğu insanın aradığı kısım. \"Video x sürede başlıyor\" yorumlarına benzer."
},
"category_poi_highlight_guideline1": {
"message": "Çoğu kişinin aradığı bölüm"
},
"category_poi_highlight_guideline2": {
"message": "Bağlamı atlayabilir"
},
"category_poi_highlight_guideline3": {
"message": "Başlığa veya küçük resme atlayabilir"
},
"category_livestream_messages": {
"message": "Canlı Yayın: Bağış/Mesaj Okuma"
},
@@ -864,6 +945,9 @@
"LearnMore": {
"message": "Dahasını Öğren"
},
"FullDetails": {
"message": "Tüm Detaylar"
},
"CopyDownvoteButtonInfo": {
"message": "Olumsuz oy verir ve yeni bir kısım seçmeniz için bir kopya oluşturur"
},
@@ -879,6 +963,15 @@
"ChangeCategoryTooltip": {
"message": "Bu, kısımlarınız için anında geçerli olur"
},
"downvote": {
"message": "Eksi oy"
},
"upvote": {
"message": "Olumlu oy"
},
"hideSegment": {
"message": "Segmenti gizleyin"
},
"SponsorTimeEditScrollNewFeature": {
"message": "Zaman aralığını hızlı bir şekilde ayarlamak için düzenleme kutusunun üzerinde fare tekerini kullanın. Değişikliklere ince ayar yapmak için ctrl veya shift tuşunun kombinasyonları kullanılabilir."
},
@@ -930,7 +1023,16 @@
"youtubeKeybindWarning": {
"message": "Bu kısayol YouTube tarafından kullanılıyor. Bunu kullanmak istediğinize emin misiniz?"
},
"betaServerWarning": {
"message": "BETA Sunucusu etkinleştirildi!"
},
"openOptionsPage": {
"message": "Seçenekler sayfasını aç"
},
"resetToDefault": {
"message": "Ayarları varsayılana sıfırla"
},
"confirmResetToDefault": {
"message": "Tüm ayarları varsayılan değerlerine sıfırlamak istediğinizden emin misiniz? Bu geri alınamaz."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Показувати сповіщення після пропуску сегмента"
},
"showCategoryGuidelines": {
"message": "Показати Довідку по Категоріях"
},
"noticeVisibilityMode0": {
"message": "Повнорозмірні сповіщення про пропуски"
},
@@ -947,5 +950,11 @@
},
"openOptionsPage": {
"message": "Відкрити сторінку налаштувань"
},
"resetToDefault": {
"message": "Повернутись до стандартних налаштувань"
},
"confirmResetToDefault": {
"message": "Ви впевнені, що хочете скинути всі налаштування до значень за замовчуванням? Це не можна буде скасувати."
}
}

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "Hiển thị thông báo sau khi bỏ qua phân đoạn"
},
"showCategoryGuidelines": {
"message": "Hiển thị Danh mục Trợ giúp"
},
"noticeVisibilityMode0": {
"message": "Thông báo bỏ qua với kích thước đầy đủ"
},
@@ -542,18 +545,36 @@
"message": "đến",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline2": {
"message": "Chơi như thể không có gì bị bỏ qua"
},
"category_sponsor": {
"message": "Nhà tài trợ"
},
"category_sponsor_description": {
"message": "Nội dung được trả tiền để quảng cáo, giới thiệu và quảng cáo trực tiếp. Không phải là quảng cáo không trả công hay được đề cập miễn phí."
},
"category_sponsor_guideline1": {
"message": "Quảng cáo trả phí"
},
"category_sponsor_guideline2": {
"message": "Không dành cho các khoản đóng góp"
},
"category_selfpromo": {
"message": "Quảng cáo không trả công/Tự quảng cáo"
},
"category_selfpromo_description": {
"message": "Tương tự như 'nhà tài trợ' ngoại trừ việc quảng cáo không được trả tiền hay tự quảng cáo. Điều này bao gồm các phần hàng hóa, đóng góp, hoặc thông tin về người mà họ hợp tác cùng."
},
"category_selfpromo_guideline1": {
"message": "Quyên góp, tư cách thành viên và hàng hóa tùy chỉnh"
},
"category_selfpromo_guideline2": {
"message": "Lời cảm ơn miễn phí không thêm vào video"
},
"category_selfpromo_guideline3": {
"message": "Không dành cho các sản phẩm và hàng hóa do công ty thiết kế"
},
"category_exclusive_access": {
"message": "Truy cập riêng"
},
@@ -564,12 +585,24 @@
"message": "Video này giới thiệu sản phẩm, dịch vụ hoặc vị trí mà họ đã nhận được quyền truy cập miễn phí hoặc được trợ cấp",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "Toàn bộ video giới thiệu nội dung nào đó có quyền truy cập miễn phí hoặc được trợ cấp"
},
"category_interaction": {
"message": "Nhắc tương tác (Đăng ký)"
},
"category_interaction_description": {
"message": "Nhắc nhở người xem Thích, Đăng ký hoặc Theo dõi. Nếu nó dài hoặc là một cái gì cụ thể, nó nên là danh mục \"Tự quảng cáo\"."
},
"category_interaction_guideline1": {
"message": "Lời nhắc ngắn gọn để thích, đăng ký hoặc theo dõi"
},
"category_interaction_guideline2": {
"message": "Bao gồm lời nhắc gián tiếp để nhận xét"
},
"category_interaction_guideline3": {
"message": "Không dành cho quảng cáo chung, chỉ dành cho lời kêu gọi hành động"
},
"category_interaction_short": {
"message": "Nhắc nhở tương tác"
},
@@ -582,18 +615,36 @@
"category_intro_short": {
"message": "Tạm ngừng"
},
"category_intro_guideline1": {
"message": "Khoảng thời gian không có nội dung thực tế"
},
"category_intro_guideline2": {
"message": "Không dành cho chuyển tiếp với thông tin"
},
"category_outro": {
"message": "Màn hình kết thúc/Danh đề"
},
"category_outro_description": {
"message": "Credits hoặc khi thẻ màn hình kết thúc của YouTube xuất hiện. Không dùng với những đoạn có thông tin."
},
"category_outro_guideline1": {
"message": "Không bao gồm nội dung, ngay cả khi thẻ kết thúc ở trên màn hình"
},
"category_preview": {
"message": "Xem trước/Tóm tắt"
},
"category_preview_description": {
"message": "Tóm tắt nhanh về tập trước/tập sau trong 1 chuỗi video (series) dài (hoặc cũng có thể là tóm tắt trước về video sắp chiếu)."
},
"category_preview_guideline1": {
"message": "Các clip xuất hiện sau đó hoặc trong một video trong tương lai"
},
"category_preview_guideline2": {
"message": "Tóm tắt video trước đó"
},
"category_preview_guideline3": {
"message": "Không dành cho các phần thêm nội dung bổ sung"
},
"category_filler_description": {
"message": "Tập hợp các cảnh không bắt buộc để xem trong video. Điều này không bao gồm các đoạn chứa nội dung hoặc nói về ngữ cảnh của video."
},

View File

@@ -239,6 +239,9 @@
"showSkipNotice": {
"message": "在跳過片段後顯示通知"
},
"showCategoryGuidelines": {
"message": "顯示分類說明"
},
"noticeVisibilityMode0": {
"message": "完整大小的略過提醒"
},
@@ -542,18 +545,39 @@
"message": "到",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"generic_guideline1": {
"message": "包含轉場"
},
"generic_guideline2": {
"message": "如同沒有略過任何片段般播放"
},
"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_selfpromo_guideline2": {
"message": "與影片無關之免費的推廣"
},
"category_selfpromo_guideline3": {
"message": "不是企業設計的商品和週邊商品"
},
"category_exclusive_access": {
"message": "獨家限定"
},
@@ -564,12 +588,24 @@
"message": "此影片展示了獲得特殊或免費存取的產品、服務或位置",
"description": "Short description for this category"
},
"category_exclusive_access_guideline1": {
"message": "整部影片在展示收到的免費或付費存取權"
},
"category_interaction": {
"message": "互動提醒 (訂閱)"
},
"category_interaction_description": {
"message": "當有短提醒來請觀眾按讚、訂閱或跟隨他們時。如果該片段很長或很針對某事,它應該歸類為自我推銷。"
},
"category_interaction_guideline1": {
"message": "要求點讚、訂閱或跟隨的短暫提醒"
},
"category_interaction_guideline2": {
"message": "包含要求評論的非直接提醒"
},
"category_interaction_guideline3": {
"message": "並非一般促銷、只限於要求進行某動作"
},
"category_interaction_short": {
"message": "互動提醒"
},
@@ -582,18 +618,36 @@
"category_intro_short": {
"message": "開場"
},
"category_intro_guideline1": {
"message": "無實際內容的片段"
},
"category_intro_guideline2": {
"message": "不是含有資訊的轉場"
},
"category_outro": {
"message": "結束畫面/鳴謝"
},
"category_outro_description": {
"message": "鳴謝或當 YouTube 結尾資訊卡出現時。不是含有資訊的總結"
},
"category_outro_guideline1": {
"message": "即使結尾的資訊卡已開始顯示也不應包含內容"
},
"category_preview": {
"message": "預覽/摘要"
},
"category_preview_description": {
"message": "係指影片的前情提要或內容預告等刻意編輯過的內容"
},
"category_preview_guideline1": {
"message": "出現在後面或未來影片的片段"
},
"category_preview_guideline2": {
"message": "上一部影片的回顧"
},
"category_preview_guideline3": {
"message": "不是有額外內容的部分"
},
"category_filler": {
"message": "離題閒聊/玩笑"
},
@@ -603,6 +657,15 @@
"category_filler_short": {
"message": "填充詞"
},
"category_filler_guideline1": {
"message": "為了幽默而離題的片段"
},
"category_filler_guideline2": {
"message": "分心、失誤、重播"
},
"category_filler_guideline3": {
"message": "不是需要觀看才能理解主題的片段"
},
"category_music_offtopic": {
"message": "音樂:非音樂部分"
},
@@ -612,12 +675,27 @@
"category_music_offtopic_short": {
"message": "非音樂"
},
"category_music_offtopic_guideline1": {
"message": "不在官方釋出內容的片段"
},
"category_music_offtopic_guideline2": {
"message": "在現場演出的非音樂片段"
},
"category_poi_highlight": {
"message": "重點"
},
"category_poi_highlight_description": {
"message": "大部分的人在影片尋找的部分。類似「影片在幾分幾秒開始」的留言"
},
"category_poi_highlight_guideline1": {
"message": "大部分觀眾想要看的部分"
},
"category_poi_highlight_guideline2": {
"message": "可以幫助略過前後文"
},
"category_poi_highlight_guideline3": {
"message": "可以跳過到標題或縮圖的內容"
},
"category_livestream_messages": {
"message": "直播:捐贈/訊息閱讀"
},
@@ -867,6 +945,9 @@
"LearnMore": {
"message": "了解更多"
},
"FullDetails": {
"message": "完整詳細資料"
},
"CopyDownvoteButtonInfo": {
"message": "按倒讚和建立本機副本以便你重新提交"
},
@@ -947,5 +1028,11 @@
},
"openOptionsPage": {
"message": "打開選項頁面"
},
"resetToDefault": {
"message": "重設設定為預設值"
},
"confirmResetToDefault": {
"message": "你確定要將所有設定重設為預設值嗎?此動作無法還原。"
}
}

View File

@@ -1,3 +1,12 @@
:root {
--skip-notice-right: 10px;
--skip-notice-padding: 5px;
--skip-notice-margin: 5px;
--skip-notice-border-horizontal: 5px;
--skip-notice-border-vertical: 10px;
--sb-dark-red-outline: rgb(130,0,0,0.9);
}
.hidden {
display: none;
}
@@ -12,16 +21,20 @@
height: 100%;
transform: scaleY(0.6) translateY(-30%) translateY(1.5px);
z-index: 40;
z-index: 42;
transition: transform .1s cubic-bezier(0,0,0.2,1);
}
.ytm-progress-bar > #previewbar {
.ytp-big-mode #previewbar {
transform: scaleY(0.625) translateY(-30%) translateY(1.5px);
}
.progress-bar-line > #previewbar {
height: 3px;
}
#previewbar.hovered {
div:hover > #previewbar.sbNotInvidious {
transform: scaleY(1)
}
@@ -30,6 +43,10 @@
height: 100%;
}
.previewbar.requiredSegment {
transform: scaleY(3)
}
/* Make sure settings are upfront */
.ytp-settings-menu {
z-index: 6000 !important;
@@ -45,23 +62,48 @@
transform: translateY(-1em) !important;
}
.ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips {
transform: translateY(-2em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible {
transform: translateY(-2em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips {
transform: translateY(-4em) !important;
}
#movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper {
transform: translateY(1em) !important;
}
#movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper {
transform: translateY(2em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper {
transform: translateY(0.5em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper {
transform: translateY(1em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper > .ytp-tooltip-text {
display: block !important;
transform: translateY(1em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible.sponsorTwoTooltips > .ytp-tooltip-text-wrapper > .ytp-tooltip-text {
display: block !important;
transform: translateY(2em) !important;
}
div:hover > .sponsorBlockChapterBar {
z-index: 41 !important;
}
/* */
.popup {
@@ -88,6 +130,20 @@
vertical-align: top;
}
.playerButton.hidden:not(.autoHiding) {
display: none !important;
}
/* Removes auto width from being a ytp-player-button */
.sbPlayerDownvote {
width: auto !important;
}
/* Adds back the padding */
.sbPlayerDownvote svg {
padding-right: 3.6px;
}
.autoHiding {
overflow: visible !important;
}
@@ -113,8 +169,8 @@
.sponsorSkipObject {
font-family: Roboto, Arial, Helvetica, sans-serif;
margin-left: 2px;
margin-right: 2px;
margin-left: var(--skip-notice-margin);
margin-right: var(--skip-notice-margin);
}
.sponsorSkipLogo {
@@ -145,7 +201,7 @@
position: absolute;
right: 5px;
bottom: 100px;
right: 10px;
right: var(--skip-notice-right);
}
.sponsorSkipNoticeParent {
@@ -156,9 +212,6 @@
}
.sponsorSkipNoticeParent, .sponsorSkipNotice {
min-width: 350px;
max-width: 50%;
border-spacing: 5px 10px;
padding-left: 5px;
padding-right: 5px;
@@ -166,7 +219,17 @@
border-collapse: unset;
}
.sponsorSkipNoticeParent {
min-width: 350px;
max-width: 50%;
}
.sponsorSkipNotice {
width: 100%;
}
.sponsorSkipNoticeTableContainer {
color: white;
background-color: rgba(28, 28, 28, 0.9);
border-radius: 5px;
min-width: 100%;
@@ -177,7 +240,7 @@
}
.sponsorSkipNoticeLimitWidth {
min-width: calc(100% - 50px);
max-width: calc(100% - 50px);
}
.sponsorSkipNotice .hidden {
@@ -345,6 +408,23 @@
.sponsorTimesInfoMessage {
font-size: 13.3333px;
color: rgb(235, 235, 235);
overflow-wrap: anywhere;
}
.sb-guidelines-notice .sponsorTimesInfoMessage td {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
font-size: 15px;
display: flex;
align-items: center;
}
.sponsorTimesInfoIcon {
width: 30px;
padding-right: 10px;
padding-left: 10px;
}
.segmentSummary {
@@ -501,12 +581,56 @@ input::-webkit-inner-spin-button {
margin-bottom: 5px;
background-color: rgba(28, 28, 28, 0.9);
border-color: rgb(130,0,0,0.9);
border-color: var(--sb-dark-red-outline);
color: white;
border-width: 3px;
padding: 3px;
}
.sponsorTimeEditSelector > option {
background-color: rgba(28, 28, 28, 0.9);
color: white;
}
/* Start SelectorComponent */
.sbSelector {
position: absolute;
text-align: center;
width: calc(100% - var(--skip-notice-right) - var(--skip-notice-padding) * 2 - var(--skip-notice-margin) * 2 - var(--skip-notice-border-horizontal) * 2);
z-index: 1000;
}
.sbSelectorBackground {
text-align: center;
background-color: rgba(28, 28, 28, 0.9);
border-radius: 6px;
padding: 3px;
margin: auto;
width: 170px;
}
.sbSelectorOption {
cursor: pointer;
background-color: rgb(43, 43, 43);
padding: 5px;
margin: 5px;
color: white;
border-radius: 5px;
font-size: 14px;
margin-left: auto;
margin-right: auto;
}
.sbSelectorOption:hover {
background-color: #3a0000;
}
/* End SelectorComponent */
.helpButton {
height: 25px;
cursor: pointer;
@@ -521,17 +645,6 @@ input::-webkit-inner-spin-button {
opacity: 0.8;
}
.sbChatNotice iframe {
height: 32px;
cursor: pointer;
height: 100%;
}
.sbChatClose {
height: 14px;
cursor: pointer;
}
.skipButtonControlBarContainer {
cursor: pointer;
display: flex;
@@ -605,6 +718,11 @@ input::-webkit-inner-spin-button {
border-color: rgba(28, 28, 28, 0.7) transparent transparent transparent;
}
.sponsorBlockTooltip.sbTriangle.centeredSBTriangle::after {
left: 50%;
right: 50%;
}
.sponsorBlockLockedColor {
color: #ffc83d;
}

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

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

After

Width:  |  Height:  |  Size: 1.2 KiB

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

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

106
public/icons/export.svg Normal file
View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 67.671 67.671"
style="enable-background:new 0 0 67.671 67.671;"
xml:space="preserve"
sodipodi:docname="export.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs41" /><sodipodi:namedview
id="namedview39"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="9.309749"
inkscape:cx="33.835499"
inkscape:cy="16.649214"
inkscape:window-width="1366"
inkscape:window-height="731"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g6"
style="fill:#ffffff">
<path
d="M 52.946,23.348 H 42.834 v 6 h 10.112 c 3.007,0 5.34,1.536 5.34,2.858 v 26.606 c 0,1.322 -2.333,2.858 -5.34,2.858 H 14.724 c -3.007,0 -5.34,-1.536 -5.34,-2.858 V 32.207 c 0,-1.322 2.333,-2.858 5.34,-2.858 h 10.11 v -6 h -10.11 c -6.359,0 -11.34,3.891 -11.34,8.858 v 26.606 c 0,4.968 4.981,8.858 11.34,8.858 h 38.223 c 6.358,0 11.34,-3.891 11.34,-8.858 V 32.207 C 64.286,27.239 59.305,23.348 52.946,23.348 Z"
id="path2"
style="fill:#ffffff" />
<path
d="m 24.957,14.955 c 0.768,0 1.535,-0.293 2.121,-0.879 l 3.756,-3.756 v 13.028 6 11.494 c 0,1.657 1.343,3 3,3 1.657,0 3,-1.343 3,-3 v -11.494 -6 -13.231 l 3.959,3.959 c 0.586,0.586 1.354,0.879 2.121,0.879 0.767,0 1.535,-0.293 2.121,-0.879 1.172,-1.171 1.172,-3.071 0,-4.242 L 36.078,0.877 C 35.492,0.291 34.725,0 33.958,0 33.95,0 33.943,0 33.935,0 33.927,0 33.92,0 33.912,0 33.145,0 32.378,0.291 31.792,0.877 l -8.957,8.957 c -1.172,1.171 -1.172,3.071 0,4.242 0.587,0.586 1.354,0.879 2.122,0.879 z"
id="path4"
style="fill:#ffffff" />
</g>
<g
id="g8"
style="fill:#ffffff">
</g>
<g
id="g10"
style="fill:#ffffff">
</g>
<g
id="g12"
style="fill:#ffffff">
</g>
<g
id="g14"
style="fill:#ffffff">
</g>
<g
id="g16"
style="fill:#ffffff">
</g>
<g
id="g18"
style="fill:#ffffff">
</g>
<g
id="g20"
style="fill:#ffffff">
</g>
<g
id="g22"
style="fill:#ffffff">
</g>
<g
id="g24"
style="fill:#ffffff">
</g>
<g
id="g26"
style="fill:#ffffff">
</g>
<g
id="g28"
style="fill:#ffffff">
</g>
<g
id="g30"
style="fill:#ffffff">
</g>
<g
id="g32"
style="fill:#ffffff">
</g>
<g
id="g34"
style="fill:#ffffff">
</g>
<g
id="g36"
style="fill:#ffffff">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

91
public/icons/import.svg Normal file
View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 67.671 67.671"
style="enable-background:new 0 0 67.671 67.671;"
xml:space="preserve"
sodipodi:docname="import.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs41" /><sodipodi:namedview
id="namedview39"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="9.309749"
inkscape:cx="33.835499"
inkscape:cy="33.835499"
inkscape:window-width="1920"
inkscape:window-height="983"
inkscape:window-x="482"
inkscape:window-y="768"
inkscape:window-maximized="1"
inkscape:current-layer="g6" />
<g
id="g6">
<path
d="M52.946,23.348H42.834v6h10.112c3.007,0,5.34,1.536,5.34,2.858v26.606c0,1.322-2.333,2.858-5.34,2.858H14.724 c-3.007,0-5.34-1.536-5.34-2.858V32.207c0-1.322,2.333-2.858,5.34-2.858h10.11v-6h-10.11c-6.359,0-11.34,3.891-11.34,8.858v26.606 c0,4.968,4.981,8.858,11.34,8.858h38.223c6.358,0,11.34-3.891,11.34-8.858V32.207C64.286,27.239,59.305,23.348,52.946,23.348z"
id="path2"
style="fill:#ffffff" />
<path
d="m 42.913,34.887 c -0.768,0 -1.370265,0.528017 -2.121,0.879 l -3.756,3.756 v -19.028 -6 V 3 c 0,-1.657 -1.343,-3 -3,-3 -1.657,0 -3,1.343 -3,3 v 11.494 12 13.231 l -3.959,-3.959 c -0.586,-0.586 -1.354,-0.879 -2.121,-0.879 -0.767,0 -1.535,0.293 -2.121,0.879 -1.172,1.171 -1.172,3.071 0,4.242 l 8.957,8.957 c 0.586,0.586 1.353,0.877 2.12,0.877 h 0.023 0.023 c 0.767,0 1.534,-0.291 2.12,-0.877 l 8.957,-8.957 c 1.172,-1.171 1.172,-3.071 0,-4.242 -0.587,-0.586 -1.354,-0.879 -2.122,-0.879 z"
id="path4"
sodipodi:nodetypes="sscccssscccssccsscssccs"
style="fill:#ffffff" />
</g>
<g
id="g8">
</g>
<g
id="g10">
</g>
<g
id="g12">
</g>
<g
id="g14">
</g>
<g
id="g16">
</g>
<g
id="g18">
</g>
<g
id="g20">
</g>
<g
id="g22">
</g>
<g
id="g24">
</g>
<g
id="g26">
</g>
<g
id="g28">
</g>
<g
id="g30">
</g>
<g
id="g32">
</g>
<g
id="g34">
</g>
<g
id="g36">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.3 KiB

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

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

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/icons/segway.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/icons/skip.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/></svg>

After

Width:  |  Height:  |  Size: 196 B

1
public/icons/sort.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"/></svg>

After

Width:  |  Height:  |  Size: 201 B

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

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -123,6 +123,14 @@ html, body {
border-image: linear-gradient(to right, var(--border-color), #00000000 80%) 1;
}
.categoryExtraOptions {
padding-bottom: 20px;
}
#music_offtopic_autoSkipOnMusicVideos {
padding-bottom: 0;
}
.option-group > div:last-child, .option-group > #keybind-dialog {
border-bottom: inherit;
}
@@ -309,6 +317,14 @@ input[type='number'] {
color: grey;
}
.disabled .slider {
cursor: default;
}
tr.disabled {
opacity: 0.3;
}
#options {
height: 100vh;
flex-basis: 80%;
@@ -346,6 +362,10 @@ input[type='number'] {
padding: 4px;
}
.switch-label {
width: inherit;
}
.switch {
position: relative;
display: inline-block;
@@ -670,4 +690,9 @@ svg {
#options > div {
max-width: 100%;
}
}
.upsellButton {
cursor: pointer;
vertical-align: middle;
}

View File

@@ -66,18 +66,6 @@
</div>
<div data-type="toggle" data-sync="autoSkipOnMusicVideos">
<div class="switch-container">
<label class="switch">
<input id="autoSkipOnMusicVideos" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="autoSkipOnMusicVideos">
__MSG_autoSkipOnMusicVideos__
</label>
</div>
</div>
<div data-type="toggle" data-sync="muteSegments">
<div class="switch-container">
<label class="switch">
@@ -110,6 +98,20 @@
<div class="small-description">__MSG_minDurationDescription__</div>
</div>
<div data-type="toggle" data-sync="manualSkipOnFullVideo">
<div class="switch-container">
<label class="switch">
<input id="manualSkipOnFullVideo" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="manualSkipOnFullVideo">
__MSG_enableManualSkipOnFullVideo__
</label>
</div>
<div class="small-description">__MSG_whatManualSkipOnFullVideo__</div>
</div>
<div data-type="toggle" data-sync="forceChannelCheck">
<div class="switch-container">
@@ -175,6 +177,18 @@
<option value="4">__MSG_noticeVisibilityMode4__</option>
</select>
</div>
<div data-type="toggle" data-sync="showCategoryGuidelines">
<div class="switch-container">
<label class="switch">
<input id="showCategoryGuidelines" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="showCategoryGuidelines">
__MSG_showCategoryGuidelines__
</label>
</div>
</div>
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideVideoPlayerControls">
<div class="switch-container">
@@ -302,6 +316,18 @@
</div>
</div>
<div data-type="toggle" data-toggle-type="reverse" data-sync="showUpsells" data-no-safari="true">
<div class="switch-container">
<label class="switch">
<input id="showUpsell" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="showUpsells">
__MSG_hideUpsells__
</label>
</div>
</div>
</div>
<div id="keybinds" class="option-group hidden">
@@ -321,6 +347,16 @@
<div class="inline"></div>
</div>
<div data-type="keybind-change" data-sync="nextChapterKeybind">
<label class="optionLabel">__MSG_nextChapterKeybind__:</label>
<div class="inline"></div>
</div>
<div data-type="keybind-change" data-sync="previousChapterKeybind">
<label class="optionLabel">__MSG_previousChapterKeybind__:</label>
<div class="inline"></div>
</div>
</div>
<div id="import" class="option-group hidden">
@@ -340,6 +376,8 @@
</div>
</div>
</div>
<div data-type="react-UnsubmittedVideosComponent"></div>
<div data-type="private-text-change" data-sync="*" data-confirm-message="exportOptionsWarning">
<h2>__MSG_exportOptions__</h2>
@@ -368,6 +406,12 @@
</div>
</div>
<div data-type="button-press" data-sync="resetToDefault" data-confirm-message="confirmResetToDefault">
<div class="option-button trigger-button">
__MSG_resetToDefault__
</div>
</div>
</div>
<div id="advanced" class="option-group hidden">
@@ -462,7 +506,7 @@
<div class="small-description">__MSG_copyDebugInformationOptions__</div>
</div>
<div data-type="toggle" data-sync="testingServer" data-confirm-message="testingServerWarning" data-no-safari="true">
<div class="switch-container">
<label class="switch">

View File

@@ -19,6 +19,12 @@
<br/>
<div class="center">
__MSG_invidiousPermissionRefresh__
</div>
<br/>
<div class="center">
<div id="acceptPermissionButton" class="option-button inline">
__MSG_acceptPermission__

View File

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

View File

@@ -6,11 +6,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link id="sponsorBlockPopupFont" href="/libs/Source+Sans+Pro.css" rel="stylesheet">
<link id="sponsorBlockStyleSheet" href="popup.css" rel="stylesheet">
<link id="sponsorBlockStyleSheet" href="shared.css" rel="stylesheet">
</head>
<body id="sponsorBlockPopupBody">
<body id="sponsorBlockPopupBody" style="visibility: hidden">
<div id="sponsorblockPopup" class="sponsorBlockPageBody sb-preload">
<button id="sbCloseButton" title="__MSG_closePopup__" class="sbCloseButton hidden">
<img src="icons/close.png" width="15" height="15">
</button>
<div id="sbBetaServerWarning" class="hidden" title="__MSG_openOptionsPage__">
__MSG_betaServerWarning__
</div>
@@ -19,20 +24,48 @@
<img src="icons/IconSponsorBlocker256px.png" alt="SponsorBlock" width="40" height="40" id="sponsorBlockPopupLogo">
<p class="u-mZ">SponsorBlock</p>
</header>
<div id="videoInfo">
<!-- Loading text -->
<p id="loadingIndicator" class="u-mZ">__MSG_noVideoID__</p>
<p id="loadingIndicator" class="u-mZ grey-text">__MSG_noVideoID__</p>
<!-- If the video was found in the database -->
<p id="videoFound" class="u-mZ"></p>
<p id="videoFound" class="u-mZ grey-text"></p>
<button id="refreshSegmentsButton" title="__MSG_refreshSegments__">
<img src="/icons/refresh.svg" alt="Refresh icon" id="refreshSegments" />
</button>
<!-- Video Segments -->
<div id="issueReporterContainer">
<div id="issueReporterTabs" class="hidden">
<span id="issueReporterTabSegments" class="sbSelected">
<span>__MSG_SegmentsCap__</span>
</span>
<span id="issueReporterTabChapters">
<span>__MSG_Chapters__</span>
</span>
</div>
<div id="issueReporterTimeButtons"></div>
<div id="issueReporterImportExport">
<div id="importExportButtons">
<button id="importSegmentsButton" title="__MSG_importSegments__">
<img src="/icons/import.svg" alt="Refresh icon" id="importSegments" />
</button>
<button id="exportSegmentsButton" class="hidden" title="__MSG_exportSegments__">
<img src="/icons/export.svg" alt="Export icon" id="exportSegments" />
</button>
</div>
<span id="importSegmentsMenu" class="hidden">
<textarea id="importSegmentsText" rows="5" style="width:80%"></textarea>
<button id="importSegmentsSubmit" title="__MSG_importSegments__">
__MSG_Import__
</button>
</span>
</div>
</div>
</div>
<!-- Toggle Box -->
<div class="sbControlsMenu">
<label id="whitelistButton" for="whitelistToggle" class="hidden sbControlsMenu-item">
<input type="checkbox" style="display:none;" id="whitelistToggle">
@@ -64,25 +97,31 @@
__MSG_forceChannelCheckPopup__
</a>
<!-- Submit box -->
<div id="mainControls" style="display: none">
<p class="sbHeader">
__MSG_recordTimesDescription__
</p>
<sub class="sponsorStartHint">__MSG_popupHint__</sub>
<div>
<button id="sponsorStart" class="sbMediumButton">__MSG_sponsorStart__</button>
</div>
<div id="submissionSection" style="display: none">
<b style="display: block; margin-top: 12px;">__MSG_submissionEditHint__</b>
<button id="submitTimes" class="sbMediumButton">__MSG_submitTimesButton__</button>
<sub class="sponsorStartHint grey-text">__MSG_popupHint__</sub>
<div align="center" style="margin: 8px 0;">
<button id="sponsorStart" class="sbMediumButton" style="margin-right: 8px">__MSG_sponsorStart__</button>
<button id="submitTimes" class="sbMediumButton" style="display: none;">__MSG_submitTimesButton__</button>
</div>
<span id="submissionHint" style="display: none;">__MSG_submissionEditHint__</span>
</div>
<h1 class="recordingSubtitle sbHeader">__MSG_yourWork__</h1>
<!-- Your Work box -->
<div class="sbYourWorkCols">
<div id="usernameElement">
<div>
<p class="u-mZ">__MSG_Username__:</p>
<p class="sbHeader" style="padding: 8px 16px;">
__MSG_yourWork__
</p>
<div>
<!-- Username -->
<div id="usernameElement">
<span class="u-mZ grey-text">__MSG_Username__:
<!-- loading/errors -->
<span id="setUsernameStatus" class="u-mZ white-text" style="display: none"></span>
</span>
<div id="setUsernameContainer">
<p id="usernameValue"></p>
<button id="setUsernameButton" title="__MSG_setUsername__">
@@ -93,65 +132,69 @@
</button>
</div>
<div id="setUsername" style="display: none">
<div id="setUsernameStatusContainer" style="display: none">
<p id="setUsernameStatus" class="u-mZ"></p>
</div>
<input id="usernameInput" placeholder="Username">
<button id="submitUsername">
<img src="/icons/check.svg" alt="__MSG_setUsername__" width="16" height="16" id="sbPopupIconCheck">
</button>
</div>
</div>
</div>
<div id="sponsorTimesContributionsContainer" class="hidden">
<div>
<p class="u-mZ">__MSG_Submissions__:</p>
<span id="sponsorTimesContributionsDisplay">
0
</span>
<!-- Submissions -->
<div id="sponsorTimesContributionsContainer" class="hidden">
<p class="u-mZ grey-text">__MSG_Submissions__:</p>
<span id="sponsorTimesContributionsDisplay">0</span>
</div>
</div>
<p id="sponsorTimesViewsContainer" style="display: none" class="u-mZ sbStatsSentence">
__MSG_savedPeopleFrom__
<b>
<span id="sponsorTimesViewsDisplay">0</span>
</b>
<span id="sponsorTimesViewsDisplayEndWord">__MSG_Segments__</span>
<br />
<span class="sbExtraInfo">
(
<b>
<span id="sponsorTimesOthersTimeSavedDisplay">0</span>
<span id="sponsorTimesOthersTimeSavedEndWord">__MSG_minsLower__</span>
</b>
<span>__MSG_youHaveSavedTimeEnd__</span>
)
</span>
</p>
<p id="sponsorTimesSkipsDoneContainer" style="display: none" class="u-mZ sbStatsSentence">
__MSG_youHaveSkipped__
<b>
<span id="sponsorTimesSkipsDoneDisplay">0</span>
</b>
<span id="sponsorTimesSkipsDoneEndWord">__MSG_Segments__</span>
<span class="sbExtraInfo">
(
<b>
<span id="sponsorTimeSavedDisplay">0</span>
<span id="sponsorTimeSavedEndWord">__MSG_minsLower__</span>
</b>
)
</span>
</p>
</div>
<p id="sponsorTimesViewsContainer" style="display: none" class="u-mZ">
__MSG_savedPeopleFrom__
<b><span id="sponsorTimesViewsDisplay">
0
</span></b>
<span id="sponsorTimesViewsDisplayEndWord">__MSG_Segments__</span>
<br>
(<b><span id="sponsorTimesOthersTimeSavedDisplay">0</span>
<span id="sponsorTimesOthersTimeSavedEndWord">__MSG_minsLower__</span></b>
<span>__MSG_youHaveSavedTimeEnd__</span>).
</p>
<p id="sponsorTimesSkipsDoneContainer" style="display: none" class="u-mZ">
__MSG_youHaveSkipped__
<b><span id="sponsorTimesSkipsDoneDisplay">
0
</span></b>
<span id="sponsorTimesSkipsDoneEndWord">__MSG_Segments__</span>
(<b><span id="sponsorTimeSavedDisplay">
0
</span>
<span id="sponsorTimeSavedEndWord">__MSG_minsLower__</span></b>).
</p>
<div id="sponsorTimesDonateContainer" style="display: none; align-items: center; justify-content: center;">
<img class="sbHeart" src="/icons/heart.svg" alt="Heart icon" />
<a id="sbConsiderDonateLink" href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener">
__MSG_considerDonating__
</a>
<img id="sbCloseDonate" src="/icons/close.png" alt="Close icon" height="8" style="padding-left: 5px; cursor: pointer;" />
</div>
<footer id="sbFooter">
<div id="sponsorTimesDonateContainer" style="display: none; align-items: center; justify-content: center;">
<img class="sbHeart" src="/icons/heart.svg" alt="Heart icon" />
<a id="sbConsiderDonateLink" href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener">
__MSG_considerDonating__
</a>
<img id="sbCloseDonate" src="/icons/close.png" alt="Close icon" height="8" style="padding-left: 5px; cursor: pointer;" />
</div>
<a href="https://sponsor.ajay.app" target="_blank" rel="noopener">__MSG_website__</a> |
<a href="https://sponsor.ajay.app/stats" target="_blank" rel="noopener">__MSG_viewLeaderboard__</a> |
<a id="helpButton">__MSG_help__</a>
<a href="https://sponsor.ajay.app" target="_blank" rel="noopener">__MSG_website__</a>
<a href="https://sponsor.ajay.app/stats" target="_blank" rel="noopener">__MSG_viewLeaderboard__</a>
<br />
<a href="https://github.com/ajayyy/SponsorBlock" target="_blank" rel="noopener">GitHub</a>
<br/>
<a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a> |
<a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a> |
<a id="helpButton">__MSG_help__</a> |
<a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a>
<a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a>
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">$</a>
</footer>

View File

@@ -0,0 +1 @@
{"Albania":{"allowed":true},"Algeria":{"allowed":true},"Angola":{"allowed":true},"Argentina":{"allowed":true},"Armenia":{"allowed":true},"Australia":{"allowed":false},"Austria":{"allowed":false},"Azerbaijan":{"allowed":true},"Bangladesh":{"allowed":true},"Belarus":{"allowed":true},"Belgium":{"allowed":false},"Belize":{"allowed":true},"Benin":{"allowed":true},"Bhutan":{"allowed":true},"Bolivia":{"allowed":true},"Bosnia and Herzegovina":{"allowed":true},"Botswana":{"allowed":true},"Brazil":{"allowed":true},"Bulgaria":{"allowed":true},"Burkina Faso":{"allowed":true},"Burundi":{"allowed":true},"Cameroon":{"allowed":true},"Canada":{"allowed":false},"Central African Republic":{"allowed":true},"Chad":{"allowed":true},"Chile":{"allowed":true},"China":{"allowed":true},"Colombia":{"allowed":true},"Comoros":{"allowed":true},"Costa Rica":{"allowed":true},"Croatia":{"allowed":true},"Cyprus":{"allowed":false},"Czech Republic":{"allowed":false},"Denmark":{"allowed":false},"Djibouti":{"allowed":true},"Dominican Republic":{"allowed":true},"DR Congo":{"allowed":true},"Ecuador":{"allowed":true},"Egypt":{"allowed":true},"El Salvador":{"allowed":true},"Estonia":{"allowed":false},"Eswatini":{"allowed":true},"Ethiopia":{"allowed":true},"Fiji":{"allowed":true},"Finland":{"allowed":false},"France":{"allowed":false},"Gabon":{"allowed":true},"Gambia":{"allowed":true},"Georgia":{"allowed":true},"Germany":{"allowed":false},"Ghana":{"allowed":true},"Greece":{"allowed":true},"Guatemala":{"allowed":true},"Guinea":{"allowed":true},"Guinea-Bissau":{"allowed":true},"Guyana":{"allowed":true},"Haiti":{"allowed":true},"Honduras":{"allowed":true},"Hungary":{"allowed":true},"Iceland":{"allowed":false},"India":{"allowed":true},"Iran":{"allowed":true},"Iraq":{"allowed":true},"Ireland":{"allowed":false},"Israel":{"allowed":false},"Italy":{"allowed":false},"Ivory Coast":{"allowed":true},"Jamaica":{"allowed":true},"Japan":{"allowed":false},"Jordan":{"allowed":true},"Kazakhstan":{"allowed":true},"Kenya":{"allowed":true},"Kiribati":{"allowed":true},"Kyrgyzstan":{"allowed":true},"Laos":{"allowed":true},"Latvia":{"allowed":true},"Lebanon":{"allowed":true},"Lesotho":{"allowed":true},"Liberia":{"allowed":true},"Lithuania":{"allowed":true},"Luxembourg":{"allowed":false},"Madagascar":{"allowed":true},"Malawi":{"allowed":true},"Malaysia":{"allowed":true},"Maldives":{"allowed":true},"Mali":{"allowed":true},"Malta":{"allowed":false},"Mauritania":{"allowed":true},"Mauritius":{"allowed":true},"Mexico":{"allowed":true},"Micronesia":{"allowed":true},"Moldova":{"allowed":true},"Mongolia":{"allowed":true},"Montenegro":{"allowed":true},"Morocco":{"allowed":true},"Mozambique":{"allowed":true},"Myanmar":{"allowed":true},"Namibia":{"allowed":true},"Nepal":{"allowed":true},"Netherlands":{"allowed":false},"Nicaragua":{"allowed":true},"Niger":{"allowed":true},"Nigeria":{"allowed":true},"North Macedonia":{"allowed":true},"Norway":{"allowed":false},"Pakistan":{"allowed":true},"Panama":{"allowed":true},"Papua New Guinea":{"allowed":true},"Paraguay":{"allowed":true},"Peru":{"allowed":true},"Philippines":{"allowed":true},"Poland":{"allowed":true},"Portugal":{"allowed":true},"Republic of the Congo":{"allowed":true},"Romania":{"allowed":true},"Russia":{"allowed":true},"Rwanda":{"allowed":true},"Saint Lucia":{"allowed":true},"Samoa":{"allowed":true},"Sao Tome and Principe":{"allowed":true},"Senegal":{"allowed":true},"Serbia":{"allowed":true},"Seychelles":{"allowed":true},"Sierra Leone":{"allowed":true},"Slovakia":{"allowed":true},"Slovenia":{"allowed":false},"Solomon Islands":{"allowed":true},"South Africa":{"allowed":true},"South Korea":{"allowed":false},"South Sudan":{"allowed":true},"Spain":{"allowed":false},"Sri Lanka":{"allowed":true},"Sudan":{"allowed":true},"Suriname":{"allowed":true},"Sweden":{"allowed":false},"Switzerland":{"allowed":false},"Syria":{"allowed":true},"Taiwan":{"allowed":false},"Tajikistan":{"allowed":true},"Tanzania":{"allowed":true},"Thailand":{"allowed":true},"Timor-Leste":{"allowed":true},"Togo":{"allowed":true},"Tonga":{"allowed":true},"Trinidad and Tobago":{"allowed":true},"Tunisia":{"allowed":true},"Turkey":{"allowed":true},"Turkmenistan":{"allowed":true},"Tuvalu":{"allowed":true},"Uganda":{"allowed":true},"Ukraine":{"allowed":true},"United Arab Emirates":{"allowed":false},"United Kingdom":{"allowed":false},"United States":{"allowed":false},"Uruguay":{"allowed":true},"Uzbekistan":{"allowed":true},"Vanuatu":{"allowed":true},"Venezuela":{"allowed":true},"Vietnam":{"allowed":true},"Yemen":{"allowed":true},"Zambia":{"allowed":true},"Zimbabwe":{"allowed":true}}

219
public/shared.css Normal file
View File

@@ -0,0 +1,219 @@
.sponsorSkipNoticeParent {
position: absolute;
bottom: 100px;
right: var(--skip-notice-right);
}
.sponsorSkipNoticeParent, .sponsorSkipNotice {
border-spacing: var(--skip-notice-border-horizontal) var(--skip-notice-border-vertical);
padding-left: var(--skip-notice-padding);
padding-right: var(--skip-notice-padding);
border-collapse: unset;
}
.sponsorSkipNoticeParent {
min-width: 350px;
max-width: 50%;
}
.sponsorSkipNotice {
width: 100%;
}
.sponsorSkipNoticeTableContainer {
background-color: rgba(28, 28, 28, 0.9);
border-radius: 5px;
min-width: 100%;
}
.sponsorSkipNotice {
transition: all 0.1s ease-out;
}
.sponsorSkipNoticeLimitWidth {
max-width: calc(100% - 50px);
}
.sponsorSkipNotice .hidden {
display: none;
}
/* For Cloudtube */
.sponsorSkipNotice td, .sponsorSkipNotice table, .sponsorSkipNotice th {
border: none;
}
.sponsorSkipNoticeFadeIn {
animation: fadeIn 0.5s ease-out;
}
.sponsorSkipNoticeFaded {
opacity: 0.5;
}
.sponsorSkipNoticeFadeOut {
transition: opacity 3s cubic-bezier(0.55, 0.055, 0.675, 0.19);
opacity: 0 !important;
animation: none !important;
}
.sponsorSkipNotice .sponsorSkipNoticeTimeLeft {
color: #eeeeee;
border-radius: 4px;
padding: 2px 5px;
font-size: 12px;
display: flex;
align-items: center;
border: 1px solid #eeeeee;
}
.sponsorSkipNoticeTimeLeft img {
vertical-align: middle;
height: 13px;
padding-top: 7.8%;
padding-bottom: 7.8%;
}
/* if two are very close to eachother */
.secondSkipNotice {
bottom: 290px;
}
.noticeLeftIcon {
display: flex;
align-items: center;
}
.sponsorSkipNotice .sponsorSkipNoticeUnskipSection {
float: left;
border-left: 1px solid rgb(150, 150, 150);
}
.sponsorSkipNoticeButton {
background: none;
color: rgb(235, 235, 235);
border: none;
display: inline-block;
font-size: 13.3333px !important;
cursor: pointer;
margin-right: 10px;
padding: 2px 5px;
}
.sponsorSkipNoticeButton:hover {
background-color: rgba(235, 235, 235,0.2);
border-radius: 4px;
transition: background-color 0.4s;
}
.sponsorSkipNoticeFirstRow .sponsorSkipNoticeButton.sponsorSkipSmallButton {
height: 1.3em;
padding: 0;
}
.sponsorTimesVoteButtonsContainer {
float: left;
vertical-align:middle;
padding: 2px 5px;
margin-right: 4px;
}
.sponsorTimesVoteButtonsContainer div{
display: inline-block;
}
.sponsorSkipNoticeRightSection {
right: 0;
position: absolute;
float: right;
margin-right: 10px;
display: flex;
align-items: center;
}
.sponsorSkipNoticeRightButton {
margin-right: 0;
}
.sponsorSkipNoticeCloseButton {
height: 10px;
width: 10px;
box-sizing: unset;
padding: 2px 5px;
margin-left: 2px;
float: right;
}
.sponsorSkipNoticeCloseButton.biggerCloseButton {
padding: 20px;
}
.sponsorSkipMessage {
font-size: 14px;
font-weight: bold;
color: rgb(235, 235, 235);
margin-top: auto;
display: inline-block;
margin-right: 10px;
margin-bottom: auto;
}
.sponsorSkipInfo {
font-size: 10px;
color: #000000;
text-align: center;
margin-top: 0px;
}
#sponsorTimesThanksForVotingText {
font-size: 20px;
font-weight: bold;
color: #000000;
text-align: center;
margin-top: 0px;
margin-bottom: 0px;
}
#sponsorTimesThanksForVotingInfoText {
font-size: 12px;
font-weight: bold;
color: #000000;
text-align: center;
margin-top: 0px;
}
.sponsorTimesVoteButtonMessage {
float: left;
}
.sponsorTimesInfoMessage {
font-size: 13.3333px;
color: rgb(235, 235, 235);
}
.sb-guidelines-notice .sponsorTimesInfoMessage td {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
font-size: 15px;
display: flex;
align-items: center;
}

94
public/upsell/index.html Normal file
View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<head>
<title>Upsell - SponsorBlock</title>
<meta charset="utf-8">
<link href="styles.css" rel="stylesheet" />
<script src="../js/vendor.js"></script>
<script src="../js/upsell.js"></script>
</head>
<body class="sponsorBlockPageBody">
<div id="title" class="titleBar">
<img src="../icons/LogoSponsorBlocker256px.png" height="80" class="profilepic" />
SponsorBlock
</div>
<br />
<div class="center">
<p>
__MSG_chaptersPage1__
</p>
</div>
<div class="center">
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/H_mP7bpbA_c?modestbranding=1&rel=0" title="Demo Video"
frameborder="0" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture"
allowfullscreen>
</iframe>
</div>
<br />
<div class="center row-item">
<a href="https://buy.ajay.app/l/sponsorblock" class="option-link side-by-side" target="_blank" rel="noreferrer">
<div id="oneTimePurchase" class="option-button inline">
__MSG_oneTimePurchase__
</div>
</a>
<a href="https://www.patreon.com/ajayyy" class="option-link side-by-side" target="_blank" rel="noreferrer">
<div class="option-button side-by-side inline">
__MSG_joinOnPatreon__
</div>
</a>
</div>
<div class="center row-item">
<input id="redeemCodeInput" class="option-text-box" type="text" placeholder="__MSG_enterLicenseKey__">
<div id="redeemButton" class="option-button inline">
__MSG_redeem__
</div>
</div>
<div class="center row-item">
<a href="https://www.patreon.com/oauth2/authorize?response_type=code&client_id=-W7ib8J-LB3jowb1fqE07A7RDUovy45_pOoWcjby6yr5upo6At8Jlg2BPhWDXO2k&redirect_uri=https%3A%2F%2Fsponsor.ajay.app%2Fapi%2FgenerateToken%2Fpatreon"
class="option-link" target="_blank" rel="noreferrer">
<div class="option-button inline">
__MSG_patreonSignIn__
</div>
</a>
</div>
<div id="cantAfford" class="center">
</div>
<div class="center">
__MSG_alreadyDonated__ sponsorblock-free@ajay.app
</div>
<div id="subsidizedPrice" class="center hidden">
__MSG_selectYourCountry__
</div>
<div id="subsidizedLink" class="center hidden">
<a href="https://buy.ajay.app/l/sponsorblock/purchasing-power" class="option-link" target="_blank"
rel="noreferrer">
<div class="option-button inline">
__MSG_discountLink__
</div>
</a>
</div>
<div id="noSubsidizedLink" class="center hidden">
__MSG_noDiscount__
</div>
</body>

387
public/upsell/styles.css Normal file
View File

@@ -0,0 +1,387 @@
/* Based on options page CSS */
html {
color-scheme: dark;
}
body {
font-family: sans-serif;
}
.center {
text-align: center;
}
.center p {
margin: auto;
}
.inline {
display: inline-block;
}
.bold {
font-weight: bold;
}
.hidden {
display: none !important;
}
.row-item {
margin-top: 10px;
margin-bottom: 10px;
}
.keybind-status {
display: inline;
}
.small-description {
color: white;
font-size: 13px;
}
.medium-description {
color: white;
font-size: 15px;
}
.option-text-box {
width: 300px;
}
.option-button {
cursor: pointer;
background-color: #c00000;
padding: 10px;
color: white;
border-radius: 5px;
font-size: 14px;
width: max-content;
}
.option-link {
text-decoration: none;
}
.option-link.side-by-side {
padding: 50px;
}
.option-button:hover {
background-color: #fc0303;
}
.option-button.disabled {
cursor: default;
background-color: #520000;
color: grey;
}
#options {
max-width: 60%;
text-align: left;
display: inline-block;
}
.switch-container:after {
content: attr(label-name);
position: absolute;
padding: 4px;
width: max-content;
font-size: 14px;
color: white;
}
.text-label-container {
font-size: 14px;
color: white;
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #707070;
}
.animated * {
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
}
.animated .slider:before {
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #fc0303;
}
input:checked + .slider:before {
-webkit-transform: translateX(16px);
-ms-transform: translateX(16px);
transform: translateX(16px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
/* Boilerplate CSS from https://ajay.app */
body {
background-color: #333333;
}
.projectPreview {
position: relative;
}
.projectPreviewImage {
position: absolute;
left: -90px;
width: 80px;
top: 50%;
transform: translateY(-50%);
}
.projectPreviewImageLarge {
position: absolute;
left: -210px;
width: 200px;
top: 50%;
transform: translateY(-20%);
}
.projectPreviewImageLargeRight {
position: absolute;
right: -210px;
width: 200px;
top: 50%;
transform: translateY(-50%);
}
.createdBy {
font-size: 14px;
text-align: center;
padding-top: 0px;
padding-bottom: 0px;
display: inline-block;
}
#title {
background-color: #636363;
text-align: center;
vertical-align: middle;
font-size: 50px;
color: #212121;
padding: 20px;
text-decoration: none;
transition: font-size 1s;
}
.subtitle {
font-size: 40px;
color: #dad8d8;
padding-top: 10px;
transition: font-size 0.4s;
}
.subtitle:hover {
font-size: 45px;
transition: font-size 0.4s;
}
.profilepic {
background-color: #636363 !important;
vertical-align: middle;
}
.profilepiccircle {
vertical-align: middle;
overflow: hidden;
border-radius: 50%;
}
a {
text-decoration: underline;
color: inherit;
}
.link {
padding: 20px;
height: 80px;
transition: height 0.2s;
}
.link:hover {
height: 95px;
transition: height 0.2s;
}
#contact,.smalllink {
font-size: 25px;
color: #e8e8e8;
text-align: center;
padding: 10px;
}
#contact {
text-decoration: none;
}
p,li {
font-size: 20px;
color: #c4c4c4;
padding: 10px;
}
p,li,code,a {
max-width: 60%;
text-align: left;
overflow-wrap: break-word;
}
@media screen and (orientation:portrait) {
p,li,code,a {
max-width: 100%;
}
.projectPreviewImage {
position: unset;
width: 130px;
display: block;
margin: auto;
transform: none;
}
}
.previewImage {
max-height: 200px;
}
img {
max-width: 100%;
text-align: center;
}
#recentPostTitle {
font-size: 30px;
color: #dad8d8;
}
#recentPostDate {
font-size: 15px;
color: #dad8d8;
}
h1,h2,h3,h4,h5,h6 {
color: #dad8d8;
}
svg {
text-decoration: none;
}
.number-container:before {
content: attr(label-name);
padding-right: 4px;
width: max-content;
font-size: 14px;
color: white;
}
/* React styles */
.categoryTableElement {
font-size: 16px;
color: white;
}
.categoryTableElement > * {
padding-right: 15px;
padding-bottom: 15px;
}
.optionsSelector {
background-color: #c00000;
color: white;
border: none;
font-size: 14px;
padding: 5px;
border-radius: 5px;
}
.categoryColorTextBox {
width: 60px;
background: none;
border: none;
}
#subsidizedPrice {
margin-top: 5px;
margin-bottom: 5px;
}
#discountButton {
text-decoration: underline;
cursor: pointer;
}

View File

@@ -8,11 +8,14 @@ import { Registration } from "./types";
window.SB = Config;
import Utils from "./utils";
import { GenericUtils } from "./utils/genericUtils";
const utils = new Utils({
registerFirefoxContentScript,
unregisterFirefoxContentScript
});
const popupPort: Record<string, chrome.runtime.Port> = {};
// Used only on Firefox, which does not support non persistent background pages.
const contentScriptRegistrations = {};
@@ -23,14 +26,37 @@ if (utils.isFirefox()) {
});
}
chrome.tabs.onUpdated.addListener(function(tabId) {
chrome.tabs.sendMessage(tabId, {
function onTabUpdatedListener(tabId: number) {
chrome.tabs.sendMessage(tabId, {
message: 'update',
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox
}
function onNavigationApiAvailableChange(changes: {[key: string]: chrome.storage.StorageChange}) {
if (changes.navigationApiAvailable) {
if (changes.navigationApiAvailable.newValue) {
chrome.tabs.onUpdated.removeListener(onTabUpdatedListener);
} else {
chrome.tabs.onUpdated.addListener(onTabUpdatedListener);
}
}
}
// If Navigation API is not supported, then background has to inform content script about video change.
// This happens on Safari, Firefox, and Chromium 101 (inclusive) and below.
chrome.tabs.onUpdated.addListener(onTabUpdatedListener);
utils.wait(() => Config.local !== null).then(() => {
if (Config.local.navigationApiAvailable) {
chrome.tabs.onUpdated.removeListener(onTabUpdatedListener);
}
});
if (!Config.configSyncListeners.includes(onNavigationApiAvailableChange)) {
Config.configSyncListeners.push(onNavigationApiAvailableChange);
}
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
switch(request.message) {
switch(request.message) {
case "openConfig":
chrome.tabs.create({url: chrome.runtime.getURL('options/options.html' + (request.hash ? '#' + request.hash : ''))});
return;
@@ -61,14 +87,45 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
case "unregisterContentScript":
unregisterFirefoxContentScript(request.id)
return false;
case "tabs": {
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id,
request.data,
(response) => {
callback(response);
}
);
});
return true;
}
case "time":
if (sender.tab) {
popupPort[sender.tab.id]?.postMessage(request);
}
return false;
}
});
chrome.runtime.onConnect.addListener((port) => {
if (port.name === "popup") {
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
popupPort[tabs[0].id] = port;
});
}
});
//add help page on install
chrome.runtime.onInstalled.addListener(function () {
// This let's the config sync to run fully before checking.
// This is required on Firefox
setTimeout(function() {
setTimeout(async () => {
const userID = Config.config.userID;
// If there is no userID, then it is the first install.
@@ -77,13 +134,19 @@ chrome.runtime.onInstalled.addListener(function () {
chrome.tabs.create({url: chrome.extension.getURL("/help/index.html")});
//generate a userID
const newUserID = utils.generateUserID();
const newUserID = GenericUtils.generateUserID();
//save this UUID
Config.config.userID = newUserID;
// Don't show update notification
Config.config.categoryPillUpdate = true;
}
if (Config.config.supportInvidious) {
if (!(await utils.containsInvidiousPermission())) {
chrome.tabs.create({url: chrome.extension.getURL("/permissions/index.html")});
}
}
}, 1500);
});
@@ -120,7 +183,7 @@ async function submitVote(type: number, UUID: string, category: string) {
if (userID == undefined || userID === "undefined") {
//generate one
userID = utils.generateUserID();
userID = GenericUtils.generateUserID();
Config.config.userID = userID;
}
@@ -167,7 +230,7 @@ async function asyncRequestToServer(type: string, address: string, data = {}) {
async function sendRequestToCustomServer(type: string, url: string, data = {}) {
// If GET, convert JSON to parameters
if (type.toLowerCase() === "get") {
url = utils.objectToURI(url, data, true);
url = GenericUtils.objectToURI(url, data, true);
data = null;
}

View File

@@ -0,0 +1,126 @@
import * as React from "react";
import Config from "../config";
import { Category, SegmentUUID, SponsorTime } from "../types";
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
import { AnimationUtils } from "../utils/animationUtils";
import { GenericUtils } from "../utils/genericUtils";
import { Tooltip } from "../render/Tooltip";
export interface ChapterVoteProps {
vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>;
}
export interface ChapterVoteState {
segment?: SponsorTime;
show: boolean;
}
class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVoteState> {
tooltip?: Tooltip;
constructor(props: ChapterVoteProps) {
super(props);
this.state = {
segment: null,
show: false
};
}
render(): React.ReactElement {
if (this.tooltip && !this.state.show) {
this.tooltip.close();
this.tooltip = null;
}
return (
<>
{/* Upvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerUpvoteChapter"}
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "hidden" : "")}
draggable="false"
title={chrome.i18n.getMessage("upvoteButtonInfo")}
onClick={(e) => this.vote(e, 1)}>
<ThumbsUpSvg className="playerButtonImage"
fill={Config.config.colorPalette.white}
width={null} height={null} />
</button>
{/* Downvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerDownvoteChapter"}
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "hidden" : "")}
draggable="false"
title={chrome.i18n.getMessage("reportButtonInfo")}
onClick={(e) => {
const chapterNode = document.querySelector(".ytp-chapter-container") as HTMLElement;
if (this.tooltip) {
this.tooltip.close();
this.tooltip = null;
} else {
const referenceNode = chapterNode?.parentElement?.parentElement;
if (referenceNode) {
const outerBounding = referenceNode.getBoundingClientRect();
const buttonBounding = (e.target as HTMLElement)?.parentElement?.getBoundingClientRect();
this.tooltip = new Tooltip({
referenceNode: chapterNode?.parentElement?.parentElement,
prependElement: chapterNode?.parentElement,
showLogo: false,
showGotIt: false,
bottomOffset: `${outerBounding.height + 25}px`,
leftOffset: `${buttonBounding.x - outerBounding.x}px`,
extraClass: "centeredSBTriangle",
buttons: [
{
name: chrome.i18n.getMessage("incorrectVote"),
listener: (event) => this.vote(event, 0, e.target as HTMLElement).then(() => {
this.tooltip?.close();
this.tooltip = null;
})
}, {
name: chrome.i18n.getMessage("harmfulVote"),
listener: (event) => this.vote(event, 30, e.target as HTMLElement).then(() => {
this.tooltip?.close();
this.tooltip = null;
})
}
]
});
}
}
}}>
<ThumbsDownSvg
className="playerButtonImage"
fill={downvoteButtonColor(this.state.segment ? [this.state.segment] : null, SkipNoticeAction.Downvote, SkipNoticeAction.Downvote)}
width={null}
height={null} />
</button>
</>
);
}
private async vote(event: React.MouseEvent, type: number, element?: HTMLElement): Promise<void> {
event.stopPropagation();
if (this.state.segment) {
const stopAnimation = AnimationUtils.applyLoadingAnimation(element ?? event.currentTarget as HTMLElement, 0.3);
const response = await this.props.vote(type, this.state.segment.UUID);
await stopAnimation();
if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) {
this.setState({
show: type === 1
});
} else if (response.statusCode !== 403) {
alert(GenericUtils.getErrorMessage(response.statusCode, response.responseText));
}
}
}
}
export default ChapterVoteComponent;

View File

@@ -11,6 +11,7 @@ export interface NoticeProps {
noticeTitle: string,
maxCountdownTime?: () => number,
dontPauseCountdown?: boolean,
amountOfPreviousNotices?: number,
showInSecondSlot?: boolean,
timed?: boolean,
@@ -24,6 +25,9 @@ export interface NoticeProps {
smaller?: boolean,
limitWidth?: boolean,
extraClass?: string,
hideLogo?: boolean,
hideRightInfo?: boolean,
// Callback for when this is closed
closeListener: () => void,
@@ -35,8 +39,6 @@ export interface NoticeProps {
}
export interface NoticeState {
noticeTitle: string,
maxCountdownTime: () => number,
countdownTime: number,
@@ -54,9 +56,13 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
amountOfPreviousNotices: number;
parentRef: React.RefObject<HTMLDivElement>;
constructor(props: NoticeProps) {
super(props);
this.parentRef = React.createRef();
const maxCountdownTime = () => {
if (this.props.maxCountdownTime) return this.props.maxCountdownTime();
else return Config.config.skipNoticeDuration;
@@ -71,8 +77,6 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
// Setup state
this.state = {
noticeTitle: props.noticeTitle,
maxCountdownTime,
//the countdown until this notice closes
@@ -97,9 +101,11 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
return (
<div id={"sponsorSkipNotice" + this.idSuffix}
className={"sponsorSkipObject sponsorSkipNoticeParent"
+ (this.props.showInSecondSlot ? " secondSkipNotice" : "")}
+ (this.props.showInSecondSlot ? " secondSkipNotice" : "")
+ (this.props.extraClass ? ` ${this.props.extraClass}` : "")}
onMouseEnter={(e) => this.onMouseEnter(e) }
onMouseLeave={() => this.timerMouseLeave()}
ref={this.parentRef}
style={noticeStyle} >
<div className={"sponsorSkipNoticeTableContainer"
+ (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")
@@ -114,16 +120,18 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
{/* Left column */}
<td className="noticeLeftIcon">
{/* Logo */}
<img id={"sponsorSkipLogo" + this.idSuffix}
className="sponsorSkipLogo sponsorSkipObject"
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
{!this.props.hideLogo &&
<img id={"sponsorSkipLogo" + this.idSuffix}
className="sponsorSkipLogo sponsorSkipObject"
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
}
<span id={"sponsorSkipMessage" + this.idSuffix}
style={{float: "left"}}
style={{float: "left", marginRight: this.props.hideLogo ? "0px" : null}}
className="sponsorSkipMessage sponsorSkipObject">
{this.state.noticeTitle}
{this.props.noticeTitle}
</span>
{this.props.firstColumn}
@@ -132,28 +140,30 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
{this.props.firstRow}
{/* Right column */}
<td className="sponsorSkipNoticeRightSection"
style={{top: "9.32px"}}>
{!this.props.hideRightInfo &&
<td className="sponsorSkipNoticeRightSection"
style={{top: "9.32px"}}>
{/* Time left */}
{this.props.timed ? (
<span id={"sponsorSkipNoticeTimeLeft" + this.idSuffix}
onClick={() => this.toggleManualPause()}
className="sponsorSkipObject sponsorSkipNoticeTimeLeft">
{this.getCountdownElements()}
</span>
) : ""}
{/* Time left */}
{this.props.timed ? (
<span id={"sponsorSkipNoticeTimeLeft" + this.idSuffix}
onClick={() => this.toggleManualPause()}
className="sponsorSkipObject sponsorSkipNoticeTimeLeft">
{this.getCountdownElements()}
</span>
) : ""}
{/* Close button */}
<img src={chrome.extension.getURL("icons/close.png")}
className={"sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
+ (this.props.biggerCloseButton ? " biggerCloseButton" : "")}
onClick={() => this.close()}>
</img>
</td>
{/* Close button */}
<img src={chrome.extension.getURL("icons/close.png")}
className={"sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
+ (this.props.biggerCloseButton ? " biggerCloseButton" : "")}
onClick={() => this.close()}>
</img>
</td>
}
</tr>
{this.props.children}
@@ -286,7 +296,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
}
pauseCountdown(): void {
if (!this.props.timed) return;
if (!this.props.timed || this.props.dontPauseCountdown) return;
//remove setInterval
if (this.countdownInterval) clearInterval(this.countdownInterval);
@@ -344,12 +354,6 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
if (!silent) this.props.closeListener();
}
changeNoticeTitle(title: string): void {
this.setState({
noticeTitle: title
});
}
addNoticeInfoMessage(message: string, message2 = ""): void {
//TODO: Replace
@@ -384,6 +388,10 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
document.querySelector("#sponsorSkipNotice" + this.idSuffix + " > tbody").insertBefore(thanksForVotingText2, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
}
}
getElement(): React.RefObject<HTMLDivElement> {
return this.parentRef;
}
}
export default NoticeComponent;

View File

@@ -1,6 +1,7 @@
import * as React from "react";
export interface NoticeTextSelectionProps {
icon?: string,
text: string,
idSuffix: string,
onClick?: (event: React.MouseEvent) => unknown
@@ -24,14 +25,42 @@ class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionPr
}
return (
<p id={"sponsorTimesInfoMessage" + this.props.idSuffix}
<tr id={"sponsorTimesInfoMessage" + this.props.idSuffix}
onClick={this.props.onClick}
style={style}
className="sponsorTimesInfoMessage">
{this.props.text}
</p>
<td>
{this.props.icon ?
<img src={chrome.runtime.getURL(this.props.icon)} className="sponsorTimesInfoIcon" />
: null}
<span>
{this.getTextElements(this.props.text)}
</span>
</td>
</tr>
);
}
private getTextElements(text: string): Array<string | React.ReactElement> {
const elements: Array<string | React.ReactElement> = [];
const textParts = text.split(/(?=\s+)/);
for (const textPart of textParts) {
if (textPart.match(/^\s*http/)) {
elements.push(
<a href={textPart} target="_blank" rel="noreferrer">
{textPart}
</a>
);
} else {
elements.push(textPart);
}
}
return elements;
}
}
export default NoticeTextSelectionComponent;

View File

@@ -0,0 +1,58 @@
import * as React from "react";
export interface SelectorOption {
label: string;
}
export interface SelectorProps {
id: string;
options: SelectorOption[];
onChange: (value: string) => void;
}
export interface SelectorState {
}
class SelectorComponent extends React.Component<SelectorProps, SelectorState> {
constructor(props: SelectorProps) {
super(props);
// Setup state
this.state = {
}
}
render(): React.ReactElement {
return (
<div id={this.props.id}
className="sbSelector">
<div className="sbSelectorBackground">
{this.getOptions()}
</div>
</div>
);
}
getOptions(): React.ReactElement[] {
const result: React.ReactElement[] = [];
for (const option of this.props.options) {
result.push(
<div className="sbSelectorOption"
onClick={(e) => {
e.stopPropagation();
this.props.onChange(option.label);
}}
key={option.label}>
{option.label}
</div>
);
}
return result;
}
}
export default SelectorComponent;

View File

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

View File

@@ -1,10 +1,13 @@
import * as React from "react";
import * as CompileConfig from "../../config.json";
import Config from "../config";
import { ActionType, Category, ContentContainer, SponsorTime } from "../types";
import { ActionType, Category, ChannelIDStatus, ContentContainer, SponsorTime } from "../types";
import Utils from "../utils";
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
import { RectangleTooltip } from "../render/RectangleTooltip";
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
import { GenericUtils } from "../utils/genericUtils";
import { noRefreshFetchingChaptersAllowed } from "../utils/licenseKey";
const utils = new Utils();
@@ -18,22 +21,30 @@ export interface SponsorTimeEditProps {
submissionNotice: SubmissionNoticeComponent;
categoryList?: Category[];
categoryChangeListener?: (index: number, category: Category) => void;
}
export interface SponsorTimeEditState {
editing: boolean;
sponsorTimeEdits: [string, string];
selectedCategory: Category;
description: string;
suggestedNames: SelectorOption[];
chapterNameSelectorOpen: boolean;
}
const DEFAULT_CATEGORY = "chooseACategory";
const categoryNamesGrams: string[] = [].concat(...CompileConfig.categoryList.filter((name) => name !== "chapter")
.map((name) => chrome.i18n.getMessage("category_" + name).split(/\/|\s|-/)));
class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, SponsorTimeEditState> {
idSuffix: string;
categoryOptionRef: React.RefObject<HTMLSelectElement>;
actionTypeOptionRef: React.RefObject<HTMLSelectElement>;
descriptionOptionRef: React.RefObject<HTMLInputElement>;
configUpdateListener: () => void;
@@ -41,26 +52,35 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
// Used when selecting POI or Full
timesBeforeChanging: number[] = [];
fullVideoWarningShown = false;
categoryNameWarningShown = false;
// For description auto-complete
fetchingSuggestions: boolean;
constructor(props: SponsorTimeEditProps) {
super(props);
this.categoryOptionRef = React.createRef();
this.actionTypeOptionRef = React.createRef();
this.descriptionOptionRef = React.createRef();
this.idSuffix = this.props.idSuffix;
this.previousSkipType = ActionType.Skip;
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
this.state = {
editing: false,
sponsorTimeEdits: [null, null],
selectedCategory: DEFAULT_CATEGORY as Category
selectedCategory: DEFAULT_CATEGORY as Category,
description: sponsorTime.description || "",
suggestedNames: [],
chapterNameSelectorOpen: false
};
}
componentDidMount(): void {
// Prevent inputs from triggering key events
document.getElementById("sponsorTimesContainer" + this.idSuffix).addEventListener('keydown', function (event) {
document.getElementById("sponsorTimeEditContainer" + this.idSuffix).addEventListener('keydown', function (event) {
event.stopPropagation();
});
@@ -86,6 +106,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
render(): React.ReactElement {
this.checkToShowFullVideoWarning();
this.checkToShowChapterWarning();
const style: React.CSSProperties = {
textAlign: "center"
@@ -95,14 +116,6 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
style.marginTop = "15px";
}
// This method is required to get !important
// https://stackoverflow.com/a/45669262/1985387
const oldYouTubeDarkStyles = (node) => {
if (node) {
node.style.setProperty("color", "black", "important");
node.style.setProperty("text-shadow", "none", "important");
}
};
// Create time display
let timeDisplay: JSX.Element;
const timeDisplayStyle: React.CSSProperties = {};
@@ -122,11 +135,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
</span>
<input id={"submittingTime0" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditInput"
ref={oldYouTubeDarkStyles}
type="text"
style={{color: "inherit", backgroundColor: "inherit"}}
value={this.state.sponsorTimeEdits[0]}
onChange={(e) => {this.handleOnChange(0, e, sponsorTime, e.target.value)}}
onWheel={(e) => {this.changeTimesWhenScrolling(0, e, sponsorTime)}}>
onChange={(e) => this.handleOnChange(0, e, sponsorTime, e.target.value)}
onWheel={(e) => this.changeTimesWhenScrolling(0, e, sponsorTime)}>
</input>
{sponsorTime.actionType !== ActionType.Poi ? (
@@ -137,11 +150,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
<input id={"submittingTime1" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditInput"
ref={oldYouTubeDarkStyles}
type="text"
style={{color: "inherit", backgroundColor: "inherit"}}
value={this.state.sponsorTimeEdits[1]}
onChange={(e) => {this.handleOnChange(1, e, sponsorTime, e.target.value)}}
onWheel={(e) => {this.changeTimesWhenScrolling(1, e, sponsorTime)}}>
onChange={(e) => this.handleOnChange(1, e, sponsorTime, e.target.value)}
onWheel={(e) => this.changeTimesWhenScrolling(1, e, sponsorTime)}>
</input>
<span id={"nowButton1" + this.idSuffix}
@@ -166,15 +179,15 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
style={timeDisplayStyle}
className="sponsorTimeDisplay"
onClick={this.toggleEditTime.bind(this)}>
{utils.getFormattedTime(segment[0], true) +
{GenericUtils.getFormattedTime(segment[0], true) +
((!isNaN(segment[1]) && sponsorTime.actionType !== ActionType.Poi)
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segment[1], true) : "")}
? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(segment[1], true) : "")}
</div>
);
}
return (
<div style={style}>
<div id={"sponsorTimeEditContainer" + this.idSuffix} style={style}>
{timeDisplay}
@@ -184,7 +197,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
className="sponsorTimeEditSelector sponsorTimeCategories"
defaultValue={sponsorTime.category}
ref={this.categoryOptionRef}
onChange={this.categorySelectionChange.bind(this)}>
style={{color: "inherit", backgroundColor: "inherit"}}
onChange={(event) => this.categorySelectionChange(event)}>
{this.getCategoryOptions()}
</select>
@@ -207,6 +221,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
<select id={"sponsorTimeActionTypes" + this.idSuffix}
className="sponsorTimeEditSelector sponsorTimeActionTypes"
defaultValue={sponsorTime.actionType}
style={{color: "inherit", backgroundColor: "inherit"}}
ref={this.actionTypeOptionRef}
onChange={(e) => this.actionTypeSelectionChange(e)}>
{this.getActionTypeOptions(sponsorTime)}
@@ -214,6 +229,28 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
</div>
): ""}
{/* Chapter Name */}
{sponsorTime.actionType === ActionType.Chapter ? (
<div onMouseLeave={() => this.setState({chapterNameSelectorOpen: false})}>
<input id={"chapterName" + this.idSuffix}
className="sponsorTimeEdit"
ref={this.descriptionOptionRef}
type="text"
value={this.state.description}
onContextMenu={(e) => e.stopPropagation()}
onChange={(e) => this.descriptionUpdate(e.target.value)}
onFocus={() => this.setState({chapterNameSelectorOpen: true})}>
</input>
{this.state.chapterNameSelectorOpen && this.state.description &&
<SelectorComponent
id={"chapterNameSelector" + this.idSuffix}
options={this.state.suggestedNames}
onChange={(v) => this.descriptionUpdate(v)}
/>
}
</div>
): ""}
<br/>
{/* Editing Tools */}
@@ -228,7 +265,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
<span id={"sponsorTimePreviewButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey)}>
{chrome.i18n.getMessage("preview")}
{sponsorTime.actionType !== ActionType.Chapter ? chrome.i18n.getMessage("preview")
: chrome.i18n.getMessage("End")}
</span>
): ""}
@@ -255,16 +293,15 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const sponsorTimeEdits = this.state.sponsorTimeEdits;
// check if change is small engough to show tooltip
const before = utils.getFormattedTimeToSeconds(sponsorTimeEdits[index]);
const after = utils.getFormattedTimeToSeconds(targetValue);
const before = GenericUtils.getFormattedTimeToSeconds(sponsorTimeEdits[index]);
const after = GenericUtils.getFormattedTimeToSeconds(targetValue);
const difference = Math.abs(before - after);
if (0 < difference && difference< 0.5) this.showScrollToEditToolTip();
if (0 < difference && difference < 0.5) this.showScrollToEditToolTip();
sponsorTimeEdits[index] = targetValue;
if (index === 0 && sponsorTime.actionType === ActionType.Poi) sponsorTimeEdits[1] = targetValue;
this.setState({sponsorTimeEdits});
this.saveEditTimes();
this.setState({sponsorTimeEdits}, () => this.saveEditTimes());
}
changeTimesWhenScrolling(index: number, e: React.WheelEvent, sponsorTime: SponsorTime): void {
@@ -280,7 +317,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
const sponsorTimeEdits = this.state.sponsorTimeEdits;
let timeAsNumber = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[index]);
let timeAsNumber = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[index]);
if (timeAsNumber !== null && e.deltaY != 0) {
if (e.deltaY < 0) {
timeAsNumber += step;
@@ -289,7 +326,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
} else {
timeAsNumber = 0;
}
sponsorTimeEdits[index] = utils.getFormattedTime(timeAsNumber, true);
sponsorTimeEdits[index] = GenericUtils.getFormattedTime(timeAsNumber, true);
if (sponsorTime.actionType === ActionType.Poi) sponsorTimeEdits[1] = sponsorTimeEdits[0];
this.setState({sponsorTimeEdits});
@@ -299,26 +337,29 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
showScrollToEditToolTip(): void {
if (!Config.config.scrollToEditTimeUpdate && document.getElementById("sponsorRectangleTooltip" + "sponsorTimesContainer" + this.idSuffix) === null) {
this.showToolTip(chrome.i18n.getMessage("SponsorTimeEditScrollNewFeature"), () => { Config.config.scrollToEditTimeUpdate = true });
this.showToolTip(chrome.i18n.getMessage("SponsorTimeEditScrollNewFeature"), "scrollToEdit", () => { Config.config.scrollToEditTimeUpdate = true });
}
}
showToolTip(text: string, buttonFunction?: () => void): boolean {
showToolTip(text: string, id: string, buttonFunction?: () => void): boolean {
const element = document.getElementById("sponsorTimesContainer" + this.idSuffix);
if (element) {
new RectangleTooltip({
text,
referenceNode: element.parentElement,
prependElement: element,
timeout: 15,
bottomOffset: 0 + "px",
leftOffset: -318 + "px",
backgroundColor: "rgba(28, 28, 28, 1.0)",
htmlId: "sponsorTimesContainer" + this.idSuffix,
buttonFunction,
fontSize: "14px",
maxHeight: "200px"
});
if (element) {
const htmlId = `sponsorRectangleTooltip${id + this.idSuffix}`;
if (!document.getElementById(htmlId)) {
new RectangleTooltip({
text,
referenceNode: element.parentElement,
prependElement: element,
timeout: 15,
bottomOffset: 0 + "px",
leftOffset: -318 + "px",
backgroundColor: "rgba(28, 28, 28, 1.0)",
htmlId,
buttonFunction,
fontSize: "14px",
maxHeight: "200px"
});
}
return true;
} else {
@@ -333,12 +374,25 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
if (videoPercentage > 0.6 && !this.fullVideoWarningShown
&& (sponsorTime.category === "sponsor" || sponsorTime.category === "selfpromo" || sponsorTime.category === "chooseACategory")) {
if (this.showToolTip(chrome.i18n.getMessage("fullVideoTooltipWarning"))) {
if (this.showToolTip(chrome.i18n.getMessage("fullVideoTooltipWarning"), "fullVideoWarning")) {
this.fullVideoWarningShown = true;
}
}
}
checkToShowChapterWarning(): void {
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
if (sponsorTime.actionType === ActionType.Chapter && sponsorTime.description
&& !this.categoryNameWarningShown
&& categoryNamesGrams.some(
(category) => sponsorTime.description.toLowerCase().includes(category.toLowerCase()))) {
if (this.showToolTip(chrome.i18n.getMessage("chapterNameTooltipWarning"), "chapterWarning")) {
this.categoryNameWarningShown = true;
}
}
}
getCategoryOptions(): React.ReactElement[] {
const elements = [(
<option value={DEFAULT_CATEGORY}
@@ -348,6 +402,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
)];
for (const category of (this.props.categoryList ?? CompileConfig.categoryList)) {
// If permission not loaded, treat it like we have permission except chapter
const defaultBlockCategories = ["chapter"];
const permission = Config.config.permissions[category as Category] && (category !== "chapter" || noRefreshFetchingChaptersAllowed());
if ((defaultBlockCategories.includes(category) || permission !== undefined) && !permission) continue;
elements.push(
<option value={category}
key={category}
@@ -365,9 +424,10 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
categorySelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {
const chosenCategory = event.target.value as Category;
// See if show more categories was pressed
if (event.target.value !== DEFAULT_CATEGORY && !Config.config.categorySelections.some((category) => category.name === event.target.value)) {
const chosenCategory = event.target.value;
if (chosenCategory !== DEFAULT_CATEGORY && !Config.config.categorySelections.some((category) => category.name === chosenCategory)) {
event.target.value = DEFAULT_CATEGORY;
// Alert that they have to enable this category first
@@ -381,8 +441,12 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
this.handleReplacingLostTimes(event.target.value as Category, sponsorTime.actionType, sponsorTime);
this.handleReplacingLostTimes(chosenCategory, sponsorTime.actionType, sponsorTime);
this.saveEditTimes();
if (this.props.categoryChangeListener) {
this.props.categoryChangeListener(this.props.index, chosenCategory);
}
}
actionTypeSelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {
@@ -414,7 +478,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
this.previousSkipType = ActionType.Full;
} else if ((category === "chooseACategory" || (CompileConfig.categorySupport[category]?.includes(ActionType.Skip)
} else if ((category === "chooseACategory" || ((CompileConfig.categorySupport[category]?.includes(ActionType.Skip)
|| CompileConfig.categorySupport[category]?.includes(ActionType.Chapter))
&& ![ActionType.Poi, ActionType.Full].includes(this.getNextActionType(category, actionType))))
&& this.previousSkipType !== ActionType.Skip) {
if (this.timesBeforeChanging[0]) {
@@ -464,7 +529,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
this.setState({
sponsorTimeEdits: this.getFormattedSponsorTimesEdits(sponsorTime)
}, this.saveEditTimes);
}, () => this.saveEditTimes());
}
toggleEditTime(): void {
@@ -487,16 +552,16 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
/** Returns an array in the sponsorTimeEdits form (formatted time string) from a normal seconds sponsor time */
getFormattedSponsorTimesEdits(sponsorTime: SponsorTime): [string, string] {
return [utils.getFormattedTime(sponsorTime.segment[0], true),
utils.getFormattedTime(sponsorTime.segment[1], true)];
return [GenericUtils.getFormattedTime(sponsorTime.segment[0], true),
GenericUtils.getFormattedTime(sponsorTime.segment[1], true)];
}
saveEditTimes(): void {
const sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
if (this.state.editing) {
const startTime = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[0]);
const endTime = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[1]);
const startTime = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[0]);
const endTime = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[1]);
// Change segment time only if the format was correct
if (startTime !== null && endTime !== null) {
@@ -507,8 +572,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const category = this.categoryOptionRef.current.value as Category
sponsorTimesSubmitting[this.props.index].category = category;
const inputActionType = this.actionTypeOptionRef?.current?.value as ActionType;
sponsorTimesSubmitting[this.props.index].actionType = this.getNextActionType(category, inputActionType);
const actionType = this.getNextActionType(category, this.actionTypeOptionRef?.current?.value as ActionType);
sponsorTimesSubmitting[this.props.index].actionType = actionType;
const description = actionType === ActionType.Chapter ? this.descriptionOptionRef?.current?.value : "";
sponsorTimesSubmitting[this.props.index].description = description;
Config.config.unsubmittedSegments[this.props.contentContainer().sponsorVideoID] = sponsorTimesSubmitting;
Config.forceSyncUpdate("unsubmittedSegments");
@@ -530,19 +598,19 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
previewTime(ctrlPressed = false, shiftPressed = false): void {
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
const index = this.props.index;
const skipTime = sponsorTimes[index].segment[0];
// If segment starts at 0:00, start playback at the end of the segment
if (skipTime === 0) {
this.props.contentContainer().previewTime(sponsorTimes[index].segment[1]);
return;
}
let seekTime = 2;
if (ctrlPressed) seekTime = 0.5;
if (shiftPressed) seekTime = 0.25;
this.props.contentContainer().previewTime(skipTime - (seekTime * this.props.contentContainer().v.playbackRate));
const startTime = sponsorTimes[index].segment[0];
const endTime = sponsorTimes[index].segment[1];
const isChapter = sponsorTimes[index].actionType === ActionType.Chapter;
// If segment starts at 0:00, start playback at the end of the segment
const skipToEndTime = startTime === 0 || isChapter;
const skipTime = skipToEndTime ? endTime : (startTime - (seekTime * this.props.contentContainer().v.playbackRate));
this.props.contentContainer().previewTime(skipTime, !isChapter);
}
inspectTime(): void {
@@ -586,6 +654,41 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
}
descriptionUpdate(description: string): void {
this.setState({
description
});
if (!this.fetchingSuggestions) {
this.fetchSuggestions(description);
}
this.saveEditTimes();
}
async fetchSuggestions(description: string): Promise<void> {
if (this.props.contentContainer().channelIDInfo.status !== ChannelIDStatus.Found) return;
this.fetchingSuggestions = true;
const result = await utils.asyncRequestToServer("GET", "/api/chapterNames", {
description,
channelID: this.props.contentContainer().channelIDInfo.id
});
if (result.ok) {
try {
const names = JSON.parse(result.responseText) as {description: string}[];
this.setState({
suggestedNames: names.map(n => ({
label: n.description
}))
});
} catch (e) {} //eslint-disable-line no-empty
}
this.fetchingSuggestions = false;
}
configUpdate(): void {
this.forceUpdate();
}

View File

@@ -1,10 +1,13 @@
import * as React from "react";
import Config from "../config"
import { ContentContainer } from "../types";
import GenericNotice from "../render/GenericNotice";
import { Category, ContentContainer } from "../types";
import * as CompileConfig from "../../config.json";
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
import { getGuidelineInfo } from "../utils/constants";
export interface SubmissionNoticeProps {
// Contains functions and variables from the content script needed by the skip notice
@@ -32,6 +35,8 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
videoObserver: MutationObserver;
guidelinesReminder: GenericNotice;
constructor(props: SubmissionNoticeProps) {
super(props);
this.noticeRef = React.createRef();
@@ -68,12 +73,20 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
}
render(): React.ReactElement {
const sortButton =
<img id={"sponsorSkipSortButton" + this.state.idSuffix}
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipSmallButton"
onClick={() => this.sortSegments()}
title={chrome.i18n.getMessage("sortSegments")}
src={chrome.extension.getURL("icons/sort.svg")}>
</img>;
return (
<NoticeComponent noticeTitle={this.state.noticeTitle}
idSuffix={this.state.idSuffix}
ref={this.noticeRef}
closeListener={this.cancel.bind(this)}
zIndex={5000}>
zIndex={5000}
firstColumn={sortButton}>
{/* Text Boxes */}
{this.getMessageBoxes()}
@@ -128,6 +141,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
index={i}
contentContainer={this.props.contentContainer}
submissionNotice={this}
categoryChangeListener={this.categoryChangeListener.bind(this)}
ref={timeRef}>
</SponsorTimeEditComponent>
);
@@ -154,9 +168,10 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
}
cancel(): void {
this.guidelinesReminder?.close();
this.noticeRef.current.close(true);
this.contentContainer().resetSponsorSubmissionNotice();
this.contentContainer().resetSponsorSubmissionNotice(false);
this.props.closeListener();
}
@@ -190,6 +205,55 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
this.cancel();
}
sortSegments(): void {
let sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
sponsorTimesSubmitting = sponsorTimesSubmitting.sort((a, b) => a.segment[0] - b.segment[0]);
Config.config.unsubmittedSegments[this.props.contentContainer().sponsorVideoID] = sponsorTimesSubmitting;
Config.forceSyncUpdate("unsubmittedSegments");
this.forceUpdate();
}
categoryChangeListener(index: number, category: Category): void {
const dialogWidth = this.noticeRef?.current?.getElement()?.current?.offsetWidth;
if (category !== "chooseACategory" && Config.config.showCategoryGuidelines
&& this.contentContainer().v.offsetWidth > dialogWidth * 2) {
const options = {
title: chrome.i18n.getMessage(`category_${category}`),
textBoxes: getGuidelineInfo(category),
buttons: [{
name: chrome.i18n.getMessage("FullDetails"),
listener: () => window.open(CompileConfig.wikiLinks[category])
},
{
name: chrome.i18n.getMessage("Hide"),
listener: () => {
Config.config.showCategoryGuidelines = false;
this.guidelinesReminder?.close();
this.guidelinesReminder = null;
}
}],
timed: false,
style: {
right: `${dialogWidth + 10}px`,
},
extraClass: "sb-guidelines-notice"
};
if (options.textBoxes) {
if (this.guidelinesReminder) {
this.guidelinesReminder.update(options);
} else {
this.guidelinesReminder = new GenericNotice(null, "GuidelinesReminder", options);
}
} else {
this.guidelinesReminder?.close();
this.guidelinesReminder = null;
}
}
}
}
export default SubmissionNoticeComponent;

View File

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

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import * as CompileConfig from "../../config.json";
import { Category } from "../types";
import * as CompileConfig from "../../../config.json";
import { Category } from "../../types";
import CategorySkipOptionsComponent from "./CategorySkipOptionsComponent";
export interface CategoryChooserProps {

View File

@@ -1,10 +1,13 @@
import * as React from "react";
import Config from "../config"
import * as CompileConfig from "../../config.json";
import { Category, CategorySkipOption } from "../types";
import Config from "../../config"
import * as CompileConfig from "../../../config.json";
import { Category, CategorySkipOption } from "../../types";
import { getCategorySuffix } from "../utils/categoryUtils";
import { getCategorySuffix } from "../../utils/categoryUtils";
import ToggleOptionComponent, { ToggleOptionProps } from "./ToggleOptionComponent";
import { fetchingChaptersAllowed } from "../../utils/licenseKey";
import LockSvg from "../../svg-icons/lock_svg";
export interface CategorySkipOptionsProps {
category: Category;
@@ -15,9 +18,11 @@ export interface CategorySkipOptionsProps {
export interface CategorySkipOptionsState {
color: string;
previewColor: string;
hideChapter: boolean;
}
class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> {
setBarColorTimeout: NodeJS.Timeout;
constructor(props: CategorySkipOptionsProps) {
super(props);
@@ -26,10 +31,28 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
this.state = {
color: props.defaultColor || Config.config.barTypes[this.props.category]?.color,
previewColor: props.defaultPreviewColor || Config.config.barTypes["preview-" + this.props.category]?.color,
}
hideChapter: true
};
fetchingChaptersAllowed().then((allowed) => {
this.setState({
hideChapter: !allowed
});
});
}
render(): React.ReactElement {
if (this.state.hideChapter) {
// Ensure force update refreshes this
fetchingChaptersAllowed().then((allowed) => {
if (allowed) {
this.setState({
hideChapter: !allowed
});
}
});
}
let defaultOption = "disable";
// Set the default opton properly
for (const categorySelection of Config.config.categorySelections) {
@@ -50,10 +73,20 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}
}
let extraClasses = "";
const disabled = this.props.category === "chapter" && this.state.hideChapter;
if (disabled) {
extraClasses += " disabled";
if (!Config.config.showUpsells) {
return <></>;
}
}
return (
<>
<tr id={this.props.category + "OptionsRow"}
className="categoryTableElement">
className={`categoryTableElement${extraClasses}`} >
<td id={this.props.category + "OptionName"}
className="categoryTableLabel">
{chrome.i18n.getMessage("category_" + this.props.category)}
@@ -64,21 +97,29 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
<select
className="optionsSelector"
defaultValue={defaultOption}
disabled={disabled}
onChange={this.skipOptionSelected.bind(this)}>
{this.getCategorySkipOptions()}
</select>
{disabled &&
<LockSvg className="upsellButton" onClick={() => chrome.tabs.create({url: chrome.runtime.getURL('upsell/index.html')})}/>
}
</td>
<td id={this.props.category + "ColorOption"}
className="colorOption">
<input
className="categoryColorTextBox option-text-box"
type="color"
onChange={(event) => this.setColorState(event, false)}
value={this.state.color} />
</td>
{this.props.category !== "chapter" &&
<td id={this.props.category + "ColorOption"}
className="colorOption">
<input
className="categoryColorTextBox option-text-box"
type="color"
disabled={disabled}
onChange={(event) => this.setColorState(event, false)}
value={this.state.color} />
</td>
}
{this.props.category !== "exclusive_access" &&
{!["chapter", "exclusive_access"].includes(this.props.category) &&
<td id={this.props.category + "PreviewColorOption"}
className="previewColorOption">
<input
@@ -92,7 +133,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
</tr>
<tr id={this.props.category + "DescriptionRow"}
className="small-description categoryTableDescription">
className={`small-description categoryTableDescription${extraClasses}`}>
<td
colSpan={2}>
{chrome.i18n.getMessage("category_" + this.props.category + "_description")}
@@ -102,6 +143,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
</a>
</td>
</tr>
{this.getExtraOptionComponents(this.props.category, extraClasses, disabled)}
</>
);
@@ -110,10 +153,10 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
skipOptionSelected(event: React.ChangeEvent<HTMLSelectElement>): void {
let option: CategorySkipOption;
this.removeCurrentCategorySelection();
switch (event.target.value) {
case "disable":
case "disable":
Config.config.categorySelections = Config.config.categorySelections.filter(
categorySelection => categorySelection.name !== this.props.category);
return;
case "showOverlay":
option = CategorySkipOption.ShowOverlay;
@@ -129,35 +172,25 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
break;
}
Config.config.categorySelections.push({
name: this.props.category,
option: option
});
// Forces the Proxy to send this to the chrome storage API
Config.config.categorySelections = Config.config.categorySelections;
}
/** Removes this category from the config list of category selections */
removeCurrentCategorySelection(): void {
// Remove it if it exists
for (let i = 0; i < Config.config.categorySelections.length; i++) {
if (Config.config.categorySelections[i].name === this.props.category) {
Config.config.categorySelections.splice(i, 1);
// Forces the Proxy to send this to the chrome storage API
Config.config.categorySelections = Config.config.categorySelections;
break;
}
const existingSelection = Config.config.categorySelections.find(selection => selection.name === this.props.category);
if (existingSelection) {
existingSelection.option = option;
} else {
Config.config.categorySelections.push({
name: this.props.category,
option: option
});
}
Config.forceSyncUpdate("categorySelections");
}
getCategorySkipOptions(): JSX.Element[] {
const elements: JSX.Element[] = [];
let optionNames = ["disable", "showOverlay", "manualSkip", "autoSkip"];
if (this.props.category === "exclusive_access") optionNames = ["disable", "showOverlay"];
if (this.props.category === "chapter") optionNames = ["disable", "showOverlay"]
else if (this.props.category === "exclusive_access") optionNames = ["disable", "showOverlay"];
for (const optionName of optionNames) {
elements.push(
@@ -172,6 +205,8 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}
setColorState(event: React.FormEvent<HTMLInputElement>, preview: boolean): void {
clearTimeout(this.setBarColorTimeout);
if (preview) {
this.setState({
previewColor: event.currentTarget.value
@@ -188,7 +223,46 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}
// Make listener get called
Config.config.barTypes = Config.config.barTypes;
this.setBarColorTimeout = setTimeout(() => {
Config.config.barTypes = Config.config.barTypes;
}, 50);
}
getExtraOptionComponents(category: string, extraClasses: string, disabled: boolean): JSX.Element[] {
const result = [];
for (const option of this.getExtraOptions(category)) {
result.push(
<tr key={option.configKey} className={extraClasses}>
<td id={`${category}_${option.configKey}`} className="categoryExtraOptions">
<ToggleOptionComponent
configKey={option.configKey}
label={option.label}
disabled={disabled}
style={{width: "inherit"}}
/>
</td>
</tr>
)
}
return result;
}
getExtraOptions(category: string): ToggleOptionProps[] {
switch (category) {
case "chapter":
return [{
configKey: "renderSegmentsAsChapters",
label: chrome.i18n.getMessage("renderAsChapters"),
}];
case "music_offtopic":
return [{
configKey: "autoSkipOnMusicVideos",
label: chrome.i18n.getMessage("autoSkipOnMusicVideos"),
}];
default:
return [];
}
}
}

View File

@@ -1,9 +1,9 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import Config from "../config";
import { Keybind } from "../types";
import Config from "../../config";
import { Keybind } from "../../types";
import KeybindDialogComponent from "./KeybindDialogComponent";
import { keybindEquals, keybindToString, formatKey } from "../utils/configUtils";
import { keybindEquals, keybindToString, formatKey } from "../../utils/configUtils";
export interface KeybindProps {
option: string;

View File

@@ -1,8 +1,8 @@
import * as React from "react";
import { ChangeEvent } from "react";
import Config from "../config";
import { Keybind } from "../types";
import { keybindEquals, formatKey } from "../utils/configUtils";
import Config from "../../config";
import { Keybind } from "../../types";
import { keybindEquals, formatKey } from "../../utils/configUtils";
export interface KeybindDialogProps {
option: string;

View File

@@ -0,0 +1,57 @@
import * as React from "react";
import Config from "../../config";
export interface ToggleOptionProps {
configKey: string;
label: string;
disabled?: boolean;
style?: React.CSSProperties;
}
export interface ToggleOptionState {
enabled: boolean;
}
class ToggleOptionComponent extends React.Component<ToggleOptionProps, ToggleOptionState> {
constructor(props: ToggleOptionProps) {
super(props);
// Setup state
this.state = {
enabled: Config.config[props.configKey]
}
}
render(): React.ReactElement {
return (
<div>
<div className="switch-container" style={this.props.style}>
<label className="switch">
<input id={this.props.configKey}
type="checkbox"
checked={this.state.enabled}
disabled={this.props.disabled}
onChange={(e) => this.clicked(e)}/>
<span className="slider round"></span>
</label>
<label className="switch-label" htmlFor={this.props.configKey}>
{this.props.label}
</label>
</div>
</div>
);
}
clicked(event: React.ChangeEvent<HTMLInputElement>): void {
Config.config[this.props.configKey] = event.target.checked;
this.setState({
enabled: event.target.checked
});
}
}
export default ToggleOptionComponent;

View File

@@ -0,0 +1,72 @@
import * as React from "react";
import Config from "../../config";
import UnsubmittedVideoListItem from "./UnsubmittedVideoListItem";
export interface UnsubmittedVideoListProps {
}
export interface UnsubmittedVideoListState {
}
class UnsubmittedVideoListComponent extends React.Component<UnsubmittedVideoListProps, UnsubmittedVideoListState> {
constructor(props: UnsubmittedVideoListProps) {
super(props);
// Setup state
this.state = {
};
}
render(): React.ReactElement {
// Render nothing if there are no unsubmitted segments
if (Object.keys(Config.config.unsubmittedSegments).length == 0)
return <></>;
return (
<table id="unsubmittedVideosList"
className="categoryChooserTable"
style={{marginTop: "10px"}} >
<tbody>
{/* Headers */}
<tr id="UnsubmittedVideosListHeader"
className="categoryTableElement categoryTableHeader">
<th id="UnsubmittedVideoID">
{chrome.i18n.getMessage("videoID")}
</th>
<th id="UnsubmittedSegmentCount">
{chrome.i18n.getMessage("segmentCount")}
</th>
<th id="UnsubmittedVideoActions">
{chrome.i18n.getMessage("actions")}
</th>
</tr>
{this.getUnsubmittedVideos()}
</tbody>
</table>
);
}
getUnsubmittedVideos(): JSX.Element[] {
const elements: JSX.Element[] = [];
for (const videoID of Object.keys(Config.config.unsubmittedSegments)) {
elements.push(
<UnsubmittedVideoListItem videoID={videoID} key={videoID}>
</UnsubmittedVideoListItem>
);
}
return elements;
}
}
export default UnsubmittedVideoListComponent;

View File

@@ -0,0 +1,95 @@
import * as React from "react";
import Config from "../../config";
import { exportTimes, exportTimesAsHashParam } from "../../utils/exporter";
export interface UnsubmittedVideosListItemProps {
videoID: string;
}
export interface UnsubmittedVideosListItemState {
}
class UnsubmittedVideoListItem extends React.Component<UnsubmittedVideosListItemProps, UnsubmittedVideosListItemState> {
constructor(props: UnsubmittedVideosListItemProps) {
super(props);
// Setup state
this.state = {
};
}
render(): React.ReactElement {
const segmentCount = Config.config.unsubmittedSegments[this.props.videoID]?.length ?? 0;
return (
<>
<tr id={this.props.videoID + "UnsubmittedSegmentsRow"}
className="categoryTableElement">
<td id={this.props.videoID + "UnsubmittedVideoID"}
className="categoryTableLabel">
<a href={`https://youtu.be/${this.props.videoID}`}
target="_blank" rel="noreferrer">
{this.props.videoID}
</a>
</td>
<td id={this.props.videoID + "UnsubmittedSegmentCount"}>
{segmentCount}
</td>
<td id={this.props.videoID + "UnsubmittedVideoActions"}>
<div id={this.props.videoID + "ExportSegmentsAction"}
className="option-button inline low-profile"
onClick={this.exportSegments.bind(this)}>
{chrome.i18n.getMessage("exportSegments")}
</div>
{" "}
<div id={this.props.videoID + "ExportSegmentsAsURLAction"}
className="option-button inline low-profile"
onClick={this.exportSegmentsAsURL.bind(this)}>
{chrome.i18n.getMessage("exportSegmentsAsURL")}
</div>
{" "}
<div id={this.props.videoID + "ClearSegmentsAction"}
className="option-button inline low-profile"
onClick={this.clearSegments.bind(this)}>
{chrome.i18n.getMessage("clearTimes")}
</div>
</td>
</tr>
</>
);
}
clearSegments(): void {
if (confirm(chrome.i18n.getMessage("clearThis"))) {
delete Config.config.unsubmittedSegments[this.props.videoID];
Config.forceSyncUpdate("unsubmittedSegments");
}
}
exportSegments(): void {
this.copyToClipboard(exportTimes(Config.config.unsubmittedSegments[this.props.videoID]));
}
exportSegmentsAsURL(): void {
this.copyToClipboard(`https://youtube.com/watch?v=${this.props.videoID}${exportTimesAsHashParam(Config.config.unsubmittedSegments[this.props.videoID])}`)
}
copyToClipboard(text: string): void {
navigator.clipboard.writeText(text)
.then(() => {
alert(chrome.i18n.getMessage("CopiedExclamation"));
})
.catch(() => {
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
});
}
}
export default UnsubmittedVideoListItem;

View File

@@ -0,0 +1,55 @@
import * as React from "react";
import Config from "../../config";
import UnsubmittedVideoListComponent from "./UnsubmittedVideoListComponent";
export interface UnsubmittedVideosProps {
}
export interface UnsubmittedVideosState {
tableVisible: boolean,
}
class UnsubmittedVideosComponent extends React.Component<UnsubmittedVideosProps, UnsubmittedVideosState> {
constructor(props: UnsubmittedVideosProps) {
super(props);
this.state = {
tableVisible: false,
};
}
render(): React.ReactElement {
const videoCount = Object.keys(Config.config.unsubmittedSegments).length;
const segmentCount = Object.values(Config.config.unsubmittedSegments).reduce((acc: number, vid: Array<unknown>) => acc + vid.length, 0);
return <>
<div style={{marginBottom: "10px"}}>
{segmentCount == 0 ?
chrome.i18n.getMessage("unsubmittedSegmentCountsZero") :
chrome.i18n.getMessage("unsubmittedSegmentCounts")
.replace("{0}", `${segmentCount} ${chrome.i18n.getMessage("unsubmittedSegments" + (segmentCount == 1 ? "Singular" : "Plural"))}`)
.replace("{1}", `${videoCount} ${chrome.i18n.getMessage("videos" + (videoCount == 1 ? "Singular" : "Plural"))}`)
}
</div>
{videoCount > 0 && <div className="option-button inline" onClick={() => this.setState({tableVisible: !this.state.tableVisible})}>
{chrome.i18n.getMessage(this.state.tableVisible ? "hideUnsubmittedSegments" : "showUnsubmittedSegments")}
</div>}
{" "}
<div className="option-button inline" onClick={this.clearAllSegments}>
{chrome.i18n.getMessage("clearUnsubmittedSegments")}
</div>
{this.state.tableVisible && <UnsubmittedVideoListComponent/>}
</>;
}
clearAllSegments(): void {
if (confirm(chrome.i18n.getMessage("clearUnsubmittedSegmentsConfirm")))
Config.config.unsubmittedSegments = {};
}
}
export default UnsubmittedVideosComponent;

View File

@@ -1,14 +1,20 @@
import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
import { keybindEquals } from "./utils/configUtils";
export interface Permission {
canSubmit: boolean;
}
interface SBConfig {
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,
@@ -19,6 +25,7 @@ interface SBConfig {
disableSkipping: boolean,
muteSegments: boolean,
fullVideoSegments: boolean,
manualSkipOnFullVideo: boolean,
trackViewCount: boolean,
trackViewCountInPrivate: boolean,
trackDownvotes: boolean,
@@ -44,6 +51,7 @@ interface SBConfig {
allowExpirements: boolean,
showDonationLink: boolean,
showPopupDonationCount: number,
showUpsells: boolean,
donateClicked: number,
autoHideInfoButton: boolean,
autoSkipOnMusicVideos: boolean,
@@ -55,6 +63,8 @@ interface SBConfig {
scrollToEditTimeUpdate: boolean,
categoryPillUpdate: boolean,
darkMode: boolean,
showCategoryGuidelines: boolean,
chaptersAvailable: boolean,
// Used to cache calculated text color info
categoryPillColors: {
@@ -67,10 +77,19 @@ interface SBConfig {
skipKeybind: Keybind,
startSponsorKeybind: Keybind,
submitKeybind: Keybind,
nextChapterKeybind: Keybind,
previousChapterKeybind: Keybind,
// What categories should be skipped
categorySelections: CategorySelection[],
payments: {
licenseKey: string,
lastCheck: number,
freeAccess: boolean,
chaptersAllowed: boolean
}
// Preview bar
barTypes: {
"preview-chooseACategory": PreviewBarOption,
@@ -101,9 +120,11 @@ export type VideoDownvotes = { segments: { uuid: HashedValue, hidden: SponsorHid
interface SBStorage {
/* VideoID prefixes to UUID prefixes */
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>,
navigationApiAvailable: boolean,
}
export interface SBObject {
configLocalListeners: Array<(changes: StorageChangesObject) => unknown>;
configSyncListeners: Array<(changes: StorageChangesObject) => unknown>;
syncDefaults: SBConfig;
localDefaults: SBStorage;
@@ -113,18 +134,22 @@ export interface SBObject {
local: SBStorage;
forceSyncUpdate(prop: string): void;
forceLocalUpdate(prop: string): void;
resetToDefault(): void;
}
const Config: SBObject = {
/**
* Callback function when an option is updated
*/
configLocalListeners: [],
configSyncListeners: [],
syncDefaults: {
userID: null,
isVip: false,
permissions: {},
unsubmittedSegments: {},
defaultCategory: "chooseACategory" as Category,
renderSegmentsAsChapters: false,
whitelistedChannels: [],
forceChannelCheck: false,
minutesSaved: 0,
@@ -135,6 +160,7 @@ const Config: SBObject = {
disableSkipping: false,
muteSegments: true,
fullVideoSegments: true,
manualSkipOnFullVideo: false,
trackViewCount: true,
trackViewCountInPrivate: true,
trackDownvotes: true,
@@ -160,12 +186,15 @@ const Config: SBObject = {
allowExpirements: true,
showDonationLink: true,
showPopupDonationCount: 0,
showUpsells: true,
donateClicked: 0,
autoHideInfoButton: true,
autoSkipOnMusicVideos: false,
scrollToEditTimeUpdate: false, // false means the tooltip will be shown
categoryPillUpdate: false,
darkMode: true,
showCategoryGuidelines: true,
chaptersAvailable: true,
categoryPillColors: {},
@@ -179,6 +208,8 @@ const Config: SBObject = {
skipKeybind: {key: "Enter"},
startSponsorKeybind: {key: ";"},
submitKeybind: {key: "'"},
nextChapterKeybind: {key: "]"},
previousChapterKeybind: {key: "["},
categorySelections: [{
name: "sponsor" as Category,
@@ -191,6 +222,13 @@ const Config: SBObject = {
option: CategorySkipOption.ShowOverlay
}],
payments: {
licenseKey: null,
lastCheck: 0,
freeAccess: false,
chaptersAllowed: false
},
colorPalette: {
red: "#780303",
white: "#ffffff",
@@ -282,14 +320,16 @@ const Config: SBObject = {
}
},
localDefaults: {
downvotedSegments: {}
downvotedSegments: {},
navigationApiAvailable: null
},
cachedSyncConfig: null,
cachedLocalStorage: null,
config: null,
local: null,
forceSyncUpdate,
forceLocalUpdate
forceLocalUpdate,
resetToDefault
};
// Function setup
@@ -300,7 +340,7 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
for (const key in changes) {
Config.cachedSyncConfig[key] = changes[key].newValue;
}
for (const callback of Config.configSyncListeners) {
callback(changes);
}
@@ -308,9 +348,13 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
for (const key in changes) {
Config.cachedLocalStorage[key] = changes[key].newValue;
}
for (const callback of Config.configLocalListeners) {
callback(changes);
}
}
});
const syncHandler: ProxyHandler<SBConfig> = {
set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) {
Config.cachedSyncConfig[prop] = value;
@@ -327,10 +371,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data;
},
deleteProperty(obj: SBConfig, prop: keyof SBConfig) {
chrome.storage.sync.remove(<string> prop);
return true;
}
@@ -352,10 +396,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data;
},
deleteProperty(obj: SBStorage, prop: keyof SBStorage) {
chrome.storage.local.remove(<string> prop);
return true;
}
@@ -368,8 +412,20 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
}
function forceSyncUpdate(prop: string): void {
const value = Config.cachedSyncConfig[prop];
if (prop === "unsubmittedSegments") {
// Early to be safe
if (JSON.stringify(value).length + prop.length > 8000) {
for (const key in value) {
if (!value[key] || value[key].length <= 0) {
delete value[key];
}
}
}
}
chrome.storage.sync.set({
[prop]: Config.cachedSyncConfig[prop]
[prop]: value
});
}
@@ -379,7 +435,7 @@ function forceLocalUpdate(prop: string): void {
});
}
async function fetchConfig(): Promise<void> {
async function fetchConfig(): Promise<void> {
await Promise.all([new Promise<void>((resolve) => {
chrome.storage.sync.get(null, function(items) {
Config.cachedSyncConfig = <SBConfig> <unknown> items;
@@ -387,7 +443,7 @@ async function fetchConfig(): Promise<void> {
});
}), new Promise<void>((resolve) => {
chrome.storage.local.get(null, function(items) {
Config.cachedLocalStorage = <SBStorage> <unknown> items;
Config.cachedLocalStorage = <SBStorage> <unknown> items;
resolve();
});
})]);
@@ -431,9 +487,9 @@ function migrateOldSyncFormats(config: SBConfig) {
if (!config["autoSkipOnMusicVideosUpdate"]) {
config["autoSkipOnMusicVideosUpdate"] = true;
for (const selection of config.categorySelections) {
if (selection.name === "music_offtopic"
if (selection.name === "music_offtopic"
&& selection.option === CategorySkipOption.AutoSkip) {
config.autoSkipOnMusicVideos = true;
break;
}
@@ -492,6 +548,8 @@ function migrateOldSyncFormats(config: SBConfig) {
}
async function setupConfig() {
if (typeof(chrome) === "undefined") return;
await fetchConfig();
addDefaults();
const config = configProxy();
@@ -522,6 +580,16 @@ function addDefaults() {
}
}
function resetToDefault() {
chrome.storage.sync.set({
...Config.syncDefaults,
userID: Config.config.userID,
minutesSaved: Config.config.minutesSaved,
skipCount: Config.config.skipCount,
sponsorTimesContributed: Config.config.sponsorTimesContributed
});
}
// Sync config
setupConfig();

File diff suppressed because it is too large Load Diff

93
src/document.ts Normal file
View File

@@ -0,0 +1,93 @@
/*
Content script are run in an isolated DOM so it is not possible to access some key details that are sanitized when passed cross-dom
This script is used to get the details from the page and make them available for the content script by being injected directly into the page
*/
import { PageType } from "./types";
interface StartMessage {
type: "navigation",
pageType: PageType
videoID: string | null,
}
interface FinishMessage extends StartMessage {
channelID: string,
channelTitle: string
}
interface AdMessage {
type: "ad",
playing: boolean
}
interface VideoData {
type: "data",
videoID: string,
isLive: boolean,
isPremiere: boolean
}
type WindowMessage = StartMessage | FinishMessage | AdMessage | VideoData;
// global playerClient - too difficult to type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let playerClient: any;
const sendMessage = (message: WindowMessage): void => {
window.postMessage({ source: "sponsorblock", ...message }, "/");
}
function setupPlayerClient(e: CustomEvent): void {
if (playerClient) return; // early exit if already defined
playerClient = e.detail;
sendVideoData(); // send playerData after setup
e.detail.addEventListener('onAdStart', () => sendMessage({ type: "ad", playing: true } as AdMessage));
e.detail.addEventListener('onAdFinish', () => sendMessage({ type: "ad", playing: false } as AdMessage));
}
document.addEventListener("yt-player-updated", setupPlayerClient);
document.addEventListener("yt-navigate-start", navigationStartSend);
document.addEventListener("yt-navigate-finish", navigateFinishSend);
function navigationParser(event: CustomEvent): StartMessage {
const pageType: PageType = event.detail.pageType;
if (pageType) {
const result: StartMessage = { type: "navigation", pageType, videoID: null };
if (pageType === "shorts" || pageType === "watch") {
const endpoint = event.detail.endpoint
if (!endpoint) return null;
result.videoID = (pageType === "shorts" ? endpoint.reelWatchEndpoint : endpoint.watchEndpoint).videoId;
}
return result;
} else {
return null;
}
}
function navigationStartSend(event: CustomEvent): void {
const message = navigationParser(event) as StartMessage;
if (message) {
sendMessage(message);
}
}
function navigateFinishSend(event: CustomEvent): void {
sendVideoData(); // arrived at new video, send video data
const videoDetails = event.detail?.response?.playerResponse?.videoDetails;
if (videoDetails) {
sendMessage({ channelID: videoDetails.channelId, channelTitle: videoDetails.author, ...navigationParser(event) } as FinishMessage);
}
}
function sendVideoData(): void {
if (!playerClient) return;
const videoData = playerClient.getVideoData();
if (videoData) {
sendMessage({ type: "data", videoID: videoData.video_id, isLive: videoData.isLive, isPremiere: videoData.isPremiere } as VideoData);
}
}

View File

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

View File

@@ -1,47 +0,0 @@
import Config from "../config";
import Utils from "../utils";
const utils = new Utils();
export interface ChatConfig {
displayName: string,
composerInitialValue?: string,
customDescription?: string
}
export function openChat(config: ChatConfig): void {
const chat = document.createElement("div");
chat.classList.add("sbChatNotice");
chat.style.zIndex = "2000";
const iframe= document.createElement("iframe");
iframe.src = "https://chat.sponsor.ajay.app/#" + utils.objectToURI("", config, false);
chat.appendChild(iframe);
const closeButton = document.createElement("img");
closeButton.classList.add("sbChatClose");
closeButton.src = chrome.extension.getURL("icons/close.png");
closeButton.addEventListener("click", () => {
chat.remove();
closeButton.remove();
});
chat.appendChild(closeButton);
const referenceNode = utils.findReferenceNode();
referenceNode.prepend(chat);
}
export async function openWarningChat(warningMessage: string): Promise<void> {
const warningReasonMatch = warningMessage.match(/Warning reason: '(.+)'/);
alert(chrome.i18n.getMessage("warningChatInfo") + `\n\n${warningReasonMatch ? ` Warning reason: ${warningReasonMatch[1]}` : ``}`);
const userNameData = await utils.asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID);
const userName = userNameData.ok ? JSON.parse(userNameData.responseText).userName : "";
const publicUserID = await utils.getHash(Config.config.userID);
openChat({
displayName: `${userName ? userName : ``}${userName !== publicUserID ? ` | ${publicUserID}` : ``}`,
composerInitialValue: `I got a warning and confirm I [REMOVE THIS CAPITAL TEXT TO CONFIRM] reread the guidelines.` +
warningReasonMatch ? ` Warning reason: ${warningReasonMatch[1]}` : ``,
customDescription: chrome.i18n.getMessage("warningChatInfo")
});
}

View File

@@ -6,41 +6,71 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd
'use strict';
import Config from "../config";
import { ActionType } from "../types";
import Utils from "../utils";
const utils = new Utils();
import { ChapterVote } from "../render/ChapterVote";
import { ActionType, Category, SegmentContainer, SponsorHideType, SponsorSourceType, SponsorTime } from "../types";
import { partition } from "../utils/arrayUtils";
import { shortCategoryName } from "../utils/categoryUtils";
import { GenericUtils } from "../utils/genericUtils";
import { findValidElement } from "../utils/pageUtils";
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003;
export interface PreviewBarSegment {
segment: [number, number];
category: string;
unsubmitted: boolean;
category: Category;
actionType: ActionType;
unsubmitted: boolean;
showLarger: boolean;
description: string;
source: SponsorSourceType;
requiredSegment?: boolean;
}
interface ChapterGroup extends SegmentContainer {
originalDuration: number;
actionType: ActionType;
}
class PreviewBar {
container: HTMLUListElement;
categoryTooltip?: HTMLDivElement;
tooltipContainer?: HTMLElement;
categoryTooltipContainer?: HTMLElement;
chapterTooltip?: HTMLDivElement;
parent: HTMLElement;
onMobileYouTube: boolean;
onInvidious: boolean;
progressBar: HTMLElement;
segments: PreviewBarSegment[] = [];
existingChapters: PreviewBarSegment[] = [];
videoDuration = 0;
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) {
// For chapter bar
hoveredSection: HTMLElement;
customChaptersBar: HTMLElement;
chaptersBarSegments: PreviewBarSegment[];
chapterVote: ChapterVote;
originalChapterBar: HTMLElement;
originalChapterBarBlocks: NodeListOf<HTMLElement>;
chapterMargin: number;
unfilteredChapterGroups: ChapterGroup[];
chapterGroups: ChapterGroup[];
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, chapterVote: ChapterVote, test=false) {
if (test) return;
this.container = document.createElement('ul');
this.container.id = 'previewbar';
this.parent = parent;
this.onMobileYouTube = onMobileYouTube;
this.onInvidious = onInvidious;
this.chapterVote = chapterVote;
this.updatePageElements();
this.createElement(parent);
this.createChapterMutationObservers();
this.setupHoverText();
}
@@ -48,19 +78,26 @@ class PreviewBar {
setupHoverText(): void {
if (this.onMobileYouTube || this.onInvidious) return;
// delete old ones
document.querySelectorAll(`.sponsorCategoryTooltip`)
.forEach((e) => e.remove());
// Create label placeholder
this.categoryTooltip = document.createElement("div");
this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
this.chapterTooltip = document.createElement("div");
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper");
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
// Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init
this.tooltipContainer = tooltipTextWrapper.parentElement;
this.categoryTooltipContainer = tooltipTextWrapper.parentElement;
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title");
if (!this.tooltipContainer || !titleTooltip) return;
if (!this.categoryTooltipContainer || !titleTooltip) return;
tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
tooltipTextWrapper.insertBefore(this.chapterTooltip, titleTooltip.nextSibling);
const seekBar = document.querySelector(".ytp-progress-bar-container");
if (!seekBar) return;
@@ -76,10 +113,10 @@ class PreviewBar {
});
const observer = new MutationObserver((mutations) => {
if (!mouseOnSeekBar || !this.categoryTooltip || !this.tooltipContainer) return;
if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer) return;
// If the mutation observed is only for our tooltip text, ignore
if (mutations.length === 1 && (mutations[0].target as HTMLElement).classList.contains("sponsorCategoryTooltip")) {
if (mutations.some((mutation) => (mutation.target as HTMLElement).classList.contains("sponsorCategoryTooltip"))) {
return;
}
@@ -93,7 +130,7 @@ class PreviewBar {
const tooltipText = tooltipTextElement.textContent;
if (tooltipText === null || tooltipText.length === 0) continue;
timeInSeconds = utils.getFormattedTimeToSeconds(tooltipText);
timeInSeconds = GenericUtils.getFormattedTimeToSeconds(tooltipText);
if (timeInSeconds !== null) break;
}
@@ -101,36 +138,32 @@ class PreviewBar {
if (timeInSeconds === null) return;
// Find the segment at that location, using the shortest if multiple found
let segment: PreviewBarSegment | null = null;
let currentSegmentLength = Infinity;
for (const seg of this.segments) {//
const segmentLength = seg.segment[1] - seg.segment[0];
const minSize = this.getMinimumSize(seg.showLarger);
const startTime = segmentLength !== 0 ? seg.segment[0] : Math.floor(seg.segment[0]);
const endTime = segmentLength > minSize ? seg.segment[1] : Math.ceil(seg.segment[0] + minSize);
if (startTime <= timeInSeconds && endTime >= timeInSeconds) {
if (segmentLength < currentSegmentLength) {
currentSegmentLength = segmentLength;
segment = seg;
}
}
const [normalSegments, chapterSegments] =
partition(this.segments.filter((s) => s.source !== SponsorSourceType.YouTube),
(segment) => segment.actionType !== ActionType.Chapter);
let mainSegment = this.getSmallestSegment(timeInSeconds, normalSegments);
let secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments);
if (mainSegment === null && secondarySegment !== null) {
mainSegment = secondarySegment;
secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments.filter((s) => s !== secondarySegment));
}
if (segment === null && this.tooltipContainer.classList.contains(TOOLTIP_VISIBLE_CLASS)) {
this.tooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
} else if (segment !== null) {
this.tooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (segment.unsubmitted) {
this.categoryTooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + utils.shortCategoryName(segment.category);
if (mainSegment === null && secondarySegment === null) {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
} else {
this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (mainSegment !== null && secondarySegment !== null) {
this.categoryTooltipContainer.classList.add("sponsorTwoTooltips");
} else {
this.categoryTooltip.textContent = utils.shortCategoryName(segment.category);
this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips");
}
// Use the class if the timestamp text uses it to prevent overlapping
this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip);
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
}
});
@@ -140,6 +173,21 @@ class PreviewBar {
});
}
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
if (segment) {
const name = segment.description || shortCategoryName(segment.category);
if (segment.unsubmitted) {
tooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + name;
} else {
tooltip.textContent = name;
}
tooltip.style.removeProperty("display");
} else {
tooltip.style.display = "none";
}
}
createElement(parent: HTMLElement): void {
this.parent = parent;
@@ -148,50 +196,94 @@ class PreviewBar {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
}
this.container.style.transform = "none";
} else if (!this.onInvidious) {
// Hover listener
this.parent.addEventListener("mouseenter", () => this.container.classList.add("hovered"));
this.parent.addEventListener("mouseleave", () => this.container.classList.remove("hovered"));
this.container.classList.add("sbNotInvidious");
}
// On the seek bar
this.parent.prepend(this.container);
}
clear(): void {
this.videoDuration = 0;
this.segments = [];
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
}
set(segments: PreviewBarSegment[], videoDuration: number): void {
this.segments = segments ?? [];
this.videoDuration = videoDuration ?? 0;
this.updatePageElements();
// Sometimes video duration is inaccurate, pull from accessibility info
const ariaDuration = parseInt(this.progressBar?.getAttribute('aria-valuemax')) ?? 0;
if (ariaDuration && Math.abs(ariaDuration - this.videoDuration) > 3) {
this.videoDuration = ariaDuration;
}
this.update();
}
private updatePageElements(): void {
const allProgressBars = document.querySelectorAll('.ytp-progress-bar') as NodeListOf<HTMLElement>;
this.progressBar = findValidElement(allProgressBars) ?? allProgressBars?.[0];
const newChapterBar = this.progressBar.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
if (this.originalChapterBar !== newChapterBar) {
// Make sure changes are undone on old bar
this.originalChapterBar?.style?.removeProperty("display");
this.originalChapterBar = newChapterBar;
}
}
private update(): void {
this.clear();
if (!segments) return;
if (!this.segments) return;
this.segments = segments;
this.videoDuration = videoDuration;
this.chapterMargin = 2;
if (this.originalChapterBar) {
this.originalChapterBarBlocks = this.originalChapterBar.querySelectorAll(":scope > div") as NodeListOf<HTMLElement>
this.existingChapters = this.segments.filter((s) => s.source === SponsorSourceType.YouTube).sort((a, b) => a.segment[0] - b.segment[0]);
if (this.existingChapters?.length > 0) {
const margin = parseFloat(this.originalChapterBarBlocks?.[0]?.style?.marginRight?.replace("px", ""));
if (margin) this.chapterMargin = margin;
}
}
this.segments.sort(({segment: a}, {segment: b}) => {
const sortedSegments = this.segments.sort(({ segment: a }, { segment: b }) => {
// Sort longer segments before short segments to make shorter segments render later
return (b[1] - b[0]) - (a[1] - a[0]);
}).forEach((segment) => {
});
for (const segment of sortedSegments) {
const bar = this.createBar(segment);
this.container.appendChild(bar);
});
}
this.createChaptersBar(this.segments.sort((a, b) => a.segment[0] - b.segment[0]));
const chapterChevron = this.getChapterChevron();
if (chapterChevron) {
if (this.segments.some((segment) => segment.actionType !== ActionType.Chapter
&& segment.source === SponsorSourceType.YouTube)) {
chapterChevron.style.removeProperty("display");
} else {
chapterChevron.style.display = "none";
}
}
}
createBar({category, unsubmitted, segment, showLarger}: PreviewBarSegment): HTMLLIElement {
createBar(barSegment: PreviewBarSegment): HTMLLIElement {
const { category, unsubmitted, segment, showLarger } = barSegment;
const bar = document.createElement('li');
bar.classList.add('previewbar');
if (barSegment.requiredSegment) bar.classList.add("requiredSegment");
bar.innerHTML = showLarger ? '&nbsp;&nbsp;' : '&nbsp;';
const fullCategoryName = (unsubmitted ? 'preview-' : '') + category;
@@ -202,7 +294,10 @@ class PreviewBar {
bar.style.position = "absolute";
const duration = Math.min(segment[1], this.videoDuration) - segment[0];
if (duration > 0) bar.style.width = this.timeToPercentage(duration);
if (duration > 0) {
bar.style.width = `calc(${this.intervalToPercentage(segment[0], segment[1])}${
this.chapterFilter(barSegment) && segment[1] < this.videoDuration ? ` - ${this.chapterMargin}px` : ''})`;
}
const time = segment[1] ? Math.min(this.videoDuration, segment[0]) : segment[0];
bar.style.left = this.timeToPercentage(time);
@@ -210,6 +305,464 @@ class PreviewBar {
return bar;
}
createChaptersBar(segments: PreviewBarSegment[]): void {
if (!this.progressBar || !this.originalChapterBar || this.originalChapterBar.childElementCount <= 0) {
if (this.customChaptersBar) this.customChaptersBar.style.display = "none";
return;
}
// Merge overlapping chapters
this.unfilteredChapterGroups = this.createChapterRenderGroups(segments);
if (segments.every((segments) => segments.source === SponsorSourceType.YouTube)
|| (!Config.config.renderSegmentsAsChapters
&& segments.every((segment) => segment.actionType !== ActionType.Chapter
|| segment.source === SponsorSourceType.YouTube))
|| this.chapterGroups?.length <= 0) {
if (this.customChaptersBar) this.customChaptersBar.style.display = "none";
this.originalChapterBar.style.removeProperty("display");
return;
}
const filteredSegments = segments?.filter((segment) => this.chapterFilter(segment));
if (filteredSegments) {
let groups = this.unfilteredChapterGroups;
if (filteredSegments.length !== segments.length) {
groups = this.createChapterRenderGroups(filteredSegments);
}
this.chapterGroups = groups.filter((segment) => this.chapterGroupFilter(segment));
if (groups.length !== this.chapterGroups.length) {
// Fix missing sections due to filtered segments
for (let i = 1; i < this.chapterGroups.length; i++) {
if (this.chapterGroups[i].segment[0] !== this.chapterGroups[i - 1].segment[1]) {
this.chapterGroups[i - 1].segment[1] = this.chapterGroups[i].segment[0]
}
}
}
} else {
this.chapterGroups = this.unfilteredChapterGroups;
}
// Create it from cloning
let createFromScratch = false;
if (!this.customChaptersBar || !this.progressBar.contains(this.customChaptersBar)) {
// Clear anything remaining
document.querySelectorAll(".sponsorBlockChapterBar").forEach((element) => element.remove());
createFromScratch = true;
this.customChaptersBar = this.originalChapterBar.cloneNode(true) as HTMLElement;
this.customChaptersBar.classList.add("sponsorBlockChapterBar");
}
this.customChaptersBar.style.display = "none";
const originalSections = this.customChaptersBar.querySelectorAll(".ytp-chapter-hover-container");
const originalSection = originalSections[0];
// For switching to a video with less chapters
if (originalSections.length > this.chapterGroups.length) {
for (let i = originalSections.length - 1; i >= this.chapterGroups.length; i--) {
this.customChaptersBar.removeChild(originalSections[i]);
}
}
// Modify it to have sections for each segment
for (let i = 0; i < this.chapterGroups.length; i++) {
const chapter = this.chapterGroups[i].segment;
let newSection = originalSections[i] as HTMLElement;
if (!newSection) {
newSection = originalSection.cloneNode(true) as HTMLElement;
this.firstTimeSetupChapterSection(newSection);
this.customChaptersBar.appendChild(newSection);
} else if (createFromScratch) {
this.firstTimeSetupChapterSection(newSection);
}
this.setupChapterSection(newSection, chapter[0], chapter[1], i !== this.chapterGroups.length - 1);
}
// Hide old bar
this.originalChapterBar.style.display = "none";
this.customChaptersBar.style.removeProperty("display");
if (createFromScratch) {
if (this.container?.parentElement === this.progressBar) {
this.progressBar.insertBefore(this.customChaptersBar, this.container.nextSibling);
} else {
this.progressBar.prepend(this.customChaptersBar);
}
}
this.updateChapterAllMutation(this.originalChapterBar, this.progressBar, true);
}
createChapterRenderGroups(segments: PreviewBarSegment[]): ChapterGroup[] {
const result: ChapterGroup[] = [];
segments?.forEach((segment, index) => {
const latestChapter = result[result.length - 1];
if (latestChapter && latestChapter.segment[1] > segment.segment[0]) {
const segmentDuration = segment.segment[1] - segment.segment[0];
if (segment.segment[0] < latestChapter.segment[0]
|| segmentDuration < latestChapter.originalDuration) {
// Remove latest if it starts too late
let latestValidChapter = latestChapter;
const chaptersToAddBack: ChapterGroup[] = []
while (latestValidChapter?.segment[0] >= segment.segment[0]) {
const invalidChapter = result.pop();
if (invalidChapter.segment[1] > segment.segment[1]) {
if (invalidChapter.segment[0] === segment.segment[0]) {
invalidChapter.segment[0] = segment.segment[1];
}
chaptersToAddBack.push(invalidChapter);
}
latestValidChapter = result[result.length - 1];
}
const priorityActionType = this.getActionTypePrioritized([segment.actionType, latestChapter?.actionType]);
// Split the latest chapter if smaller
result.push({
segment: [segment.segment[0], segment.segment[1]],
originalDuration: segmentDuration,
actionType: priorityActionType
});
if (latestValidChapter?.segment[1] > segment.segment[1]) {
result.push({
segment: [segment.segment[1], latestValidChapter.segment[1]],
originalDuration: latestValidChapter.originalDuration,
actionType: latestValidChapter.actionType
});
}
chaptersToAddBack.reverse();
let lastChapterChecked: number[] = segment.segment;
for (const chapter of chaptersToAddBack) {
if (chapter.segment[0] < lastChapterChecked[1]) {
chapter.segment[0] = lastChapterChecked[1];
}
lastChapterChecked = chapter.segment;
}
result.push(...chaptersToAddBack);
if (latestValidChapter) latestValidChapter.segment[1] = segment.segment[0];
} else {
// Start at end of old one otherwise
result.push({
segment: [latestChapter.segment[1], segment.segment[1]],
originalDuration: segmentDuration,
actionType: segment.actionType
});
}
} else {
// Add empty buffer before segment if needed
const lastTime = latestChapter?.segment[1] || 0;
if (segment.segment[0] > lastTime) {
result.push({
segment: [lastTime, segment.segment[0]],
originalDuration: 0,
actionType: null
});
}
// Normal case
const endTime = Math.min(segment.segment[1], this.videoDuration);
result.push({
segment: [segment.segment[0], endTime],
originalDuration: endTime - segment.segment[0],
actionType: segment.actionType
});
}
// Add empty buffer after segment if needed
if (index === segments.length - 1) {
const nextSegment = segments[index + 1];
const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration;
const lastTime = result[result.length - 1]?.segment[1] || segment.segment[1];
if (this.intervalToDecimal(lastTime, nextTime) > MIN_CHAPTER_SIZE) {
result.push({
segment: [lastTime, nextTime],
originalDuration: 0,
actionType: null
});
}
}
});
return result;
}
private getActionTypePrioritized(actionTypes: ActionType[]): ActionType {
if (actionTypes.includes(ActionType.Skip)) {
return ActionType.Skip;
} else if (actionTypes.includes(ActionType.Mute)) {
return ActionType.Mute;
} else {
return actionTypes.find(a => a) ?? actionTypes[0];
}
}
private setupChapterSection(section: HTMLElement, startTime: number, endTime: number, addMargin: boolean): void {
const sizePercent = this.intervalToPercentage(startTime, endTime);
if (addMargin) {
section.style.marginRight = `${this.chapterMargin}px`;
section.style.width = `calc(${sizePercent} - ${this.chapterMargin}px)`;
} else {
section.style.marginRight = "0";
section.style.width = sizePercent;
}
section.setAttribute("decimal-width", String(this.intervalToDecimal(startTime, endTime)));
}
private firstTimeSetupChapterSection(section: HTMLElement): void {
section.addEventListener("mouseenter", () => {
this.hoveredSection?.classList.remove("ytp-exp-chapter-hover-effect");
section.classList.add("ytp-exp-chapter-hover-effect");
this.hoveredSection = section;
});
}
private createChapterMutationObservers(): void {
if (!this.progressBar || !this.originalChapterBar) return;
const attributeObserver = new MutationObserver((mutations) => {
const changes: Record<string, HTMLElement> = {};
for (const mutation of mutations) {
const currentElement = mutation.target as HTMLElement;
if (mutation.type === "attributes"
&& currentElement.parentElement?.classList.contains("ytp-progress-list")) {
changes[currentElement.classList[0]] = mutation.target as HTMLElement;
}
}
this.updateChapterMutation(changes, this.progressBar);
});
attributeObserver.observe(this.originalChapterBar, {
subtree: true,
attributes: true,
attributeFilter: ["style", "class"]
});
const childListObserver = new MutationObserver((mutations) => {
const changes: Record<string, HTMLElement> = {};
for (const mutation of mutations) {
if (mutation.type === "childList") {
this.update();
}
}
this.updateChapterMutation(changes, this.progressBar);
});
// Only direct children, no subtree
childListObserver.observe(this.originalChapterBar, {
childList: true
});
}
private updateChapterAllMutation(originalChapterBar: HTMLElement, progressBar: HTMLElement, firstUpdate = false): void {
const elements = originalChapterBar.querySelectorAll(".ytp-progress-list > *");
const changes: Record<string, HTMLElement> = {};
for (const element of elements) {
changes[element.classList[0]] = element as HTMLElement;
}
this.updateChapterMutation(changes, progressBar, firstUpdate);
}
private updateChapterMutation(changes: Record<string, HTMLElement>, progressBar: HTMLElement, firstUpdate = false): void {
// Go through each newly generated chapter bar and update the width based on changes array
if (this.customChaptersBar) {
// Width reached so far in decimal percent
let cursor = 0;
const sections = this.customChaptersBar.querySelectorAll(".ytp-chapter-hover-container") as NodeListOf<HTMLElement>;
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
const sectionWidthDecimal = parseFloat(section.getAttribute("decimal-width"));
const sectionWidthDecimalNoMargin = sectionWidthDecimal - this.chapterMargin / progressBar.clientWidth;
for (const className in changes) {
const selector = `.${className}`
const customChangedElement = section.querySelector(selector) as HTMLElement;
if (customChangedElement) {
const fullSectionWidth = i === sections.length - 1 ? sectionWidthDecimal : sectionWidthDecimalNoMargin;
const changedElement = changes[className];
const changedData = this.findLeftAndScale(selector, changedElement, progressBar);
const left = (changedData.left) / progressBar.clientWidth;
const calculatedLeft = Math.max(0, Math.min(1, (left - cursor) / fullSectionWidth));
if (!isNaN(left) && !isNaN(calculatedLeft)) {
customChangedElement.style.left = `${calculatedLeft * 100}%`;
customChangedElement.style.removeProperty("display");
}
if (changedData.scale !== null) {
const transformScale = (changedData.scale) / progressBar.clientWidth;
customChangedElement.style.transform =
`scaleX(${Math.max(0, Math.min(1 - calculatedLeft, (transformScale - cursor) / fullSectionWidth - calculatedLeft))}`;
if (firstUpdate) {
customChangedElement.style.transition = "none";
setTimeout(() => customChangedElement.style.removeProperty("transition"), 50);
}
}
if (customChangedElement.className !== changedElement.className) {
customChangedElement.className = changedElement.className;
}
}
}
cursor += sectionWidthDecimal;
}
}
}
private findLeftAndScale(selector: string, currentElement: HTMLElement, progressBar: HTMLElement):
{ left: number, scale: number } {
const sections = currentElement.parentElement.parentElement.parentElement.children;
let currentWidth = 0;
let lastWidth = 0;
let left = 0;
let leftPosition = 0;
let scale = null;
let scalePosition = 0;
let scaleWidth = 0;
let lastScalePosition = 0;
for (let i = 0; i < sections.length; i++) {
const section = sections[i] as HTMLElement;
const checkElement = section.querySelector(selector) as HTMLElement;
const currentSectionWidthNoMargin = this.getPartialChapterSectionStyle(section, "width") || progressBar.clientWidth;
const currentSectionWidth = currentSectionWidthNoMargin
+ this.getPartialChapterSectionStyle(section, "marginRight");
// First check for left
const checkLeft = parseFloat(checkElement.style.left.replace("px", ""));
if (checkLeft !== 0) {
left = checkLeft;
leftPosition = currentWidth;
}
// Then check for scale
const transformMatch = checkElement.style.transform.match(/scaleX\(([0-9.]+?)\)/);
if (transformMatch) {
const transformScale = parseFloat(transformMatch[1]);
const endPosition = transformScale + checkLeft / currentSectionWidthNoMargin;
if (lastScalePosition > 0.99999 && endPosition === 0) {
// Last one was an end section that was fully filled
scalePosition = currentWidth - lastWidth;
break;
}
lastScalePosition = endPosition;
scale = transformScale;
scaleWidth = currentSectionWidthNoMargin;
if ((i === sections.length - 1 || endPosition < 0.99999) && endPosition > 0) {
// reached the end of this section for sure
// if the scale is always zero, then it will go through all sections but still return 0
scalePosition = currentWidth;
if (checkLeft !== 0) {
scalePosition += left;
}
break;
}
}
lastWidth = currentSectionWidth;
currentWidth += lastWidth;
}
return {
left: left + leftPosition,
scale: scale !== null ? scale * scaleWidth + scalePosition : null
};
}
private getPartialChapterSectionStyle(element: HTMLElement, param: string): number {
const data = element.style[param];
if (data?.includes("100%")) {
return 0;
} else {
return parseInt(element.style[param].match(/\d+/g)?.[0]) || 0;
}
}
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): void {
if (!segments && submittingSegments?.length <= 0) return;
segments ??= [];
if (submittingSegments?.length > 0) segments = segments.concat(submittingSegments);
const activeSegments = segments.filter((segment) => {
return segment.hidden === SponsorHideType.Visible
&& segment.segment[0] <= currentTime && segment.segment[1] > currentTime;
});
this.setActiveSegments(activeSegments);
}
/**
* Adds the text to the chapters slot if not filled by default
*/
private setActiveSegments(segments: SponsorTime[]): void {
const chaptersContainer = document.querySelector(".ytp-chapter-container") as HTMLDivElement;
if (chaptersContainer) {
if (segments.length > 0) {
chaptersContainer.style.removeProperty("display");
const chosenSegment = segments.sort((a, b) => {
if (a.actionType === ActionType.Chapter && b.actionType !== ActionType.Chapter) {
return -1;
} else if (a.actionType !== ActionType.Chapter && b.actionType === ActionType.Chapter) {
return 1;
} else {
return (b.segment[0] - a.segment[0]);
}
})[0];
const chapterButton = chaptersContainer.querySelector("button.ytp-chapter-title") as HTMLButtonElement;
chapterButton.classList.remove("ytp-chapter-container-disabled");
chapterButton.disabled = false;
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
chapterTitle.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category);
const chapterVoteContainer = this.chapterVote.getContainer();
if (chosenSegment.source === SponsorSourceType.Server) {
if (!chapterButton.contains(chapterVoteContainer)) {
const oldVoteContainers = document.querySelectorAll("#chapterVote");
if (oldVoteContainers.length > 0) {
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
}
chapterButton.insertBefore(chapterVoteContainer, this.getChapterChevron());
}
this.chapterVote.setVisibility(true);
this.chapterVote.setSegment(chosenSegment);
} else {
this.chapterVote.setVisibility(false);
}
} else {
chaptersContainer.style.display = "none";
this.chapterVote.setVisibility(false);
}
}
}
remove(): void {
this.container.remove();
@@ -218,14 +771,66 @@ class PreviewBar {
this.categoryTooltip = undefined;
}
if (this.tooltipContainer) {
this.tooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
this.tooltipContainer = undefined;
if (this.categoryTooltipContainer) {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
this.categoryTooltipContainer = undefined;
}
}
private chapterFilter(segment: PreviewBarSegment): boolean {
return (Config.config.renderSegmentsAsChapters || segment.actionType === ActionType.Chapter)
&& segment.actionType !== ActionType.Poi
&& this.chapterGroupFilter(segment);
}
private chapterGroupFilter(segment: SegmentContainer): boolean {
return segment.segment.length === 2 && this.intervalToDecimal(segment.segment[0], segment.segment[1]) > MIN_CHAPTER_SIZE;
}
intervalToPercentage(startTime: number, endTime: number) {
return `${this.intervalToDecimal(startTime, endTime) * 100}%`;
}
intervalToDecimal(startTime: number, endTime: number) {
return (this.timeToDecimal(endTime) - this.timeToDecimal(startTime));
}
timeToPercentage(time: number): string {
return Math.min(100, time / this.videoDuration * 100) + '%';
return `${this.timeToDecimal(time) * 100}%`
}
timeToDecimal(time: number): number {
if (this.originalChapterBarBlocks?.length > 1 && this.existingChapters.length === this.originalChapterBarBlocks?.length) {
// Parent element to still work when display: none
const totalPixels = this.originalChapterBar.parentElement.clientWidth;
let pixelOffset = 0;
let lastCheckedChapter = -1;
for (let i = 0; i < this.originalChapterBarBlocks.length; i++) {
const chapterElement = this.originalChapterBarBlocks[i];
const widthPixels = parseFloat(chapterElement.style.width.replace("px", ""));
if (time >= this.existingChapters[i].segment[1]) {
const marginPixels = chapterElement.style.marginRight ? parseFloat(chapterElement.style.marginRight.replace("px", "")) : 0;
pixelOffset += widthPixels + marginPixels;
lastCheckedChapter = i;
} else {
break;
}
}
// The next chapter is the one we are currently inside of
const latestChapter = this.existingChapters[lastCheckedChapter + 1];
if (latestChapter) {
const latestWidth = parseFloat(this.originalChapterBarBlocks[lastCheckedChapter + 1].style.width.replace("px", ""));
const latestChapterDuration = latestChapter.segment[1] - latestChapter.segment[0];
const percentageInCurrentChapter = (time - latestChapter.segment[0]) / latestChapterDuration;
const sizeOfCurrentChapter = latestWidth / totalPixels;
return Math.min(1, ((pixelOffset / totalPixels) + (percentageInCurrentChapter * sizeOfCurrentChapter)));
}
}
return Math.min(1, time / this.videoDuration);
}
/*
@@ -234,6 +839,31 @@ class PreviewBar {
getMinimumSize(showLarger = false): number {
return this.videoDuration * (showLarger ? 0.006 : 0.003);
}
private getSmallestSegment(timeInSeconds: number, segments: PreviewBarSegment[]): PreviewBarSegment | null {
let segment: PreviewBarSegment | null = null;
let currentSegmentLength = Infinity;
for (const seg of segments) { //
const segmentLength = seg.segment[1] - seg.segment[0];
const minSize = this.getMinimumSize(seg.showLarger);
const startTime = segmentLength !== 0 ? seg.segment[0] : Math.floor(seg.segment[0]);
const endTime = segmentLength > minSize ? seg.segment[1] : Math.ceil(seg.segment[0] + minSize);
if (startTime <= timeInSeconds && endTime >= timeInSeconds) {
if (segmentLength < currentSegmentLength) {
currentSegmentLength = segmentLength;
segment = seg;
}
}
}
return segment;
}
private getChapterChevron(): HTMLElement {
return document.querySelector(".ytp-chapter-title-chevron");
}
}
export default PreviewBar;

View File

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

View File

@@ -16,7 +16,8 @@ interface DefaultMessage {
| "getChannelID"
| "isChannelWhitelisted"
| "submitTimes"
| "refreshSegments";
| "refreshSegments"
| "closePopup";
}
interface BoolValueMessage {
@@ -29,6 +30,11 @@ interface IsInfoFoundMessage {
updating: boolean;
}
interface SkipMessage {
message: "unskip" | "reskip";
UUID: SegmentUUID;
}
interface SubmitVoteMessage {
message: "submitVote";
type: number;
@@ -41,11 +47,35 @@ interface HideSegmentMessage {
UUID: SegmentUUID;
}
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SubmitVoteMessage | HideSegmentMessage);
interface CopyToClipboardMessage {
message: "copyToClipboard";
text: string;
}
interface ImportSegmentsMessage {
message: "importSegments";
data: string;
}
interface KeyDownMessage {
message: "keydown";
key: string;
keyCode: number;
code: string;
which: number;
shiftKey: boolean;
ctrlKey: boolean;
altKey: boolean;
metaKey: boolean;
}
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SkipMessage | SubmitVoteMessage | HideSegmentMessage | CopyToClipboardMessage | ImportSegmentsMessage | KeyDownMessage);
export interface IsInfoFoundMessageResponse {
found: boolean;
status: number;
sponsorTimes: SponsorTime[];
time: number;
onMobileYouTube: boolean;
}
@@ -71,11 +101,23 @@ export type MessageResponse =
| GetChannelIDResponse
| SponsorStartResponse
| IsChannelWhitelistedResponse
| Record<string, never>
| VoteResponse;
| Record<string, never> // empty object response {}
| VoteResponse
| ImportSegmentsResponse;
export interface VoteResponse {
successType: number;
statusCode: number;
responseText: string;
}
}
export interface ImportSegmentsResponse {
importedSegments: SponsorTime[];
}
export interface TimeUpdateMessage {
message: "time";
time: number;
}
export type PopupMessage = TimeUpdateMessage;

View File

@@ -10,15 +10,21 @@ window.SB = Config;
import Utils from "./utils";
import CategoryChooser from "./render/CategoryChooser";
import KeybindComponent from "./components/KeybindComponent";
import UnsubmittedVideos from "./render/UnsubmittedVideos";
import KeybindComponent from "./components/options/KeybindComponent";
import { showDonationLink } from "./utils/configUtils";
import { localizeHtmlPage } from "./utils/pageUtils";
import { StorageChangesObject } from "./types";
const utils = new Utils();
let embed = false;
const categoryChoosers: CategoryChooser[] = [];
const unsubmittedVideos: UnsubmittedVideos[] = [];
window.addEventListener('DOMContentLoaded', init);
async function init() {
utils.localizeHtmlPage();
localizeHtmlPage();
// selected tab
if (location.hash != "") {
@@ -102,7 +108,7 @@ async function init() {
// Add click listener
checkbox.addEventListener("click", async () => {
// Confirm if required
if (confirmMessage && ((confirmOnTrue && checkbox.checked) || (!confirmOnTrue && !checkbox.checked))
if (confirmMessage && ((confirmOnTrue && checkbox.checked) || (!confirmOnTrue && !checkbox.checked))
&& !confirm(chrome.i18n.getMessage(confirmMessage))){
checkbox.checked = !checkbox.checked;
return;
@@ -119,7 +125,7 @@ async function init() {
if (!checkbox.checked) {
// Enable the notice
Config.config["dontShowNotice"] = false;
const showNoticeSwitch = <HTMLInputElement> document.querySelector("[data-sync='dontShowNotice'] > div > label > input");
showNoticeSwitch.checked = true;
}
@@ -161,7 +167,7 @@ async function init() {
}
case "text-change": {
const textChangeInput = <HTMLInputElement> optionsElements[i].querySelector(".option-text-box");
const textChangeSetButton = <HTMLElement> optionsElements[i].querySelector(".text-change-set");
textChangeInput.value = Config.config[option];
@@ -232,12 +238,22 @@ async function init() {
}
case "button-press": {
const actionButton = optionsElements[i].querySelector(".trigger-button");
const confirmMessage = optionsElements[i].getAttribute("data-confirm-message");
switch(optionsElements[i].getAttribute("data-sync")) {
case "copyDebugInformation":
actionButton.addEventListener("click", copyDebugOutputToClipboard);
break;
}
actionButton.addEventListener("click", () => {
if (confirmMessage !== null && !confirm(chrome.i18n.getMessage(confirmMessage))) {
return;
}
switch (optionsElements[i].getAttribute("data-sync")) {
case "copyDebugInformation":
copyDebugOutputToClipboard();
break;
case "resetToDefault":
Config.resetToDefault();
window.location.reload();
break;
}
});
break;
}
@@ -279,7 +295,10 @@ async function init() {
break;
}
case "react-CategoryChooserComponent":
new CategoryChooser(optionsElements[i]);
categoryChoosers.push(new CategoryChooser(optionsElements[i]));
break;
case "react-UnsubmittedVideosComponent":
unsubmittedVideos.push(new UnsubmittedVideos(optionsElements[i]));
break;
}
}
@@ -327,8 +346,8 @@ function createStickyHeader() {
/**
* Handle special cases where an option shouldn't show
*
* @param {String} element
*
* @param {String} element
*/
async function shouldHideOption(element: Element): Promise<boolean> {
return (element.getAttribute("data-private-only") === "true" && !(await isIncognitoAllowed()))
@@ -337,10 +356,8 @@ async function shouldHideOption(element: Element): Promise<boolean> {
/**
* Called when the config is updated
*
* @param {String} element
*/
function optionsConfigUpdateListener() {
function optionsConfigUpdateListener(changes: StorageChangesObject) {
const optionsContainer = document.getElementById("options");
const optionsElements = optionsContainer.querySelectorAll("*");
@@ -348,14 +365,25 @@ function optionsConfigUpdateListener() {
switch (optionsElements[i].getAttribute("data-type")) {
case "display":
updateDisplayElement(<HTMLElement> optionsElements[i])
break;
}
}
if (changes.categorySelections || changes.payments) {
for (const chooser of categoryChoosers) {
chooser.update();
}
} else if (changes.unsubmittedSegments) {
for (const chooser of unsubmittedVideos) {
chooser.update();
}
}
}
/**
* Will set display elements to the proper text
*
* @param element
*
* @param element
*/
function updateDisplayElement(element: HTMLElement) {
const displayOption = element.getAttribute("data-sync")
@@ -382,9 +410,9 @@ function updateDisplayElement(element: HTMLElement) {
/**
* Initializes the option to add Invidious instances
*
* @param element
* @param option
*
* @param element
* @param option
*/
function invidiousInstanceAddInit(element: HTMLElement, option: string) {
const textBox = <HTMLInputElement> element.querySelector(".option-text-box");
@@ -436,18 +464,12 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
/**
* Run when the invidious button is being initialized
*
* @param checkbox
* @param option
*
* @param checkbox
* @param option
*/
function invidiousInit(checkbox: HTMLInputElement, option: string) {
let permissions = ["declarativeContent"];
if (utils.isFirefox()) permissions = [];
chrome.permissions.contains({
origins: utils.getPermissionRegex(),
permissions: permissions
}, function (result) {
utils.containsInvidiousPermission().then((result) => {
if (result != checkbox.checked) {
Config.config[option] = result;
@@ -458,33 +480,19 @@ function invidiousInit(checkbox: HTMLInputElement, option: string) {
/**
* Run whenever the invidious checkbox is clicked
*
* @param checkbox
* @param option
*
* @param checkbox
* @param option
*/
async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Promise<void> {
return new Promise((resolve) => {
if (checkbox.checked) {
utils.setupExtraSitePermissions(function (granted) {
if (!granted) {
Config.config[option] = false;
checkbox.checked = false;
} else {
checkbox.checked = true;
}
resolve();
});
} else {
utils.removeExtraSiteRegistration();
}
});
const enabled = await utils.applyInvidiousPermissions(checkbox.checked, option);
checkbox.checked = enabled;
}
/**
* Will trigger the textbox to appear to be able to change an option's text.
*
* @param element
*
* @param element
*/
function activatePrivateTextChange(element: HTMLElement) {
const button = element.querySelector(".trigger-button");
@@ -501,7 +509,7 @@ function activatePrivateTextChange(element: HTMLElement) {
element.querySelector(".option-hidden-section").classList.remove("hidden");
return;
}
let result = Config.config[option];
// See if anything extra must be done
switch (option) {
@@ -512,7 +520,7 @@ function activatePrivateTextChange(element: HTMLElement) {
}
textBox.value = result;
const setButton = element.querySelector(".text-change-set");
setButton.addEventListener("click", async () => {
setTextOption(option, element, textBox.value);
@@ -541,7 +549,7 @@ function activatePrivateTextChange(element: HTMLElement) {
/**
* Function to run when a textbox change is submitted
*
*
* @param option data-sync value
* @param element main container div
* @param value new text
@@ -551,7 +559,7 @@ async function setTextOption(option: string, element: HTMLElement, value: string
const confirmMessage = element.getAttribute("data-confirm-message");
if (confirmMessage === null || confirm(chrome.i18n.getMessage(confirmMessage))) {
// See if anything extra must be done
switch (option) {
case "*":
@@ -563,13 +571,13 @@ async function setTextOption(option: string, element: HTMLElement, value: string
if (newConfig.supportInvidious) {
const checkbox = <HTMLInputElement> document.querySelector("#support-invidious > div > label > input");
checkbox.checked = true;
await invidiousOnClick(checkbox, "supportInvidious");
}
window.location.reload();
} catch (e) {
alert(chrome.i18n.getMessage("incorrectlyFormattedOptions"));
}
@@ -587,8 +595,9 @@ async function setTextOption(option: string, element: HTMLElement, value: string
function downloadConfig() {
const file = document.createElement("a");
const jsonData = JSON.parse(JSON.stringify(Config.cachedSyncConfig));
file.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData)));
file.setAttribute("download", "SponsorBlockConfig.json");
const dateTimeString = new Date().toJSON().replace("T", "_").replace(/:/g, ".").replace(/.\d+Z/g, "")
file.setAttribute("href", `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(jsonData))}`);
file.setAttribute("download", `SponsorBlockConfig_${dateTimeString}.json`);
document.body.append(file);
file.click();
file.remove();
@@ -611,7 +620,7 @@ function uploadConfig(e) {
/**
* Validates the value used for the database server address.
* Returns null and alerts the user if there is an issue.
*
*
* @param input Input server address
*/
function validateServerAddress(input: string): string {
@@ -645,7 +654,7 @@ function copyDebugOutputToClipboard() {
// Sanitise sensitive user config values
delete output.config.userID;
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
? "Default server address" : "Custom server address";
output.config.invidiousInstances = output.config.invidiousInstances.length;
output.config.whitelistedChannels = output.config.whitelistedChannels.length;

View File

@@ -1,5 +1,6 @@
import Config from "./config";
import Utils from "./utils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils();
// This is needed, if Config is not imported before Utils, things break.
@@ -9,27 +10,19 @@ Config.config;
window.addEventListener('DOMContentLoaded', init);
async function init() {
utils.localizeHtmlPage();
const domains = document.location.hash.replace("#", "").split(",");
localizeHtmlPage();
const acceptButton = document.getElementById("acceptPermissionButton");
acceptButton.addEventListener("click", () => {
chrome.permissions.request({
origins: utils.getPermissionRegex(domains),
permissions: []
}, (granted) => {
if (granted) {
utils.applyInvidiousPermissions(Config.config.supportInvidious).then((enabled) => {
Config.config.supportInvidious = enabled;
if (enabled) {
alert(chrome.i18n.getMessage("permissionRequestSuccess"));
Config.config.ytInfoPermissionGranted = true;
chrome.tabs.getCurrent((tab) => {
chrome.tabs.remove(tab.id);
});
window.close();
} else {
alert(chrome.i18n.getMessage("permissionRequestFailed"));
}
});
})
});
}

View File

@@ -1,11 +1,15 @@
import Config from "./config";
import Utils from "./utils";
import { SponsorTime, SponsorHideType, ActionType } from "./types";
import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageTypes";
import { SponsorTime, SponsorHideType, ActionType, SegmentUUID, SponsorSourceType, StorageChangesObject } from "./types";
import { Message, MessageResponse, IsInfoFoundMessageResponse, ImportSegmentsResponse, PopupMessage } from "./messageTypes";
import { showDonationLink } from "./utils/configUtils";
import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils";
import { shortCategoryName } from "./utils/categoryUtils";
import { localizeHtmlPage } from "./utils/pageUtils";
import { exportTimes } from "./utils/exporter";
import GenericNotice from "./render/GenericNotice";
const utils = new Utils();
interface MessageListener {
@@ -22,13 +26,15 @@ class MessageHandler {
sendMessage(id: number, request: Message, callback?) {
if (this.messageListener) {
this.messageListener(request, null, callback);
} else {
} else if (chrome.tabs) {
chrome.tabs.sendMessage(id, request, callback);
} else {
chrome.runtime.sendMessage({ message: "tabs", data: request }, callback);
}
}
query(config, callback) {
if (this.messageListener) {
if (this.messageListener || !chrome.tabs) {
// Send back dummy info
callback([{
url: document.URL,
@@ -41,15 +47,17 @@ class MessageHandler {
}
}
// To prevent clickjacking
let allowPopup = window === window.top;
window.addEventListener("message", async (e) => {
if (e.source !== window.parent) return;
if (e.origin.endsWith('.youtube.com')) return allowPopup = true;
});
//make this a function to allow this to run on the content page
async function runThePopup(messageListener?: MessageListener): Promise<void> {
const messageHandler = new MessageHandler(messageListener);
utils.localizeHtmlPage();
await utils.wait(() => Config.config !== null);
localizeHtmlPage();
type InputPageElements = {
whitelistToggle?: HTMLInputElement,
@@ -58,9 +66,27 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
};
type PageElements = { [key: string]: HTMLElement } & InputPageElements
/** If true, the content script is in the process of creating a new segment. */
let creatingSegment = false;
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
let downloadedTimes: SponsorTime[] = [];
//current video ID of this tab
let currentVideoID = null;
enum SegmentTab {
Segments,
Chapters
}
let segmentTab = SegmentTab.Segments;
let port: chrome.runtime.Port = null;
const PageElements: PageElements = {};
[
"sponsorBlockPopupBody",
"sponsorblockPopup",
"sponsorStart",
// Top toggles
@@ -92,7 +118,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
// Username
"setUsernameContainer",
"setUsernameButton",
"setUsernameStatusContainer",
"setUsernameStatus",
"setUsernameStatus",
"setUsername",
"usernameInput",
@@ -100,7 +126,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"submitUsername",
"sbPopupIconCopyUserID",
// More
"submissionSection",
"submissionHint",
"mainControls",
"loadingIndicator",
"videoFound",
@@ -109,12 +135,41 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"refreshSegmentsButton",
"whitelistButton",
"sbDonate",
"issueReporterTabs",
"issueReporterTabSegments",
"issueReporterTabChapters",
"sponsorTimesDonateContainer",
"sbConsiderDonateLink",
"sbCloseDonate",
"sbBetaServerWarning"
"sbBetaServerWarning",
"sbCloseButton",
"issueReporterImportExport",
"importSegmentsButton",
"exportSegmentsButton",
"importSegmentsMenu",
"importSegmentsText",
"importSegmentsSubmit"
].forEach(id => PageElements[id] = document.getElementById(id));
getSegmentsFromContentScript(false);
await utils.wait(() => Config.config !== null && allowPopup, 5000, 5);
PageElements.sponsorBlockPopupBody.style.removeProperty("visibility");
if (!Config.configSyncListeners.includes(contentConfigUpdateListener)) {
Config.configSyncListeners.push(contentConfigUpdateListener);
}
PageElements.sbCloseButton.addEventListener("click", () => {
sendTabMessage({
message: "closePopup"
});
});
if (window !== window.top) {
PageElements.sbCloseButton.classList.remove("hidden");
PageElements.sponsorBlockPopupBody.classList.add("is-embedded");
}
// Hide donate button if wanted (Safari, or user choice)
if (!showDonationLink()) {
PageElements.sbDonate.style.display = "none";
@@ -128,7 +183,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
});
}
//setup click listeners
PageElements.exportSegmentsButton.addEventListener("click", exportSegments);
PageElements.importSegmentsButton.addEventListener("click",
() => PageElements.importSegmentsMenu.classList.toggle("hidden"));
PageElements.importSegmentsSubmit.addEventListener("click", importSegments);
PageElements.sponsorStart.addEventListener("click", sendSponsorStartMessage);
PageElements.whitelistToggle.addEventListener("change", function () {
if (this.checked) {
@@ -149,16 +208,39 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.optionsButton.addEventListener("click", openOptions);
PageElements.helpButton.addEventListener("click", openHelp);
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => navigator.clipboard.writeText(await utils.getHash(Config.config.userID)));
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => copyToClipboard(await utils.getHash(Config.config.userID)));
/** If true, the content script is in the process of creating a new segment. */
let creatingSegment = false;
// Forward click events
if (window !== window.top) {
document.addEventListener("keydown", (e) => {
const target = e.target as HTMLElement;
if (target.tagName === "INPUT"
|| target.tagName === "TEXTAREA"
|| e.key === "ArrowUp"
|| e.key === "ArrowDown") {
return;
}
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
if (e.key === " ") {
// No scrolling
e.preventDefault();
}
//current video ID of this tab
let currentVideoID = null;
sendTabMessage({
message: "keydown",
key: e.key,
keyCode: e.keyCode,
code: e.code,
which: e.which,
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
metaKey: e.metaKey
});
});
}
setupComPort();
//show proper disable skipping button
const disableSkipping = Config.config.disableSkipping;
@@ -175,7 +257,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "unset";
}
utils.sendRequestToServer("GET", "/api/userInfo?value=userName&value=viewCount&value=minutesSaved&value=vip&userID=" + Config.config.userID, (res) => {
utils.sendRequestToServer("GET", "/api/userInfo?value=userName&value=viewCount&value=minutesSaved&value=vip&value=permissions&value=freeChaptersAccess&userID="
+ Config.config.userID, (res) => {
if (res.status === 200) {
const userInfo = JSON.parse(res.responseText);
PageElements.usernameValue.innerText = userInfo.userName;
@@ -202,8 +285,16 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
PageElements.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
}
Config.config.isVip = userInfo.vip;
Config.config.permissions = userInfo.permissions;
if (userInfo.freeChaptersAccess) {
Config.config.payments.chaptersAllowed = userInfo.freeChaptersAccess;
Config.config.payments.freeAccess = userInfo.freeChaptersAccess;
Config.config.payments.lastCheck = Date.now();
Config.forceSyncUpdate("payments");
}
}
});
@@ -239,7 +330,21 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
// Must be delayed so it only happens once loaded
setTimeout(() => PageElements.sponsorblockPopup.classList.remove("preload"), 250);
getSegmentsFromContentScript(false);
PageElements.issueReporterTabSegments.addEventListener("click", () => {
PageElements.issueReporterTabSegments.classList.add("sbSelected");
PageElements.issueReporterTabChapters.classList.remove("sbSelected");
segmentTab = SegmentTab.Segments;
getSegmentsFromContentScript(true);
});
PageElements.issueReporterTabChapters.addEventListener("click", () => {
PageElements.issueReporterTabSegments.classList.remove("sbSelected");
PageElements.issueReporterTabChapters.classList.add("sbSelected");
segmentTab = SegmentTab.Chapters;
getSegmentsFromContentScript(true);
});
function showDonateWidget(viewCount: number) {
if (Config.config.showDonationLink && Config.config.donateClicked <= 0 && Config.config.showPopupDonationCount < 5
@@ -272,13 +377,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
});
}
function loadTabData(tabs, updating: boolean): void {
async function loadTabData(tabs, updating: boolean): Promise<void> {
if (!currentVideoID) {
//this isn't a YouTube video then
displayNoVideo();
return;
}
await utils.wait(() => Config.config !== null, 5000, 10);
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
updateSegmentEditingUI();
@@ -311,12 +417,17 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.whitelistButton.classList.remove("hidden");
PageElements.loadingIndicator.style.display = "none";
downloadedTimes = request.sponsorTimes ?? [];
if (request.found) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
displayDownloadedSponsorTimes(request);
} else {
if (request.sponsorTimes) {
displayDownloadedSponsorTimes(request.sponsorTimes, request.time);
}
} else if (request.status == 404 || request.status == 200) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
} else {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("connectionError") + request.status;
}
}
@@ -385,156 +496,205 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
//display the video times from the array at the top, in a different section
function displayDownloadedSponsorTimes(request: { found: boolean, sponsorTimes: SponsorTime[] }) {
if (request.sponsorTimes != undefined) {
// Sort list by start time
const segmentTimes = request.sponsorTimes
.sort((a, b) => a.segment[1] - b.segment[1])
.sort((a, b) => a.segment[0] - b.segment[0]);
function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) {
let currentSegmentTab = segmentTab;
if (!sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter && segment.source !== SponsorSourceType.YouTube)) {
PageElements.issueReporterTabs.classList.add("hidden");
currentSegmentTab = SegmentTab.Segments;
} else {
PageElements.issueReporterTabs.classList.remove("hidden");
}
//add them as buttons to the issue reporting container
const container = document.getElementById("issueReporterTimeButtons");
while (container.firstChild) {
container.removeChild(container.firstChild);
// Sort list by start time
const downloadedTimes = sponsorTimes
.filter((segment) => {
if (currentSegmentTab === SegmentTab.Segments) {
return segment.actionType !== ActionType.Chapter;
} else if (currentSegmentTab === SegmentTab.Chapters) {
return segment.actionType === ActionType.Chapter
&& segment.source !== SponsorSourceType.YouTube;
} else {
return true;
}
})
.sort((a, b) => a.segment[1] - b.segment[1])
.sort((a, b) => a.segment[0] - b.segment[0]);
//add them as buttons to the issue reporting container
const container = document.getElementById("issueReporterTimeButtons");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
if (downloadedTimes.length > 0) {
PageElements.exportSegmentsButton.classList.remove("hidden");
} else {
PageElements.exportSegmentsButton.classList.add("hidden");
}
const isVip = Config.config.isVip;
for (let i = 0; i < downloadedTimes.length; i++) {
const UUID = downloadedTimes[i].UUID;
const locked = downloadedTimes[i].locked;
const category = downloadedTimes[i].category;
const actionType = downloadedTimes[i].actionType;
const segmentSummary = document.createElement("summary");
segmentSummary.classList.add("segmentSummary");
if (time >= downloadedTimes[i].segment[0]) {
if (time < downloadedTimes[i].segment[1]) {
segmentSummary.classList.add("segmentActive");
} else {
segmentSummary.classList.add("segmentPassed");
}
}
const isVip = Config.config.isVip;
for (let i = 0; i < segmentTimes.length; i++) {
const UUID = segmentTimes[i].UUID;
const locked = segmentTimes[i].locked;
const categoryColorCircle = document.createElement("span");
categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID;
categoryColorCircle.style.backgroundColor = Config.config.barTypes[category]?.color;
categoryColorCircle.classList.add("dot");
categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle");
const segmentSummary = document.createElement("summary");
segmentSummary.className = "segmentSummary";
let extraInfo = "";
if (downloadedTimes[i].hidden === SponsorHideType.Downvoted) {
//this one is downvoted
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")";
} else if (downloadedTimes[i].hidden === SponsorHideType.MinimumDuration) {
//this one is too short
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
} else if (downloadedTimes[i].hidden === SponsorHideType.Hidden) {
extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
}
const categoryColorCircle = document.createElement("span");
categoryColorCircle.id = "sponsorTimesCategoryColorCircle" + UUID;
categoryColorCircle.style.backgroundColor = Config.config.barTypes[segmentTimes[i].category]?.color;
categoryColorCircle.classList.add("dot");
categoryColorCircle.classList.add("sponsorTimesCategoryColorCircle");
const name = downloadedTimes[i].description || shortCategoryName(category);
const textNode = document.createTextNode(name + extraInfo);
const segmentTimeFromToNode = document.createElement("div");
if (downloadedTimes[i].actionType === ActionType.Full) {
segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full");
} else {
segmentTimeFromToNode.innerText = GenericUtils.getFormattedTime(downloadedTimes[i].segment[0], true) +
(actionType !== ActionType.Poi
? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(downloadedTimes[i].segment[1], true)
: "");
}
let extraInfo = "";
if (segmentTimes[i].hidden === SponsorHideType.Downvoted) {
//this one is downvoted
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")";
} else if (segmentTimes[i].hidden === SponsorHideType.MinimumDuration) {
//this one is too short
extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")";
} else if (segmentTimes[i].hidden === SponsorHideType.Hidden) {
extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
}
segmentTimeFromToNode.style.margin = "5px";
const textNode = document.createTextNode(utils.shortCategoryName(segmentTimes[i].category) + extraInfo);
const segmentTimeFromToNode = document.createElement("div");
if (segmentTimes[i].actionType === ActionType.Full) {
segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full");
} else {
segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) +
(segmentTimes[i].actionType !== ActionType.Poi
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true)
: "");
}
// for inline-styling purposes
const labelContainer = document.createElement("div");
if (actionType !== ActionType.Chapter) labelContainer.appendChild(categoryColorCircle);
segmentTimeFromToNode.style.margin = "5px";
const span = document.createElement('span');
span.className = "summaryLabel";
span.appendChild(textNode);
labelContainer.appendChild(span);
segmentSummary.appendChild(categoryColorCircle);
segmentSummary.appendChild(textNode);
segmentSummary.appendChild(segmentTimeFromToNode);
segmentSummary.appendChild(labelContainer);
segmentSummary.appendChild(segmentTimeFromToNode);
const votingButtons = document.createElement("details");
votingButtons.classList.add("votingButtons");
const votingButtons = document.createElement("details");
votingButtons.classList.add("votingButtons");
//thumbs up and down buttons
const voteButtonsContainer = document.createElement("div");
voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID;
voteButtonsContainer.setAttribute("align", "center");
//thumbs up and down buttons
const voteButtonsContainer = document.createElement("div");
voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + UUID;
voteButtonsContainer.classList.add("sbVoteButtonsContainer");
const upvoteButton = document.createElement("img");
upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID;
upvoteButton.className = "voteButton";
upvoteButton.title = chrome.i18n.getMessage("upvote");
upvoteButton.src = chrome.runtime.getURL("icons/thumbs_up.svg");
upvoteButton.addEventListener("click", () => vote(1, UUID));
const upvoteButton = document.createElement("img");
upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID;
upvoteButton.className = "voteButton";
upvoteButton.title = chrome.i18n.getMessage("upvote");
upvoteButton.src = chrome.runtime.getURL("icons/thumbs_up.svg");
upvoteButton.addEventListener("click", () => vote(1, UUID));
const downvoteButton = document.createElement("img");
downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + UUID;
downvoteButton.className = "voteButton";
downvoteButton.title = chrome.i18n.getMessage("downvote");
downvoteButton.src = locked && isVip ? chrome.runtime.getURL("icons/thumbs_down_locked.svg") : chrome.runtime.getURL("icons/thumbs_down.svg");
downvoteButton.addEventListener("click", () => vote(0, UUID));
const downvoteButton = document.createElement("img");
downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + UUID;
downvoteButton.className = "voteButton";
downvoteButton.title = chrome.i18n.getMessage("downvote");
downvoteButton.src = locked && isVip ? chrome.runtime.getURL("icons/thumbs_down_locked.svg") : chrome.runtime.getURL("icons/thumbs_down.svg");
downvoteButton.addEventListener("click", () => vote(0, UUID));
const uuidButton = document.createElement("img");
uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
uuidButton.className = "voteButton";
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
uuidButton.title = chrome.i18n.getMessage("copySegmentID");
uuidButton.addEventListener("click", () => {
navigator.clipboard.writeText(UUID);
const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3);
stopAnimation();
});
const uuidButton = document.createElement("img");
uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
uuidButton.className = "voteButton";
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
uuidButton.title = chrome.i18n.getMessage("copySegmentID");
uuidButton.addEventListener("click", () => {
copyToClipboard(UUID);
const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3);
stopAnimation();
});
const hideButton = document.createElement("img");
hideButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
hideButton.className = "voteButton";
hideButton.title = chrome.i18n.getMessage("hideSegment");
if (segmentTimes[i].hidden === SponsorHideType.Hidden) {
hideButton.src = chrome.runtime.getURL("icons/not_visible.svg");
} else {
const hideButton = document.createElement("img");
hideButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
hideButton.className = "voteButton";
hideButton.title = chrome.i18n.getMessage("hideSegment");
if (downloadedTimes[i].hidden === SponsorHideType.Hidden) {
hideButton.src = chrome.runtime.getURL("icons/not_visible.svg");
} else {
hideButton.src = chrome.runtime.getURL("icons/visible.svg");
}
hideButton.addEventListener("click", () => {
const stopAnimation = AnimationUtils.applyLoadingAnimation(hideButton, 0.4);
stopAnimation();
if (downloadedTimes[i].hidden === SponsorHideType.Hidden) {
hideButton.src = chrome.runtime.getURL("icons/visible.svg");
downloadedTimes[i].hidden = SponsorHideType.Visible;
} else {
hideButton.src = chrome.runtime.getURL("icons/not_visible.svg");
downloadedTimes[i].hidden = SponsorHideType.Hidden;
}
hideButton.addEventListener("click", () => {
const stopAnimation = AnimationUtils.applyLoadingAnimation(hideButton, 0.4);
stopAnimation();
if (segmentTimes[i].hidden === SponsorHideType.Hidden) {
hideButton.src = chrome.runtime.getURL("icons/visible.svg");
segmentTimes[i].hidden = SponsorHideType.Visible;
} else {
hideButton.src = chrome.runtime.getURL("icons/not_visible.svg");
segmentTimes[i].hidden = SponsorHideType.Hidden;
}
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{
message: "hideSegment",
type: segmentTimes[i].hidden,
UUID: UUID
}
);
});
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{
message: "hideSegment",
type: downloadedTimes[i].hidden,
UUID: UUID
}
);
});
});
//add thumbs up, thumbs down and uuid copy buttons to the container
voteButtonsContainer.appendChild(upvoteButton);
voteButtonsContainer.appendChild(downvoteButton);
voteButtonsContainer.appendChild(uuidButton);
if (segmentTimes[i].actionType === ActionType.Skip
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(segmentTimes[i].hidden)) {
voteButtonsContainer.appendChild(hideButton);
}
const skipButton = document.createElement("img");
skipButton.id = "sponsorTimesSkipButtonContainer" + UUID;
skipButton.className = "voteButton";
skipButton.src = chrome.runtime.getURL("icons/skip.svg");
skipButton.addEventListener("click", () => skipSegment(actionType, UUID, skipButton));
votingButtons.addEventListener("dblclick", () => skipSegment(actionType, UUID));
// Will contain request status
const voteStatusContainer = document.createElement("div");
voteStatusContainer.id = "sponsorTimesVoteStatusContainer" + UUID;
voteStatusContainer.classList.add("sponsorTimesVoteStatusContainer");
voteStatusContainer.style.display = "none";
const thanksForVotingText = document.createElement("div");
thanksForVotingText.id = "sponsorTimesThanksForVotingText" + UUID;
thanksForVotingText.classList.add("sponsorTimesThanksForVotingText");
voteStatusContainer.appendChild(thanksForVotingText);
votingButtons.append(segmentSummary);
votingButtons.append(voteButtonsContainer);
votingButtons.append(voteStatusContainer);
container.appendChild(votingButtons);
//add thumbs up, thumbs down and uuid copy buttons to the container
voteButtonsContainer.appendChild(upvoteButton);
voteButtonsContainer.appendChild(downvoteButton);
voteButtonsContainer.appendChild(uuidButton);
if (downloadedTimes[i].actionType === ActionType.Skip || downloadedTimes[i].actionType === ActionType.Mute
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) {
voteButtonsContainer.appendChild(hideButton);
}
voteButtonsContainer.appendChild(skipButton);
// Will contain request status
const voteStatusContainer = document.createElement("div");
voteStatusContainer.id = "sponsorTimesVoteStatusContainer" + UUID;
voteStatusContainer.classList.add("sponsorTimesVoteStatusContainer");
voteStatusContainer.style.display = "none";
const thanksForVotingText = document.createElement("div");
thanksForVotingText.id = "sponsorTimesThanksForVotingText" + UUID;
thanksForVotingText.classList.add("sponsorTimesThanksForVotingText");
voteStatusContainer.appendChild(thanksForVotingText);
votingButtons.append(segmentSummary);
votingButtons.append(voteButtonsContainer);
votingButtons.append(voteStatusContainer);
container.appendChild(votingButtons);
}
}
@@ -562,7 +722,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
function updateSegmentEditingUI() {
PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart");
PageElements.submissionSection.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
PageElements.submitTimes.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
PageElements.submissionHint.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
}
//make the options div visible
@@ -578,6 +739,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)
);
}
);
});
}
//make the options username setting option visible
function setUsernameButton() {
PageElements.usernameInput.value = PageElements.usernameValue.innerText;
@@ -589,7 +766,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.setUsername.style.display = "flex";
PageElements.setUsername.classList.add("SBExpanded");
PageElements.setUsernameStatusContainer.style.display = "none";
PageElements.setUsernameStatus.style.display = "none";
PageElements.sponsorTimesContributionsContainer.classList.add("hidden");
}
@@ -597,7 +774,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//submit the new username
function submitUsername() {
//add loading indicator
PageElements.setUsernameStatusContainer.style.display = "unset";
PageElements.setUsernameStatus.style.display = "unset";
PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("Loading");
utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) {
@@ -610,7 +787,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.setUsername.classList.remove("SBExpanded");
PageElements.usernameValue.innerText = PageElements.usernameInput.value;
PageElements.setUsernameStatusContainer.style.display = "none";
PageElements.setUsernameStatus.style.display = "none";
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
} else {
@@ -626,6 +803,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//this is not a YouTube video page
function displayNoVideo() {
document.getElementById("loadingIndicator").innerText = chrome.i18n.getMessage("noVideoID");
PageElements.issueReporterTabs.classList.add("hidden");
}
function addVoteMessage(message, UUID) {
@@ -639,6 +818,17 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
thanksForVotingText.innerText = message;
}
function removeVoteMessage(UUID) {
const voteButtonsContainer = document.getElementById("sponsorTimesVoteButtonsContainer" + UUID);
voteButtonsContainer.style.display = "block";
const voteStatusContainer = document.getElementById("sponsorTimesVoteStatusContainer" + UUID);
voteStatusContainer.style.display = "none";
const thanksForVotingText = document.getElementById("sponsorTimesThanksForVotingText" + UUID);
thanksForVotingText.removeAttribute("innerText");
}
function vote(type, UUID) {
//add loading info
addVoteMessage(chrome.i18n.getMessage("Loading"), UUID);
@@ -662,6 +852,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
} else if (response.successType == -1) {
addVoteMessage(GenericUtils.getErrorMessage(response.statusCode, response.responseText), UUID);
}
setTimeout(() => removeVoteMessage(UUID), 1500);
}
}
);
@@ -787,6 +978,37 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
);
}
function skipSegment(actionType: ActionType, UUID: SegmentUUID, element?: HTMLElement): void {
if (actionType === ActionType.Chapter) {
sendMessage({
message: "unskip",
UUID: UUID
});
} else {
sendMessage({
message: "reskip",
UUID: UUID
});
}
if (element) {
const stopAnimation = 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)
*/
@@ -805,6 +1027,52 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
hiddenButton.style.display = "none";
}
function copyToClipboard(text: string): void {
if (window === window.top) {
window.navigator.clipboard.writeText(text);
} else {
sendTabMessage({
message: "copyToClipboard",
text
});
}
}
async function importSegments() {
const text = (PageElements.importSegmentsText as HTMLInputElement).value;
await sendTabMessage({
message: "importSegments",
data: text
}) as ImportSegmentsResponse;
PageElements.importSegmentsMenu.classList.add("hidden");
}
function exportSegments() {
copyToClipboard(exportTimes(downloadedTimes));
const stopAnimation = AnimationUtils.applyLoadingAnimation(PageElements.exportSegmentsButton, 0.3);
stopAnimation();
new GenericNotice(null, "exportCopied", {
title: chrome.i18n.getMessage(`CopiedExclamation`),
timed: true,
maxCountdownTime: () => 0.6,
referenceNode: PageElements.exportSegmentsButton.parentElement,
dontPauseCountdown: true,
style: {
top: 0,
bottom: 0,
minWidth: 0,
right: "30px",
margin: "auto",
height: "max-content"
},
hideLogo: true,
hideRightInfo: true
});
}
/**
* Converts time in minutes to 2d 5h 25.1
* If less than 1 hour, just returns minutes
@@ -819,12 +1087,30 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
return (days > 0 ? days + chrome.i18n.getMessage("dayAbbreviation") + " " : "") + (hours > 0 ? hours + chrome.i18n.getMessage("hourAbbreviation") + " " : "") + (minutes % 60).toFixed(1);
}
//end of function
function contentConfigUpdateListener(changes: StorageChangesObject) {
for (const key in changes) {
switch(key) {
case "unsubmittedSegments":
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
updateSegmentEditingUI();
break;
}
}
}
function setupComPort(): void {
port = chrome.runtime.connect({ name: "popup" });
port.onDisconnect.addListener(() => setupComPort());
port.onMessage.addListener((msg) => onMessage(msg));
}
function onMessage(msg: PopupMessage) {
switch (msg.message) {
case "time":
displayDownloadedSponsorTimes(downloadedTimes, msg.time);
break;
}
}
}
if (chrome.tabs != undefined) {
//this means it is actually opened in the popup
runThePopup();
}
export default runThePopup;
runThePopup();

View File

@@ -1,15 +1,23 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import CategoryChooserComponent from "../components/CategoryChooserComponent";
import CategoryChooserComponent from "../components/options/CategoryChooserComponent";
class CategoryChooser {
ref: React.RefObject<CategoryChooserComponent>;
constructor(element: Element) {
this.ref = React.createRef();
ReactDOM.render(
<CategoryChooserComponent/>,
<CategoryChooserComponent ref={this.ref} />,
element
);
}
update(): void {
this.ref.current?.forceUpdate();
}
}
export default CategoryChooser;

View File

@@ -0,0 +1,64 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import ChapterVoteComponent, { ChapterVoteState } from "../components/ChapterVoteComponent";
import { VoteResponse } from "../messageTypes";
import { Category, SegmentUUID, SponsorTime } from "../types";
export class ChapterVote {
container: HTMLElement;
ref: React.RefObject<ChapterVoteComponent>;
unsavedState: ChapterVoteState;
mutationObserver?: MutationObserver;
constructor(vote: (type: number, UUID: SegmentUUID, category?: Category) => Promise<VoteResponse>) {
this.ref = React.createRef();
this.container = document.createElement('span');
this.container.id = "chapterVote";
this.container.style.height = "100%";
ReactDOM.render(
<ChapterVoteComponent ref={this.ref} vote={vote} />,
this.container
);
}
getContainer(): HTMLElement {
return this.container;
}
close(): void {
ReactDOM.unmountComponentAtNode(this.container);
this.container.remove();
}
setVisibility(show: boolean): void {
const newState = {
show,
...(!show ? { segment: null } : {})
};
if (this.ref.current) {
this.ref.current?.setState(newState);
} else {
this.unsavedState = newState;
}
}
async setSegment(segment: SponsorTime): Promise<void> {
if (this.ref.current?.state?.segment !== segment) {
const newState = {
segment,
show: true
};
if (this.ref.current) {
this.ref.current?.setState(newState);
} else {
this.unsavedState = newState;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More