mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2024-11-10 09:07:45 +01:00
commit
f555a8e7bb
18 changed files with 1626 additions and 851 deletions
|
@ -263,19 +263,19 @@
|
||||||
"description": "The second line of the message displayed after the notice was upgraded."
|
"description": "The second line of the message displayed after the notice was upgraded."
|
||||||
},
|
},
|
||||||
"setSkipShortcut": {
|
"setSkipShortcut": {
|
||||||
"message": "Set key for skipping a segment"
|
"message": "Skip segment",
|
||||||
|
"description": "Keybind label"
|
||||||
},
|
},
|
||||||
"setStartSponsorShortcut": {
|
"setStartSponsorShortcut": {
|
||||||
"message": "Set key for start/stop segment keybind"
|
"message": "Start/stop segment",
|
||||||
|
"description": "Keybind label"
|
||||||
},
|
},
|
||||||
"setSubmitKeybind": {
|
"setSubmitKeybind": {
|
||||||
"message": "Set key for submission keybind"
|
"message": "Submit segments",
|
||||||
|
"description": "Keybind label"
|
||||||
},
|
},
|
||||||
"keybindDescription": {
|
"keybindDescription": {
|
||||||
"message": "Select a key by typing it"
|
"message": "Select a key by typing it and choose any modifier keys you wish to use."
|
||||||
},
|
|
||||||
"keybindDescriptionComplete": {
|
|
||||||
"message": "The keybind has been set to: "
|
|
||||||
},
|
},
|
||||||
"0": {
|
"0": {
|
||||||
"message": "Connection Timeout. Check your internet connection. If your internet is working, the server is probably overloaded or down."
|
"message": "Connection Timeout. Check your internet connection. If your internet is working, the server is probably overloaded or down."
|
||||||
|
@ -388,9 +388,6 @@
|
||||||
"createdBy": {
|
"createdBy": {
|
||||||
"message": "Created By"
|
"message": "Created By"
|
||||||
},
|
},
|
||||||
"keybindCurrentlySet": {
|
|
||||||
"message": ". It is currently set to:"
|
|
||||||
},
|
|
||||||
"supportOtherSites": {
|
"supportOtherSites": {
|
||||||
"message": "Support 3rd Party YouTube-Sites"
|
"message": "Support 3rd Party YouTube-Sites"
|
||||||
},
|
},
|
||||||
|
@ -470,6 +467,15 @@
|
||||||
"exportOptions": {
|
"exportOptions": {
|
||||||
"message": "Import/Export All Options"
|
"message": "Import/Export All Options"
|
||||||
},
|
},
|
||||||
|
"exportOptionsCopy": {
|
||||||
|
"message": "Edit/copy"
|
||||||
|
},
|
||||||
|
"exportOptionsDownload": {
|
||||||
|
"message": "Save to file"
|
||||||
|
},
|
||||||
|
"exportOptionsUpload": {
|
||||||
|
"message": "Load from file"
|
||||||
|
},
|
||||||
"whatExportOptions": {
|
"whatExportOptions": {
|
||||||
"message": "This is your entire configuration in JSON. This includes your userID, so be sure to share this wisely."
|
"message": "This is your entire configuration in JSON. This includes your userID, so be sure to share this wisely."
|
||||||
},
|
},
|
||||||
|
@ -518,11 +524,8 @@
|
||||||
"copyDebugInformationComplete": {
|
"copyDebugInformationComplete": {
|
||||||
"message": "The debug information has been copied to the clip board. Feel free to remove any information you would rather not share. Save this in a text file or paste into the bug report."
|
"message": "The debug information has been copied to the clip board. Feel free to remove any information you would rather not share. Save this in a text file or paste into the bug report."
|
||||||
},
|
},
|
||||||
"theKey": {
|
|
||||||
"message": "The key"
|
|
||||||
},
|
|
||||||
"keyAlreadyUsed": {
|
"keyAlreadyUsed": {
|
||||||
"message": "is bound to another action. Please select another key."
|
"message": "This shortcut is bound to another action. Please select a different one."
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"message": "to",
|
"message": "to",
|
||||||
|
@ -787,6 +790,9 @@
|
||||||
"hideDonationLink": {
|
"hideDonationLink": {
|
||||||
"message": "Hide Donation Link"
|
"message": "Hide Donation Link"
|
||||||
},
|
},
|
||||||
|
"darkModeOptionsPage": {
|
||||||
|
"message": "Dark Mode On Options Page"
|
||||||
|
},
|
||||||
"helpPageThanksForInstalling": {
|
"helpPageThanksForInstalling": {
|
||||||
"message": "Thanks for installing SponsorBlock."
|
"message": "Thanks for installing SponsorBlock."
|
||||||
},
|
},
|
||||||
|
@ -875,5 +881,42 @@
|
||||||
"hourAbbreviation": {
|
"hourAbbreviation": {
|
||||||
"message": "h",
|
"message": "h",
|
||||||
"description": "100h"
|
"description": "100h"
|
||||||
|
},
|
||||||
|
"optionsTabBehavior": {
|
||||||
|
"message": "Behavior",
|
||||||
|
"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": "Keyboard shortcuts",
|
||||||
|
"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": "Backup/Restore",
|
||||||
|
"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": "Miscellaneous",
|
||||||
|
"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": "Skip notice appearance",
|
||||||
|
"description": "Option label"
|
||||||
|
},
|
||||||
|
"unbind": {
|
||||||
|
"message": "Unbind",
|
||||||
|
"description": "Unbind keyboard shortcut"
|
||||||
|
},
|
||||||
|
"notSet": {
|
||||||
|
"message": "Not set"
|
||||||
|
},
|
||||||
|
"change": {
|
||||||
|
"message": "Change"
|
||||||
|
},
|
||||||
|
"youtubeKeybindWarning": {
|
||||||
|
"message": "This is a built-in YouTube shortcut. Are you sure you want to use it?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<title> SponsorBlock </title>
|
<title> SponsorBlock </title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="../icons/IconSponsorBlocker32px.png" type="image/png">
|
||||||
|
|
||||||
<link href="styles.css" rel="stylesheet"/>
|
<link href="styles.css" rel="stylesheet"/>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,31 @@
|
||||||
|
:root {
|
||||||
|
--color-scheme: dark;
|
||||||
|
--background: #333333;
|
||||||
|
--header-color: #212121;
|
||||||
|
--dialog-background: #181818;
|
||||||
|
--dialog-border: white;
|
||||||
|
--text: #c4c4c4;
|
||||||
|
--title: #dad8d8;
|
||||||
|
--disabled: #520000;
|
||||||
|
--black: black;
|
||||||
|
--white: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] {
|
||||||
|
--color-scheme: light;
|
||||||
|
--background: #f9f9f9;
|
||||||
|
--header-color: white;
|
||||||
|
--dialog-background: #f9f9f9;
|
||||||
|
--dialog-border: #282828;
|
||||||
|
--text: #262626;
|
||||||
|
--title: #707070;
|
||||||
|
--disabled: #ffcaca;
|
||||||
|
--black: white;
|
||||||
|
--white: black;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
color-scheme: dark;
|
color-scheme: var(--color-scheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bigText {
|
.bigText {
|
||||||
|
@ -7,7 +33,7 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #333333;
|
background-color: var(--background);
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +41,10 @@ body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 60%;
|
max-width: 60%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
@ -54,12 +84,14 @@ body {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
color: #212121;
|
color: var(--header-color);
|
||||||
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
transition: font-size 1s;
|
transition: font-size 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +157,8 @@ p,li {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,li,a {
|
p,li,a,span,div {
|
||||||
color: #c4c4c4;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
p,li,code,a {
|
p,li,code,a {
|
||||||
|
@ -160,7 +192,7 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,h2,h3,h4,h5,h6 {
|
h1,h2,h3,h4,h5,h6 {
|
||||||
color: #dad8d8;
|
color: var(--title);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,3 +232,94 @@ svg {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* keybind dialog */
|
||||||
|
.key {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 33px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
border-color: var(--white);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unbound, .key {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keybind-dialog .dialog {
|
||||||
|
position: fixed;
|
||||||
|
border-width: 3px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 15px;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 400px;
|
||||||
|
overflow-x: auto;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 15px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--dialog-background);
|
||||||
|
border-color: var(--dialog-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-buttons {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-buttons > .option-button {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-settings {
|
||||||
|
margin: 15px 15px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-settings .key {
|
||||||
|
vertical-align: top;
|
||||||
|
margin: 15px 0 0 40px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-error {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocker {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 90;
|
||||||
|
background-color: #00000080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-button {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-color: #c00000;
|
||||||
|
padding: 10px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-button:hover:not(.disabled) {
|
||||||
|
background-color: #fc0303;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-button.disabled {
|
||||||
|
cursor: default;
|
||||||
|
background-color: var(--disabled);
|
||||||
|
color: grey;
|
||||||
|
}
|
|
@ -1,10 +1,232 @@
|
||||||
/* Options page CSS */
|
/* Options page CSS */
|
||||||
html {
|
|
||||||
color-scheme: dark;
|
:root {
|
||||||
|
--color-scheme: dark;
|
||||||
|
--background: #333333;
|
||||||
|
--menu-background: #181818;
|
||||||
|
--menu-foreground: white;
|
||||||
|
--dialog-background: #181818;
|
||||||
|
--dialog-border: white;
|
||||||
|
--tab-color: #242424;
|
||||||
|
--tab-button-hover: #4d0000;
|
||||||
|
--tab-hover: white;
|
||||||
|
--description: #dfdfdf;
|
||||||
|
--disabled: #520000;
|
||||||
|
--slider: #707070;
|
||||||
|
--title: #dad8d8;
|
||||||
|
--border-color: #484848;
|
||||||
|
--black: black;
|
||||||
|
--white: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
[data-theme="light"] {
|
||||||
|
--color-scheme: light;
|
||||||
|
--background: #f9f9f9;
|
||||||
|
--menu-background: #dbdbdb;
|
||||||
|
--menu-foreground: #212121;
|
||||||
|
--dialog-background: #f9f9f9;
|
||||||
|
--dialog-border: #282828;
|
||||||
|
--tab-color: #ababab;
|
||||||
|
--tab-button-hover: #750000;
|
||||||
|
--tab-hover: #2e2e2e;
|
||||||
|
--description: #262626;
|
||||||
|
--disabled: #ffcaca;
|
||||||
|
--slider: #bfbebe;
|
||||||
|
--title: #707070;
|
||||||
|
--border-color: #d9d9d9;
|
||||||
|
--black: white;
|
||||||
|
--white: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium-description, .switch-container, .optionLabel, .categoryTableElement {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-description, p, li, span, div {
|
||||||
|
color: var(--description);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,h2,h3,h4,h5,h6 {
|
||||||
|
color: var(--title);
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
color-scheme: var(--color-scheme);
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menubar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
flex-basis: 20%;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 600px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 15px;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: var(--menu-background);
|
||||||
|
color: var(--menu-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
#navigation {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-heading {
|
||||||
|
font-size: 18px;
|
||||||
|
height: 55px;
|
||||||
|
line-height: 55px;
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--tab-color);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-heading:hover {
|
||||||
|
background-color: var(--tab-button-hover);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-heading.selected {
|
||||||
|
background-color: #c00000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-heading:active {
|
||||||
|
background-color: #950000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group > div {
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
border-image: linear-gradient(to right, var(--border-color), #00000000 80%) 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-group > div:last-child, .option-group > #keybind-dialog {
|
||||||
|
border-bottom: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionLabel, #version {
|
||||||
|
font-size: 14px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[data-type="keybind-change"] .optionLabel {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 150px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='number'] {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 33px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
border-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unbound, .key {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybind-buttons {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybind-buttons:hover {
|
||||||
|
background-color: #00000030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keybind-buttons > div, .keybind-buttons > span {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keybind-dialog .dialog {
|
||||||
|
position: fixed;
|
||||||
|
border-width: 3px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 15px;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 400px;
|
||||||
|
overflow-x: auto;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 15px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: var(--dialog-background);
|
||||||
|
border-color: var(--dialog-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-buttons {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-buttons > .option-button {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-settings {
|
||||||
|
margin: 15px 15px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-settings .key {
|
||||||
|
vertical-align: top;
|
||||||
|
margin: 15px 0 0 40px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#change-keybind-error {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blocker {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 90;
|
||||||
|
background-color: #00000080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.low-profile {
|
||||||
|
height: 23px;
|
||||||
|
line-height: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
|
@ -15,25 +237,49 @@ body {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.next-line {
|
||||||
|
padding: 15px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hiding {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacing {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.keybind-status {
|
.keybind-status {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-description {
|
.small-description {
|
||||||
color: white;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
padding: 15px 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-description td {
|
||||||
|
padding: 10px 0 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indent {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryTableElement td {
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.medium-description {
|
.medium-description {
|
||||||
color: white;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,36 +299,46 @@ body {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-button:hover {
|
.option-button:hover:not(.disabled) {
|
||||||
background-color: #fc0303;
|
background-color: #fc0303;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-button.disabled {
|
.option-button.disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
background-color: var(--disabled);
|
||||||
background-color: #520000;
|
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
#options {
|
#options {
|
||||||
max-width: 60%;
|
height: 100vh;
|
||||||
|
flex-basis: 80%;
|
||||||
|
overflow: auto;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
display: inline-block;
|
padding: 80px 15% 0 3%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
transition: padding 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#options.embed {
|
#options > div {
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options.embed > div {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
text-align: left;
|
}
|
||||||
display: inline-block;
|
|
||||||
|
#title .profilepic {
|
||||||
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-container {
|
.switch-container {
|
||||||
content: attr(label-name);
|
content: attr(label-name);
|
||||||
position: absolute;
|
|
||||||
width: max-content;
|
width: max-content;
|
||||||
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: white;
|
|
||||||
|
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
@ -94,11 +350,6 @@ body {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-label-container {
|
|
||||||
font-size: 14px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch {
|
.switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -119,7 +370,7 @@ body {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: #707070;
|
background-color: var(--slider);
|
||||||
}
|
}
|
||||||
|
|
||||||
.animated * {
|
.animated * {
|
||||||
|
@ -162,11 +413,8 @@ input:checked + .slider:before {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Boilerplate CSS from https://ajay.app */
|
|
||||||
|
|
||||||
body {
|
/* Boilerplate CSS from https://ajay.app (edited) */
|
||||||
background-color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.projectPreview {
|
.projectPreview {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -196,29 +444,25 @@ body {
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.createdBy {
|
#createdBy {
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-top: 0px;
|
margin: auto 0 10px 0;
|
||||||
padding-bottom: 0px;
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
display: inline-block;
|
#createdBy > * {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#title {
|
#title {
|
||||||
background-color: #636363;
|
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
font-size: 50px;
|
font-size: 40px;
|
||||||
color: #212121;
|
|
||||||
|
|
||||||
padding: 20px;
|
padding: 40px 20px;
|
||||||
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
transition: font-size 1s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
|
@ -237,7 +481,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.profilepic {
|
.profilepic {
|
||||||
background-color: #636363 !important;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,21 +524,9 @@ a {
|
||||||
|
|
||||||
p,li {
|
p,li {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #c4c4c4;
|
|
||||||
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (orientation:portrait) {
|
|
||||||
#options {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.previewColorOption {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.previewImage {
|
.previewImage {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
@ -316,10 +547,6 @@ img {
|
||||||
color: #dad8d8;
|
color: #dad8d8;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,h2,h3,h4,h5,h6 {
|
|
||||||
color: #dad8d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
@ -337,8 +564,6 @@ svg {
|
||||||
|
|
||||||
.categoryTableElement {
|
.categoryTableElement {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.categoryTableElement > * {
|
.categoryTableElement > * {
|
||||||
|
@ -369,3 +594,87 @@ svg {
|
||||||
#sbDonate {
|
#sbDonate {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Handle smaller screensizes */
|
||||||
|
@media only screen and (max-width: 1600px){
|
||||||
|
#options {
|
||||||
|
padding-right: 8%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-height: 770px), only screen and (max-width: 1200px) {
|
||||||
|
#options-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#menubar {
|
||||||
|
gap: 8px;
|
||||||
|
min-width: unset;
|
||||||
|
max-width: unset;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
#navigation {
|
||||||
|
gap: 8px;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#options {
|
||||||
|
padding: 0 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-heading {
|
||||||
|
width: unset;
|
||||||
|
min-width: unset;
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#title {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#title .profilepic {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
#createdBy {
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
height: unset;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#createdBy > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#sbDonate {
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
#version {
|
||||||
|
font-size: 10px;
|
||||||
|
height: 10px;
|
||||||
|
transform: translate(-50px, -5px);
|
||||||
|
}
|
||||||
|
.sticky #menubar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 15px;
|
||||||
|
}
|
||||||
|
.sticky #title, .sticky #createdBy {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
#options {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options > div {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
<!-- Link to specific tabs by using their ID in the URL like: options.html#keybinds -->
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Options - SponsorBlock</title>
|
<title>Options - SponsorBlock</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="../icons/IconSponsorBlocker32px.png" type="image/png">
|
||||||
|
|
||||||
<link href="options.css" rel="stylesheet"/>
|
<link href="options.css" rel="stylesheet"/>
|
||||||
|
|
||||||
|
@ -13,247 +15,159 @@
|
||||||
|
|
||||||
<body class="sponsorBlockPageBody">
|
<body class="sponsorBlockPageBody">
|
||||||
|
|
||||||
|
<div id="options-container">
|
||||||
|
|
||||||
|
<div id="menubar" class="center">
|
||||||
|
|
||||||
<div id="title" class="titleBar">
|
<div id="title" class="titleBar">
|
||||||
<img src="../icons/LogoSponsorBlocker256px.png" height="80" class="profilepic"/>
|
<img src="../icons/LogoSponsorBlocker256px.png" class="profilepic" alt="SponsorBlock logo"/>
|
||||||
SponsorBlock
|
SponsorBlock
|
||||||
|
<div id="version"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="center">
|
<div id="navigation">
|
||||||
<p class="createdBy titleBar">
|
<div class="tab-heading" data-for="behavior">
|
||||||
<img src="../icons/newprofilepic.jpg" height="30" class="profilepiccircle"/>
|
__MSG_optionsTabBehavior__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-heading" data-for="interface">
|
||||||
|
__MSG_optionsTabInterface__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-heading" data-for="keybinds">
|
||||||
|
__MSG_optionsTabKeyBinds__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-heading" data-for="import">
|
||||||
|
__MSG_optionsTabBackup__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-heading" data-for="advanced">
|
||||||
|
__MSG_optionsTabAdvanced__
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="createdBy" class="titleBar">
|
||||||
|
<div>
|
||||||
|
<img src="../icons/newprofilepic.jpg" height="30" class="profilepiccircle" alt="profile picture of creator"/>
|
||||||
__MSG_createdBy__
|
__MSG_createdBy__
|
||||||
<a href="https://ajay.app">Ajay Ramachandran</a>
|
<a href="https://ajay.app">Ajay Ramachandran</a>
|
||||||
|
</div>
|
||||||
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">(__MSG_Donate__)</a>
|
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">(__MSG_Donate__)</a>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<h1>__MSG_Options__</h1>
|
|
||||||
|
|
||||||
<div id="options" class="hidden">
|
|
||||||
|
|
||||||
<div id="category-type" option-type="react-CategoryChooserComponent">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="autoSkipOnMusicVideos">
|
<div id="options">
|
||||||
<label class="switch-container">
|
|
||||||
|
<div id="behavior" class="option-group hidden">
|
||||||
|
|
||||||
|
<div id="category-type" data-type="react-CategoryChooserComponent">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-sync="autoSkipOnMusicVideos">
|
||||||
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="autoSkipOnMusicVideos" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="autoSkipOnMusicVideos">
|
||||||
__MSG_autoSkipOnMusicVideos__
|
__MSG_autoSkipOnMusicVideos__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="muteSegments">
|
<div data-type="toggle" data-sync="muteSegments">
|
||||||
<label class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="muteSegments" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="muteSegments">
|
||||||
__MSG_muteSegments__
|
__MSG_muteSegments__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="fullVideoSegments">
|
<div option-type="toggle" data-sync="fullVideoSegments">
|
||||||
<label class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="fullVideoSegments" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="fullVideoSegments">
|
||||||
__MSG_fullVideoSegments__
|
__MSG_fullVideoSegments__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div id="support-invidious" option-type="toggle" sync-option="supportInvidious" no-safari="true">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox">
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_supportOtherSites__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">(__MSG_supportedSites__ Invidious, CloudTube)</div>
|
|
||||||
<br/>
|
|
||||||
<span class="small-description">__MSG_supportOtherSitesDescription__ </span>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div option-type="private-text-change" sync-option="invidiousInstances" no-safari="true">
|
|
||||||
<div class="option-button trigger-button">
|
|
||||||
__MSG_addInvidiousInstance__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_addInvidiousInstanceDescription__</div>
|
|
||||||
|
|
||||||
<div class="option-hidden-section hidden">
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<input class="option-text-box" type="text">
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="option-button text-change-set inline">
|
|
||||||
__MSG_add__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option-button invidious-instance-reset inline">
|
|
||||||
__MSG_resetInvidiousInstance__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<span class="small-description">__MSG_currentInstances__</span>
|
|
||||||
<span class="small-description" option-type="display" sync-option="invidiousInstances"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div option-type="keybind-change" sync-option="skipKeybind">
|
|
||||||
<div class="option-button trigger-button">
|
|
||||||
__MSG_setSkipShortcut__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option-hidden-section hidden">
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="medium-description keybind-status">
|
|
||||||
__MSG_keybindDescription__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="medium-description bold keybind-status-key">
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="number-change" data-sync="minDuration">
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="keybind-change" sync-option="startSponsorKeybind">
|
|
||||||
<div class="option-button trigger-button">
|
|
||||||
__MSG_setStartSponsorShortcut__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option-hidden-section hidden">
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="medium-description keybind-status">
|
|
||||||
__MSG_keybindDescription__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="medium-description bold keybind-status-key">
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="keybind-change" sync-option="submitKeybind">
|
|
||||||
<div class="option-button trigger-button">
|
|
||||||
__MSG_setSubmitKeybind__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="option-hidden-section hidden">
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="medium-description keybind-status">
|
|
||||||
__MSG_keybindDescription__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="medium-description bold keybind-status-key">
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="number-change" sync-option="skipNoticeDuration">
|
|
||||||
<label class="number-container">
|
|
||||||
<input type="number" step="1" min="1">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_skipNoticeDurationDescription__</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="number-change" sync-option="minDuration">
|
|
||||||
<label class="number-container">
|
<label class="number-container">
|
||||||
|
<span class="optionLabel">__MSG_minDuration__</span>
|
||||||
<input type="number" step="0.1" min="0">
|
<input type="number" step="0.1" min="0">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_minDurationDescription__</div>
|
<div class="small-description">__MSG_minDurationDescription__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-sync="forceChannelCheck">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
|
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="dontShowNotice">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="forceChannelCheck" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="forceChannelCheck">
|
||||||
__MSG_showSkipNotice__
|
__MSG_forceChannelCheck__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div class="small-description">__MSG_whatForceChannelCheck__</div>
|
||||||
<br/>
|
</div>
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="selector" sync-option="noticeVisibilityMode">
|
<div data-type="toggle" data-sync="refetchWhenNotFound">
|
||||||
<select class="selector-element optionsSelector" >
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="refetchWhenNotFound" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="refetchWhenNotFound">
|
||||||
|
__MSG_enableRefetchWhenNotFound__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small-description">__MSG_whatRefetchWhenNotFound__</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="interface" class="option-group hidden">
|
||||||
|
|
||||||
|
<div data-type="number-change" data-sync="skipNoticeDuration">
|
||||||
|
<label class="number-container">
|
||||||
|
<span class="optionLabel">__MSG_skipNoticeDuration__</span>
|
||||||
|
<input type="number" step="1" min="1">
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="small-description">__MSG_skipNoticeDurationDescription__</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowNotice">
|
||||||
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="dontShowNotice" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="dontShowNotice">
|
||||||
|
__MSG_showSkipNotice__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="selector" data-sync="noticeVisibilityMode">
|
||||||
|
<label class="optionLabel" for="noticeVisibilityMode">__MSG_noticeVisibilityLabel__:</label>
|
||||||
|
|
||||||
|
<select id="noticeVisibilityMode" class="selector-element optionsSelector" >
|
||||||
<option value="0">__MSG_noticeVisibilityMode0__</option>
|
<option value="0">__MSG_noticeVisibilityMode0__</option>
|
||||||
<option value="1">__MSG_noticeVisibilityMode1__</option>
|
<option value="1">__MSG_noticeVisibilityMode1__</option>
|
||||||
<option value="2">__MSG_noticeVisibilityMode2__</option>
|
<option value="2">__MSG_noticeVisibilityMode2__</option>
|
||||||
|
@ -262,292 +176,191 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideVideoPlayerControls">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="forceChannelCheck">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="hideVideoPlayerControls" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="hideVideoPlayerControls">
|
||||||
__MSG_forceChannelCheck__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_whatForceChannelCheck__</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideVideoPlayerControls">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" checked>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_showButtons__
|
__MSG_showButtons__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_hideButtonsDescription__</div>
|
<div class="small-description">__MSG_hideButtonsDescription__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideDeleteButtonPlayerControls" data-dependent-on="hideVideoPlayerControls">
|
||||||
|
<div class="switch-container">
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideSkipButtonPlayerControls">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="hideDeleteButtonPlayerControls" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="hideDeleteButtonPlayerControls">
|
||||||
__MSG_showSkipButton__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideInfoButtonPlayerControls">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" checked>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_showInfoButton__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="autoHideInfoButton" if-false="hideInfoButtonPlayerControls">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" checked>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_autoHideInfoButton__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideDeleteButtonPlayerControls">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" checked>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_showDeleteButton__
|
__MSG_showDeleteButton__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideUploadButtonPlayerControls" data-dependent-on="hideVideoPlayerControls">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideUploadButtonPlayerControls">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="hideUploadButtonPlayerControls" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="hideUploadButtonPlayerControls">
|
||||||
__MSG_showUploadButton__
|
__MSG_showUploadButton__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideSkipButtonPlayerControls">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="audioNotificationOnSkip">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="hideSkipButtonPlayerControls" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="hideSkipButtonPlayerControls">
|
||||||
__MSG_audioNotification__
|
__MSG_showSkipButton__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="hideInfoButtonPlayerControls">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
<br/>
|
<label class="switch">
|
||||||
|
<input id="hideInfoButtonPlayerControls" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="hideInfoButtonPlayerControls">
|
||||||
|
__MSG_showInfoButton__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-sync="autoHideInfoButton" data-dependent-on="hideInfoButtonPlayerControls">
|
||||||
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="autoHideInfoButton" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="autoHideInfoButton">
|
||||||
|
__MSG_autoHideInfoButton__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-sync="audioNotificationOnSkip">
|
||||||
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="audioNotificationOnSkip" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="audioNotificationOnSkip">
|
||||||
|
__MSG_audioNotification__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="small-description">__MSG_audioNotificationDescription__</div>
|
<div class="small-description">__MSG_audioNotificationDescription__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-sync="showTimeWithSkips">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="showTimeWithSkips">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="showTimeWithSkips" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="showTimeWithSkips">
|
||||||
__MSG_showTimeWithSkips__
|
__MSG_showTimeWithSkips__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_showTimeWithSkipsDescription__</div>
|
<div class="small-description">__MSG_showTimeWithSkipsDescription__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-sync="darkMode">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="trackViewCount">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="darkMode" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="darkMode">
|
||||||
__MSG_enableViewTracking__
|
__MSG_darkModeOptionsPage__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_whatViewTracking__</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-toggle-type="reverse" data-sync="showDonationLink" data-no-safari="true">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="trackViewCountInPrivate" private-mode-only="true">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" checked>
|
<input id="showDonationLink" type="checkbox" checked>
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="showDonationLink">
|
||||||
__MSG_enableViewTrackingInPrivate__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="refetchWhenNotFound">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" checked>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_enableRefetchWhenNotFound__
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_whatRefetchWhenNotFound__</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="toggle" toggle-type="reverse" sync-option="showDonationLink" no-safari="true">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" checked>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<div class="switch-label">
|
|
||||||
__MSG_hideDonationLink__
|
__MSG_hideDonationLink__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div id="keybinds" class="option-group hidden">
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="private-text-change" sync-option="userID" confirm-message="userIDChangeWarning">
|
<div data-type="keybind-change" data-sync="skipKeybind">
|
||||||
|
<label class="optionLabel">__MSG_setSkipShortcut__:</label>
|
||||||
|
<div class="inline"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="keybind-change" data-sync="startSponsorKeybind">
|
||||||
|
<label class="optionLabel">__MSG_setStartSponsorShortcut__:</label>
|
||||||
|
<div class="inline"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="keybind-change" data-sync="submitKeybind">
|
||||||
|
<label class="optionLabel">__MSG_setSubmitKeybind__:</label>
|
||||||
|
<div class="inline"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="import" class="option-group hidden">
|
||||||
|
|
||||||
|
<div data-type="private-text-change" data-sync="userID" data-confirm-message="userIDChangeWarning">
|
||||||
<div class="option-button trigger-button">
|
<div class="option-button trigger-button">
|
||||||
__MSG_changeUserID__
|
__MSG_changeUserID__
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_whatChangeUserID__</div>
|
<div class="small-description">__MSG_whatChangeUserID__</div>
|
||||||
|
|
||||||
<div class="option-hidden-section hidden">
|
<div class="option-hidden-section hidden spacing indent">
|
||||||
<br/>
|
|
||||||
|
|
||||||
<input class="option-text-box" type="text">
|
<input class="option-text-box" type="text">
|
||||||
|
|
||||||
<br/>
|
<div class="option-button text-change-set inline low-profile">
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="option-button text-change-set">
|
|
||||||
__MSG_setUserID__
|
__MSG_setUserID__
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="private-text-change" data-sync="*" data-confirm-message="exportOptionsWarning">
|
||||||
<br/>
|
<h2>__MSG_exportOptions__</h2>
|
||||||
|
|
||||||
<div option-type="private-text-change" sync-option="*" confirm-message="exportOptionsWarning">
|
<div>
|
||||||
<div class="option-button trigger-button">
|
<div class="option-button trigger-button inline">
|
||||||
__MSG_exportOptions__
|
__MSG_exportOptionsCopy__
|
||||||
|
</div>
|
||||||
|
<div class="option-button download-button inline">
|
||||||
|
__MSG_exportOptionsDownload__
|
||||||
|
</div>
|
||||||
|
<label for="importOptions" class="option-button inline">
|
||||||
|
__MSG_exportOptionsUpload__
|
||||||
|
</label>
|
||||||
|
<input id="importOptions" type="file" class="upload-button hidden" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_whatExportOptions__</div>
|
<div class="small-description">__MSG_whatExportOptions__</div>
|
||||||
|
|
||||||
<div class="option-hidden-section hidden">
|
<div class="option-hidden-section hidden spacing indent">
|
||||||
<br/>
|
<textarea class="option-text-box" rows="10" style="width:80%"></textarea>
|
||||||
|
|
||||||
<input class="option-text-box" type="text">
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="option-button text-change-set">
|
<div class="option-button text-change-set">
|
||||||
__MSG_setOptions__
|
__MSG_setOptions__
|
||||||
|
@ -555,51 +368,111 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
</div>
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div option-type="button-press" sync-option="copyDebugInformation" confirm-message="copyDebugInformation">
|
<div id="advanced" class="option-group hidden">
|
||||||
|
|
||||||
|
<div id="support-invidious" data-type="toggle" data-sync="supportInvidious" data-no-safari="true">
|
||||||
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="supportInvidious" type="checkbox">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="supportInvidious">
|
||||||
|
__MSG_supportOtherSites__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small-description">(__MSG_supportedSites__ Invidious, CloudTube)</div>
|
||||||
|
<div class="small-description">__MSG_supportOtherSitesDescription__ </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="private-text-change" data-sync="invidiousInstances" data-no-safari="true" data-dependent-on="supportInvidious">
|
||||||
|
<div class="option-button trigger-button">
|
||||||
|
__MSG_addInvidiousInstance__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small-description">__MSG_addInvidiousInstanceDescription__</div>
|
||||||
|
|
||||||
|
<div class="indent option-hidden-section hidden spacing">
|
||||||
|
<input class="option-text-box" type="text">
|
||||||
|
<div class="inline">
|
||||||
|
<div class="option-button text-change-set inline low-profile">
|
||||||
|
__MSG_add__
|
||||||
|
</div>
|
||||||
|
<div class="option-button text-change-reset inline low-profile">
|
||||||
|
__MSG_cancel__
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:15px">
|
||||||
|
<span>__MSG_currentInstances__</span>
|
||||||
|
<span data-type="display" data-sync="invidiousInstances"></span>
|
||||||
|
<div class="option-button invidious-instance-reset spacing hidden">
|
||||||
|
__MSG_resetInvidiousInstance__
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-sync="trackViewCount">
|
||||||
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="trackViewCount" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="trackViewCount">
|
||||||
|
__MSG_enableViewTracking__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small-description">__MSG_whatViewTracking__</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="toggle" data-sync="trackViewCountInPrivate" data-dependent-on="trackViewCount" data-private-only="true">
|
||||||
|
<div class="switch-container">
|
||||||
|
<label class="switch">
|
||||||
|
<input id="trackViewCountInPrivate" type="checkbox" checked>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<label class="switch-label" for="trackViewCountInPrivate">
|
||||||
|
__MSG_enableViewTrackingInPrivate__
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-type="button-press" data-sync="copyDebugInformation" data-confirm-message="copyDebugInformation">
|
||||||
<div class="option-button trigger-button">
|
<div class="option-button trigger-button">
|
||||||
__MSG_copyDebugInformation__
|
__MSG_copyDebugInformation__
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_copyDebugInformationOptions__</div>
|
<div class="small-description">__MSG_copyDebugInformationOptions__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<div data-type="toggle" data-sync="testingServer" data-confirm-message="testingServerWarning" data-no-safari="true">
|
||||||
<br/>
|
<div class="switch-container">
|
||||||
|
|
||||||
<div option-type="toggle" sync-option="testingServer" confirm-message="testingServerWarning" no-safari="true">
|
|
||||||
<label class="switch-container">
|
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox">
|
<input id="testingServer" type="checkbox">
|
||||||
<span class="slider round"></span>
|
<span class="slider round"></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="switch-label">
|
<label class="switch-label" for="testingServer">
|
||||||
__MSG_enableTestingServer__
|
__MSG_enableTestingServer__
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_whatEnableTestingServer__</div>
|
<div class="small-description">__MSG_whatEnableTestingServer__</div>
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div option-type="text-change" sync-option="serverAddress">
|
<div data-type="text-change" data-sync="serverAddress" data-dependent-on="testingServer" data-dependent-on-inverted="true">
|
||||||
<label class="text-label-container">
|
<label class="optionLabel inline">
|
||||||
<div>__MSG_customServerAddress__</div>
|
<span class="optionLabel">__MSG_customServerAddress__:</span>
|
||||||
|
|
||||||
<input class="option-text-box" type="text">
|
<input class="option-text-box" type="text" style="margin-right:10px">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<div class="small-description">__MSG_customServerAddressDescription__</div>
|
||||||
|
|
||||||
|
<div class="next-line">
|
||||||
<div class="option-button text-change-set inline">
|
<div class="option-button text-change-set inline">
|
||||||
__MSG_save__
|
__MSG_save__
|
||||||
</div>
|
</div>
|
||||||
|
@ -607,14 +480,13 @@
|
||||||
<div class="option-button text-change-reset inline">
|
<div class="option-button text-change-reset inline">
|
||||||
__MSG_reset__
|
__MSG_reset__
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="small-description">__MSG_customServerAddressDescription__</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -31,24 +31,24 @@ class CategoryChooserComponent extends React.Component<CategoryChooserProps, Cat
|
||||||
{/* Headers */}
|
{/* Headers */}
|
||||||
<tr id={"CategoryOptionsRow"}
|
<tr id={"CategoryOptionsRow"}
|
||||||
className="categoryTableElement categoryTableHeader">
|
className="categoryTableElement categoryTableHeader">
|
||||||
<td id={"CategoryOptionName"}>
|
<th id={"CategoryOptionName"}>
|
||||||
{chrome.i18n.getMessage("category")}
|
{chrome.i18n.getMessage("category")}
|
||||||
</td>
|
</th>
|
||||||
|
|
||||||
<td id={"CategorySkipOption"}
|
<th id={"CategorySkipOption"}
|
||||||
className="skipOption">
|
className="skipOption">
|
||||||
{chrome.i18n.getMessage("skipOption")}
|
{chrome.i18n.getMessage("skipOption")}
|
||||||
</td>
|
</th>
|
||||||
|
|
||||||
<td id={"CategoryColorOption"}
|
<th id={"CategoryColorOption"}
|
||||||
className="colorOption">
|
className="colorOption">
|
||||||
{chrome.i18n.getMessage("seekBarColor")}
|
{chrome.i18n.getMessage("seekBarColor")}
|
||||||
</td>
|
</th>
|
||||||
|
|
||||||
<td id={"CategoryPreviewColorOption"}
|
<th id={"CategoryPreviewColorOption"}
|
||||||
className="previewColorOption">
|
className="previewColorOption">
|
||||||
{chrome.i18n.getMessage("previewColor")}
|
{chrome.i18n.getMessage("previewColor")}
|
||||||
</td>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{this.getCategorySkipOptions()}
|
{this.getCategorySkipOptions()}
|
||||||
|
|
75
src/components/KeybindComponent.tsx
Normal file
75
src/components/KeybindComponent.tsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import Config from "../config";
|
||||||
|
import { Keybind } from "../types";
|
||||||
|
import KeybindDialogComponent from "./KeybindDialogComponent";
|
||||||
|
import { keybindEquals, keybindToString, formatKey } from "../utils/configUtils";
|
||||||
|
|
||||||
|
export interface KeybindProps {
|
||||||
|
option: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeybindState {
|
||||||
|
keybind: Keybind;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dialog;
|
||||||
|
|
||||||
|
class KeybindComponent extends React.Component<KeybindProps, KeybindState> {
|
||||||
|
constructor(props: KeybindProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {keybind: Config.config[this.props.option]};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<div className="keybind-buttons inline" title={chrome.i18n.getMessage("change")} onClick={() => this.openEditDialog()}>
|
||||||
|
{this.state.keybind?.ctrl && <div className="key keyControl">Ctrl</div>}
|
||||||
|
{this.state.keybind?.ctrl && <span className="keyControl">+</span>}
|
||||||
|
{this.state.keybind?.alt && <div className="key keyAlt">Alt</div>}
|
||||||
|
{this.state.keybind?.alt && <span className="keyAlt">+</span>}
|
||||||
|
{this.state.keybind?.shift && <div className="key keyShift">Shift</div>}
|
||||||
|
{this.state.keybind?.shift && <span className="keyShift">+</span>}
|
||||||
|
{this.state.keybind?.key != null && <div className="key keyBase">{formatKey(this.state.keybind.key)}</div>}
|
||||||
|
{this.state.keybind == null && <span className="unbound">{chrome.i18n.getMessage("notSet")}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.state.keybind != null &&
|
||||||
|
<div className="option-button trigger-button inline" onClick={() => this.unbind()}>
|
||||||
|
{chrome.i18n.getMessage("unbind")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(other: Keybind): boolean {
|
||||||
|
return keybindEquals(this.state.keybind, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return keybindToString(this.state.keybind);
|
||||||
|
}
|
||||||
|
|
||||||
|
openEditDialog(): void {
|
||||||
|
dialog = parent.document.createElement("div");
|
||||||
|
dialog.id = "keybind-dialog";
|
||||||
|
parent.document.body.prepend(dialog);
|
||||||
|
ReactDOM.render(<KeybindDialogComponent option={this.props.option} closeListener={(updateWith) => this.closeEditDialog(updateWith)} />, dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEditDialog(updateWith: Keybind): void {
|
||||||
|
ReactDOM.unmountComponentAtNode(dialog);
|
||||||
|
dialog.remove();
|
||||||
|
if (updateWith != null)
|
||||||
|
this.setState({keybind: updateWith});
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind(): void {
|
||||||
|
this.setState({keybind: null});
|
||||||
|
Config.config[this.props.option] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeybindComponent;
|
165
src/components/KeybindDialogComponent.tsx
Normal file
165
src/components/KeybindDialogComponent.tsx
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
import Config from "../config";
|
||||||
|
import { Keybind } from "../types";
|
||||||
|
import { keybindEquals, formatKey } from "../utils/configUtils";
|
||||||
|
|
||||||
|
export interface KeybindDialogProps {
|
||||||
|
option: string;
|
||||||
|
closeListener: (updateWith) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeybindDialogState {
|
||||||
|
key: Keybind;
|
||||||
|
error: ErrorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorMessage {
|
||||||
|
message: string;
|
||||||
|
blocking: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeybindDialogComponent extends React.Component<KeybindDialogProps, KeybindDialogState> {
|
||||||
|
|
||||||
|
constructor(props: KeybindDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
key: {
|
||||||
|
key: null,
|
||||||
|
code: null,
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
message: null,
|
||||||
|
blocking: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactElement {
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<div className="blocker"></div>
|
||||||
|
<div className="dialog">
|
||||||
|
<div id="change-keybind-description">{chrome.i18n.getMessage("keybindDescription")}</div>
|
||||||
|
<div id="change-keybind-settings">
|
||||||
|
<div id="change-keybind-modifiers" className="inline">
|
||||||
|
<div>
|
||||||
|
<input id="change-keybind-ctrl" type="checkbox" onChange={this.keybindModifierChecked} />
|
||||||
|
<label htmlFor="change-keybind-ctrl">Ctrl</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="change-keybind-alt" type="checkbox" onChange={this.keybindModifierChecked} />
|
||||||
|
<label htmlFor="change-keybind-alt">Alt</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="change-keybind-shift" type="checkbox" onChange={this.keybindModifierChecked} />
|
||||||
|
<label htmlFor="change-keybind-shift">Shift</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="key inline">{formatKey(this.state.key.key)}</div>
|
||||||
|
</div>
|
||||||
|
<div id="change-keybind-error">{this.state.error?.message}</div>
|
||||||
|
<div id="change-keybind-buttons">
|
||||||
|
<div className={"option-button save-button inline" + ((this.state.error?.blocking || this.state.key.key == null) ? " disabled" : "")} onClick={() => this.save()}>
|
||||||
|
{chrome.i18n.getMessage("save")}
|
||||||
|
</div>
|
||||||
|
<div className="option-button cancel-button inline" onClick={() => this.props.closeListener(null)}>
|
||||||
|
{chrome.i18n.getMessage("cancel")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
parent.document.addEventListener("keydown", this.keybindKeyPressed);
|
||||||
|
document.addEventListener("keydown", this.keybindKeyPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
parent.document.removeEventListener("keydown", this.keybindKeyPressed);
|
||||||
|
document.removeEventListener("keydown", this.keybindKeyPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
keybindKeyPressed = (e: KeyboardEvent): void => {
|
||||||
|
if (!e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.getModifierState("AltGraph")) {
|
||||||
|
if (e.code == "Escape") {
|
||||||
|
this.props.closeListener(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
key: {
|
||||||
|
key: e.key,
|
||||||
|
code: e.code,
|
||||||
|
ctrl: this.state.key.ctrl,
|
||||||
|
alt: this.state.key.alt,
|
||||||
|
shift: this.state.key.shift}
|
||||||
|
}, () => this.setState({ error: this.isKeybindAvailable() }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keybindModifierChecked = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
const id = e.target.id;
|
||||||
|
const val = e.target.checked;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
key: {
|
||||||
|
key: this.state.key.key,
|
||||||
|
code: this.state.key.code,
|
||||||
|
ctrl: id == "change-keybind-ctrl" ? val: this.state.key.ctrl,
|
||||||
|
alt: id == "change-keybind-alt" ? val: this.state.key.alt,
|
||||||
|
shift: id == "change-keybind-shift" ? val: this.state.key.shift}
|
||||||
|
}, () => this.setState({ error: this.isKeybindAvailable() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
isKeybindAvailable(): ErrorMessage {
|
||||||
|
if (this.state.key.key == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let youtubeShortcuts: Keybind[];
|
||||||
|
if (/[a-zA-Z0-9,.+\-\][:]/.test(this.state.key.key)) {
|
||||||
|
youtubeShortcuts = [{key: "k"}, {key: "j"}, {key: "l"}, {key: "p", shift: true}, {key: "n", shift: true}, {key: ","}, {key: "."}, {key: ",", shift: true}, {key: ".", shift: true},
|
||||||
|
{key: "ArrowRight"}, {key: "ArrowLeft"}, {key: "ArrowUp"}, {key: "ArrowDown"}, {key: "ArrowRight", ctrl: true}, {key: "ArrowLeft", ctrl: true}, {key: "c"}, {key: "o"},
|
||||||
|
{key: "w"}, {key: "+"}, {key: "-"}, {key: "f"}, {key: "t"}, {key: "i"}, {key: "m"}, {key: "a"}, {key: "s"}, {key: "d"}, {key: "Home"}, {key: "End"},
|
||||||
|
{key: "0"}, {key: "1"}, {key: "2"}, {key: "3"}, {key: "4"}, {key: "5"}, {key: "6"}, {key: "7"}, {key: "8"}, {key: "9"}, {key: "]"}, {key: "["}];
|
||||||
|
} else {
|
||||||
|
youtubeShortcuts = [{key: null, code: "KeyK"}, {key: null, code: "KeyJ"}, {key: null, code: "KeyL"}, {key: null, code: "KeyP", shift: true}, {key: null, code: "KeyN", shift: true},
|
||||||
|
{key: null, code: "Comma"}, {key: null, code: "Period"}, {key: null, code: "Comma", shift: true}, {key: null, code: "Period", shift: true}, {key: null, code: "Space"},
|
||||||
|
{key: null, code: "KeyC"}, {key: null, code: "KeyO"}, {key: null, code: "KeyW"}, {key: null, code: "Equal"}, {key: null, code: "Minus"}, {key: null, code: "KeyF"}, {key: null, code: "KeyT"},
|
||||||
|
{key: null, code: "KeyI"}, {key: null, code: "KeyM"}, {key: null, code: "KeyA"}, {key: null, code: "KeyS"}, {key: null, code: "KeyD"}, {key: null, code: "BracketLeft"}, {key: null, code: "BracketRight"}];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const shortcut of youtubeShortcuts) {
|
||||||
|
const withShift = Object.assign({}, shortcut);
|
||||||
|
if (!/[0-9]/.test(this.state.key.key)) //shift+numbers don't seem to do anything on youtube, all other keys do
|
||||||
|
withShift.shift = true;
|
||||||
|
if (this.equals(shortcut) || this.equals(withShift))
|
||||||
|
return {message: chrome.i18n.getMessage("youtubeKeybindWarning"), blocking: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.option != "skipKeybind" && this.equals(Config.config['skipKeybind']) ||
|
||||||
|
this.props.option != "submitKeybind" && this.equals(Config.config['submitKeybind']) ||
|
||||||
|
this.props.option != "startSponsorKeybind" && this.equals(Config.config['startSponsorKeybind']))
|
||||||
|
return {message: chrome.i18n.getMessage("keyAlreadyUsed"), blocking: true};
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(other: Keybind): boolean {
|
||||||
|
return keybindEquals(this.state.key, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
if (this.state.key.key != null && !this.state.error?.blocking) {
|
||||||
|
Config.config[this.props.option] = this.state.key;
|
||||||
|
this.props.closeListener(this.state.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeybindDialogComponent;
|
|
@ -6,8 +6,8 @@ import NoticeComponent from "./NoticeComponent";
|
||||||
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
import { getSkippingText } from "../utils/categoryUtils";
|
import { getSkippingText } from "../utils/categoryUtils";
|
||||||
|
import { keybindToString } from "../utils/configUtils";
|
||||||
|
|
||||||
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
|
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
|
||||||
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
||||||
|
@ -344,7 +344,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
||||||
className="sponsorSkipObject sponsorSkipNoticeButton"
|
className="sponsorSkipObject sponsorSkipNoticeButton"
|
||||||
style={style}
|
style={style}
|
||||||
onClick={() => this.prepAction(SkipNoticeAction.Unskip)}>
|
onClick={() => this.prepAction(SkipNoticeAction.Unskip)}>
|
||||||
{this.state.skipButtonText + (this.state.showKeybindHint ? " (" + Config.config.skipKeybind + ")" : "")}
|
{this.state.skipButtonText + (this.state.showKeybindHint ? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -374,7 +374,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||||
if (confirm(chrome.i18n.getMessage("enableThisCategoryFirst")
|
if (confirm(chrome.i18n.getMessage("enableThisCategoryFirst")
|
||||||
.replace("{0}", chrome.i18n.getMessage("category_" + chosenCategory)))) {
|
.replace("{0}", chrome.i18n.getMessage("category_" + chosenCategory)))) {
|
||||||
// Open options page
|
// Open options page
|
||||||
chrome.runtime.sendMessage({message: "openConfig", hash: chosenCategory + "OptionsName"});
|
chrome.runtime.sendMessage({message: "openConfig", hash: "behavior"});
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as CompileConfig from "../config.json";
|
import * as CompileConfig from "../config.json";
|
||||||
import * as invidiousList from "../ci/invidiouslist.json";
|
import * as invidiousList from "../ci/invidiouslist.json";
|
||||||
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes } from "./types";
|
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind } from "./types";
|
||||||
|
import { keybindEquals } from "./utils/configUtils";
|
||||||
|
|
||||||
interface SBConfig {
|
interface SBConfig {
|
||||||
userID: string,
|
userID: string,
|
||||||
|
@ -11,9 +12,6 @@ interface SBConfig {
|
||||||
defaultCategory: Category,
|
defaultCategory: Category,
|
||||||
whitelistedChannels: string[],
|
whitelistedChannels: string[],
|
||||||
forceChannelCheck: boolean,
|
forceChannelCheck: boolean,
|
||||||
skipKeybind: string,
|
|
||||||
startSponsorKeybind: string,
|
|
||||||
submitKeybind: string,
|
|
||||||
minutesSaved: number,
|
minutesSaved: number,
|
||||||
skipCount: number,
|
skipCount: number,
|
||||||
sponsorTimesContributed: number,
|
sponsorTimesContributed: number,
|
||||||
|
@ -54,6 +52,11 @@ interface SBConfig {
|
||||||
},
|
},
|
||||||
scrollToEditTimeUpdate: boolean,
|
scrollToEditTimeUpdate: boolean,
|
||||||
categoryPillUpdate: boolean,
|
categoryPillUpdate: boolean,
|
||||||
|
darkMode: boolean,
|
||||||
|
|
||||||
|
skipKeybind: Keybind,
|
||||||
|
startSponsorKeybind: Keybind,
|
||||||
|
submitKeybind: Keybind,
|
||||||
|
|
||||||
// What categories should be skipped
|
// What categories should be skipped
|
||||||
categorySelections: CategorySelection[],
|
categorySelections: CategorySelection[],
|
||||||
|
@ -170,9 +173,6 @@ const Config: SBObject = {
|
||||||
defaultCategory: "chooseACategory" as Category,
|
defaultCategory: "chooseACategory" as Category,
|
||||||
whitelistedChannels: [],
|
whitelistedChannels: [],
|
||||||
forceChannelCheck: false,
|
forceChannelCheck: false,
|
||||||
skipKeybind: "Enter",
|
|
||||||
startSponsorKeybind: ";",
|
|
||||||
submitKeybind: "'",
|
|
||||||
minutesSaved: 0,
|
minutesSaved: 0,
|
||||||
skipCount: 0,
|
skipCount: 0,
|
||||||
sponsorTimesContributed: 0,
|
sponsorTimesContributed: 0,
|
||||||
|
@ -208,6 +208,18 @@ const Config: SBObject = {
|
||||||
autoSkipOnMusicVideos: false,
|
autoSkipOnMusicVideos: false,
|
||||||
scrollToEditTimeUpdate: false, // false means the tooltip will be shown
|
scrollToEditTimeUpdate: false, // false means the tooltip will be shown
|
||||||
categoryPillUpdate: false,
|
categoryPillUpdate: false,
|
||||||
|
darkMode: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default keybinds should not set "code" as that's gonna be different based on the user's locale. They should also only use EITHER ctrl OR alt modifiers (or none).
|
||||||
|
* Using ctrl+alt, or shift may produce a different character that we will not be able to recognize in different locales.
|
||||||
|
* The exception for shift is letters, where it only capitalizes. So shift+A is fine, but shift+1 isn't.
|
||||||
|
* Don't forget to add the new keybind to the checks in "KeybindDialogComponent.isKeybindAvailable()" and in "migrateOldFormats()"!
|
||||||
|
* TODO: Find a way to skip having to update these checks. Maybe storing keybinds in a Map?
|
||||||
|
*/
|
||||||
|
skipKeybind: {key: "Enter"},
|
||||||
|
startSponsorKeybind: {key: ";"},
|
||||||
|
submitKeybind: {key: "'"},
|
||||||
|
|
||||||
categorySelections: [{
|
categorySelections: [{
|
||||||
name: "sponsor" as Category,
|
name: "sponsor" as Category,
|
||||||
|
@ -450,6 +462,29 @@ function migrateOldFormats(config: SBConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof config["skipKeybind"] == "string") {
|
||||||
|
config["skipKeybind"] = {key: config["skipKeybind"]};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof config["startSponsorKeybind"] == "string") {
|
||||||
|
config["startSponsorKeybind"] = {key: config["startSponsorKeybind"]};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof config["submitKeybind"] == "string") {
|
||||||
|
config["submitKeybind"] = {key: config["submitKeybind"]};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbind key if it matches a previous one set by the user (should be ordered oldest to newest)
|
||||||
|
const keybinds = ["skipKeybind", "startSponsorKeybind", "submitKeybind"];
|
||||||
|
for (let i = keybinds.length-1; i >= 0; i--) {
|
||||||
|
for (let j = 0; j < keybinds.length; j++) {
|
||||||
|
if (i == j)
|
||||||
|
continue;
|
||||||
|
if (keybindEquals(config[keybinds[i]], config[keybinds[j]]))
|
||||||
|
config[keybinds[i]] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove some old unused options
|
// Remove some old unused options
|
||||||
if (config["sponsorVideoID"] !== undefined) {
|
if (config["sponsorVideoID"] !== undefined) {
|
||||||
chrome.storage.sync.remove("sponsorVideoID");
|
chrome.storage.sync.remove("sponsorVideoID");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, VideoInfo, StorageChangesObject, ChannelIDInfo, ChannelIDStatus, SponsorSourceType, SegmentUUID, Category, SkipToTimeParams, ToggleSkippable, ActionType, ScheduledTime } from "./types";
|
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, VideoInfo, StorageChangesObject, ChannelIDInfo, ChannelIDStatus, SponsorSourceType, SegmentUUID, Category, SkipToTimeParams, ToggleSkippable, ActionType, ScheduledTime } from "./types";
|
||||||
|
|
||||||
import { ContentContainer } from "./types";
|
import { ContentContainer, Keybind } from "./types";
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import * as Chat from "./js-components/chat";
|
||||||
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
||||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||||
import { findValidElement, getControls, getHashParams, isVisible } from "./utils/pageUtils";
|
import { findValidElement, getControls, getHashParams, isVisible } from "./utils/pageUtils";
|
||||||
|
import { keybindEquals } from "./utils/configUtils";
|
||||||
import { CategoryPill } from "./render/CategoryPill";
|
import { CategoryPill } from "./render/CategoryPill";
|
||||||
import { AnimationUtils } from "./utils/animationUtils";
|
import { AnimationUtils } from "./utils/animationUtils";
|
||||||
import { GenericUtils } from "./utils/genericUtils";
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
|
@ -135,6 +136,9 @@ const manualSkipPercentCount = 0.5;
|
||||||
//get messages from the background script and the popup
|
//get messages from the background script and the popup
|
||||||
chrome.runtime.onMessage.addListener(messageListener);
|
chrome.runtime.onMessage.addListener(messageListener);
|
||||||
|
|
||||||
|
//store pressed modifier keys
|
||||||
|
const pressedKeys = new Set();
|
||||||
|
|
||||||
function messageListener(request: Message, sender: unknown, sendResponse: (response: MessageResponse) => void): void | boolean {
|
function messageListener(request: Message, sender: unknown, sendResponse: (response: MessageResponse) => void): void | boolean {
|
||||||
//messages from popup script
|
//messages from popup script
|
||||||
switch(request.message){
|
switch(request.message){
|
||||||
|
@ -1279,7 +1283,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||||
&& skippingSegments.length === 1
|
&& skippingSegments.length === 1
|
||||||
&& skippingSegments[0].actionType === ActionType.Poi) {
|
&& skippingSegments[0].actionType === ActionType.Poi) {
|
||||||
skipButtonControlBar.enable(skippingSegments[0]);
|
skipButtonControlBar.enable(skippingSegments[0]);
|
||||||
if (onMobileYouTube) skipButtonControlBar.setShowKeybindHint(false);
|
if (onMobileYouTube || Config.config.skipKeybind == null) skipButtonControlBar.setShowKeybindHint(false);
|
||||||
|
|
||||||
activeSkipKeybindElement?.setShowKeybindHint(false);
|
activeSkipKeybindElement?.setShowKeybindHint(false);
|
||||||
activeSkipKeybindElement = skipButtonControlBar;
|
activeSkipKeybindElement = skipButtonControlBar;
|
||||||
|
@ -1288,7 +1292,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||||
//send out the message saying that a sponsor message was skipped
|
//send out the message saying that a sponsor message was skipped
|
||||||
if (!Config.config.dontShowNotice || !autoSkip) {
|
if (!Config.config.dontShowNotice || !autoSkip) {
|
||||||
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime);
|
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime);
|
||||||
if (onMobileYouTube) newSkipNotice.setShowKeybindHint(false);
|
if (onMobileYouTube || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
|
||||||
skipNotices.push(newSkipNotice);
|
skipNotices.push(newSkipNotice);
|
||||||
|
|
||||||
activeSkipKeybindElement?.setShowKeybindHint(false);
|
activeSkipKeybindElement?.setShowKeybindHint(false);
|
||||||
|
@ -1894,30 +1898,46 @@ function addPageListeners(): void {
|
||||||
|
|
||||||
function addHotkeyListener(): void {
|
function addHotkeyListener(): void {
|
||||||
document.addEventListener("keydown", hotkeyListener);
|
document.addEventListener("keydown", hotkeyListener);
|
||||||
|
document.addEventListener("keyup", (e) => pressedKeys.delete(e.key));
|
||||||
}
|
}
|
||||||
|
|
||||||
function hotkeyListener(e: KeyboardEvent): void {
|
function hotkeyListener(e: KeyboardEvent): void {
|
||||||
if (["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|
if (["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|
||||||
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return;
|
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return;
|
||||||
|
|
||||||
const key = e.key;
|
if (["Alt", "Control", "Shift", "AltGraph"].includes(e.key)) {
|
||||||
|
pressedKeys.add(e.key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key:Keybind = {key: e.key, code: e.code, alt: pressedKeys.has("Alt"), ctrl: pressedKeys.has("Control"), shift: pressedKeys.has("Shift")};
|
||||||
|
|
||||||
const skipKey = Config.config.skipKeybind;
|
const skipKey = Config.config.skipKeybind;
|
||||||
const startSponsorKey = Config.config.startSponsorKeybind;
|
const startSponsorKey = Config.config.startSponsorKeybind;
|
||||||
const submitKey = Config.config.submitKeybind;
|
const submitKey = Config.config.submitKeybind;
|
||||||
|
|
||||||
switch (key) {
|
if (!pressedKeys.has("AltGraph")) {
|
||||||
case skipKey:
|
if (keybindEquals(key, skipKey)) {
|
||||||
if (activeSkipKeybindElement) {
|
if (activeSkipKeybindElement)
|
||||||
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
|
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
|
||||||
}
|
return;
|
||||||
break;
|
} else if (keybindEquals(key, startSponsorKey)) {
|
||||||
case startSponsorKey:
|
|
||||||
startOrEndTimingNewSegment();
|
startOrEndTimingNewSegment();
|
||||||
break;
|
return;
|
||||||
case submitKey:
|
} else if (keybindEquals(key, submitKey)) {
|
||||||
|
submitSponsorTimes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//legacy - to preserve keybinds for skipKey, startSponsorKey and submitKey for people who set it before the update. (shouldn't be changed for future keybind options)
|
||||||
|
if (key.key == skipKey?.key && skipKey.code == null && !keybindEquals(Config.defaults.skipKeybind, skipKey)) {
|
||||||
|
if (activeSkipKeybindElement)
|
||||||
|
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
|
||||||
|
} else if (key.key == startSponsorKey?.key && startSponsorKey.code == null && !keybindEquals(Config.defaults.startSponsorKeybind, startSponsorKey)) {
|
||||||
|
startOrEndTimingNewSegment();
|
||||||
|
} else if (key.key == submitKey?.key && submitKey.code == null && !keybindEquals(Config.defaults.submitKeybind, submitKey)) {
|
||||||
submitSponsorTimes();
|
submitSponsorTimes();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,10 @@ async function init() {
|
||||||
|
|
||||||
await utils.wait(() => Config.config !== null);
|
await utils.wait(() => Config.config !== null);
|
||||||
|
|
||||||
|
if (!Config.config.darkMode) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
|
}
|
||||||
|
|
||||||
if (!showDonationLink()) {
|
if (!showDonationLink()) {
|
||||||
document.getElementById("sbDonate").style.display = "none";
|
document.getElementById("sbDonate").style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
import { SponsorTime } from "../types";
|
import { SponsorTime } from "../types";
|
||||||
import { getSkippingText } from "../utils/categoryUtils";
|
import { getSkippingText } from "../utils/categoryUtils";
|
||||||
|
import { keybindToString } from "../utils/configUtils";
|
||||||
|
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
import { AnimationUtils } from "../utils/animationUtils";
|
import { AnimationUtils } from "../utils/animationUtils";
|
||||||
|
@ -180,7 +181,7 @@ export class SkipButtonControlBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTitle(): string {
|
private getTitle(): string {
|
||||||
return getSkippingText([this.segment], false) + (this.showKeybindHint ? " (" + Config.config.skipKeybind + ")" : "");
|
return getSkippingText([this.segment], false) + (this.showKeybindHint ? " (" + keybindToString(Config.config.skipKeybind) + ")" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChapterPrefix(): HTMLElement {
|
private getChapterPrefix(): HTMLElement {
|
||||||
|
|
372
src/options.ts
372
src/options.ts
|
@ -1,3 +1,6 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
|
||||||
import Config from "./config";
|
import Config from "./config";
|
||||||
import * as CompileConfig from "../config.json";
|
import * as CompileConfig from "../config.json";
|
||||||
import * as invidiousList from "../ci/invidiouslist.json";
|
import * as invidiousList from "../ci/invidiouslist.json";
|
||||||
|
@ -7,21 +10,38 @@ window.SB = Config;
|
||||||
|
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
import CategoryChooser from "./render/CategoryChooser";
|
import CategoryChooser from "./render/CategoryChooser";
|
||||||
|
import KeybindComponent from "./components/KeybindComponent";
|
||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
let embed = false;
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', init);
|
window.addEventListener('DOMContentLoaded', init);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
utils.localizeHtmlPage();
|
utils.localizeHtmlPage();
|
||||||
|
|
||||||
|
// selected tab
|
||||||
|
if (location.hash != "") {
|
||||||
|
const substr = location.hash.substring(1);
|
||||||
|
let menuItem = document.querySelector(`[data-for='${substr}']`);
|
||||||
|
if (menuItem == null)
|
||||||
|
menuItem = document.querySelector(`[data-for='behavior']`);
|
||||||
|
menuItem.classList.add("selected");
|
||||||
|
} else {
|
||||||
|
document.querySelector(`[data-for='behavior']`).classList.add("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("version").innerText = "v. " + chrome.runtime.getManifest().version;
|
||||||
|
|
||||||
// Remove header if needed
|
// Remove header if needed
|
||||||
if (window.location.hash === "#embed") {
|
if (window.location.hash === "#embed") {
|
||||||
|
embed = true;
|
||||||
for (const element of document.getElementsByClassName("titleBar")) {
|
for (const element of document.getElementsByClassName("titleBar")) {
|
||||||
element.classList.add("hidden");
|
element.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("options").classList.add("embed");
|
document.getElementById("options").classList.add("embed");
|
||||||
|
createStickyHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.configListeners.includes(optionsConfigUpdateListener)) {
|
if (!Config.configListeners.includes(optionsConfigUpdateListener)) {
|
||||||
|
@ -30,8 +50,12 @@ async function init() {
|
||||||
|
|
||||||
await utils.wait(() => Config.config !== null);
|
await utils.wait(() => Config.config !== null);
|
||||||
|
|
||||||
|
if (!Config.config.darkMode) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
|
}
|
||||||
|
|
||||||
if (!showDonationLink()) {
|
if (!showDonationLink()) {
|
||||||
document.getElementById("sbDonate").style.visibility = "hidden";
|
document.getElementById("sbDonate").classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all of the toggle options to the correct option
|
// Set all of the toggle options to the correct option
|
||||||
|
@ -39,31 +63,31 @@ async function init() {
|
||||||
const optionsElements = optionsContainer.querySelectorAll("*");
|
const optionsElements = optionsContainer.querySelectorAll("*");
|
||||||
|
|
||||||
for (let i = 0; i < optionsElements.length; i++) {
|
for (let i = 0; i < optionsElements.length; i++) {
|
||||||
if ((optionsElements[i].getAttribute("private-mode-only") === "true" && !(await isIncognitoAllowed()))
|
const dependentOnName = optionsElements[i].getAttribute("data-dependent-on");
|
||||||
|| (optionsElements[i].getAttribute("no-safari") === "true" && navigator.vendor === "Apple Computer, Inc.")
|
const dependentOn = optionsContainer.querySelector(`[data-sync='${dependentOnName}']`);
|
||||||
|| (optionsElements[i].getAttribute("if-false") && Config.config[optionsElements[i].getAttribute("if-false")])) {
|
let isDependentOnReversed = false;
|
||||||
optionsElements[i].classList.add("hidden");
|
if (dependentOn)
|
||||||
|
isDependentOnReversed = dependentOn.getAttribute("data-toggle-type") === "reverse" || optionsElements[i].getAttribute("data-dependent-on-inverted") === "true";
|
||||||
|
|
||||||
|
if (await shouldHideOption(optionsElements[i]) || (dependentOn && (isDependentOnReversed ? Config.config[dependentOnName] : !Config.config[dependentOnName]))) {
|
||||||
|
optionsElements[i].classList.add("hidden", "hiding");
|
||||||
|
if (!dependentOn)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = optionsElements[i].getAttribute("sync-option");
|
const option = optionsElements[i].getAttribute("data-sync");
|
||||||
|
|
||||||
switch (optionsElements[i].getAttribute("option-type")) {
|
switch (optionsElements[i].getAttribute("data-type")) {
|
||||||
case "toggle": {
|
case "toggle": {
|
||||||
const optionResult = Config.config[option];
|
const optionResult = Config.config[option];
|
||||||
|
|
||||||
const checkbox = optionsElements[i].querySelector("input");
|
const checkbox = optionsElements[i].querySelector("input");
|
||||||
const reverse = optionsElements[i].getAttribute("toggle-type") === "reverse";
|
const reverse = optionsElements[i].getAttribute("data-toggle-type") === "reverse";
|
||||||
|
|
||||||
const confirmMessage = optionsElements[i].getAttribute("confirm-message");
|
const confirmMessage = optionsElements[i].getAttribute("data-confirm-message");
|
||||||
|
|
||||||
if (optionResult != undefined) {
|
if (optionResult != undefined)
|
||||||
checkbox.checked = optionResult;
|
checkbox.checked = reverse ? !optionResult : optionResult;
|
||||||
|
|
||||||
if (reverse) {
|
|
||||||
optionsElements[i].querySelector("input").checked = !optionResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if anything extra should be run first time
|
// See if anything extra should be run first time
|
||||||
switch (option) {
|
switch (option) {
|
||||||
|
@ -73,7 +97,7 @@ async function init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add click listener
|
// Add click listener
|
||||||
checkbox.addEventListener("click", () => {
|
checkbox.addEventListener("click", async () => {
|
||||||
// Confirm if required
|
// Confirm if required
|
||||||
if (checkbox.checked && confirmMessage && !confirm(chrome.i18n.getMessage(confirmMessage))){
|
if (checkbox.checked && confirmMessage && !confirm(chrome.i18n.getMessage(confirmMessage))){
|
||||||
checkbox.checked = false;
|
checkbox.checked = false;
|
||||||
|
@ -92,11 +116,36 @@ async function init() {
|
||||||
// Enable the notice
|
// Enable the notice
|
||||||
Config.config["dontShowNotice"] = false;
|
Config.config["dontShowNotice"] = false;
|
||||||
|
|
||||||
const showNoticeSwitch = <HTMLInputElement> document.querySelector("[sync-option='dontShowNotice'] > label > label > input");
|
const showNoticeSwitch = <HTMLInputElement> document.querySelector("[data-sync='dontShowNotice'] > div > label > input");
|
||||||
showNoticeSwitch.checked = true;
|
showNoticeSwitch.checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case "showDonationLink":
|
||||||
|
if (checkbox.checked)
|
||||||
|
document.getElementById("sbDonate").classList.add("hidden");
|
||||||
|
else
|
||||||
|
document.getElementById("sbDonate").classList.remove("hidden");
|
||||||
|
break;
|
||||||
|
case "darkMode":
|
||||||
|
if (checkbox.checked) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If other options depend on this, hide/show them
|
||||||
|
const dependents = optionsContainer.querySelectorAll(`[data-dependent-on='${option}']`);
|
||||||
|
for (let j = 0; j < dependents.length; j++) {
|
||||||
|
const disableWhenChecked = dependents[j].getAttribute("data-dependent-on-inverted") === "true";
|
||||||
|
if (!await shouldHideOption(dependents[j]) && (!disableWhenChecked && checkbox.checked || disableWhenChecked && !checkbox.checked)) {
|
||||||
|
dependents[j].classList.remove("hidden");
|
||||||
|
setTimeout(() => dependents[j].classList.remove("hiding"), 1);
|
||||||
|
} else {
|
||||||
|
dependents[j].classList.add("hiding");
|
||||||
|
setTimeout(() => dependents[j].classList.add("hidden"), 400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -155,7 +204,15 @@ async function init() {
|
||||||
const button = optionsElements[i].querySelector(".trigger-button");
|
const button = optionsElements[i].querySelector(".trigger-button");
|
||||||
button.addEventListener("click", () => activatePrivateTextChange(<HTMLElement> optionsElements[i]));
|
button.addEventListener("click", () => activatePrivateTextChange(<HTMLElement> optionsElements[i]));
|
||||||
|
|
||||||
const privateTextChangeOption = optionsElements[i].getAttribute("sync-option");
|
if (option == "*") {
|
||||||
|
const downloadButton = optionsElements[i].querySelector(".download-button");
|
||||||
|
downloadButton.addEventListener("click", downloadConfig);
|
||||||
|
|
||||||
|
const uploadButton = optionsElements[i].querySelector(".upload-button");
|
||||||
|
uploadButton.addEventListener("change", (e) => uploadConfig(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateTextChangeOption = optionsElements[i].getAttribute("data-sync");
|
||||||
// See if anything extra must be done
|
// See if anything extra must be done
|
||||||
switch (privateTextChangeOption) {
|
switch (privateTextChangeOption) {
|
||||||
case "invidiousInstances":
|
case "invidiousInstances":
|
||||||
|
@ -167,7 +224,7 @@ async function init() {
|
||||||
case "button-press": {
|
case "button-press": {
|
||||||
const actionButton = optionsElements[i].querySelector(".trigger-button");
|
const actionButton = optionsElements[i].querySelector(".trigger-button");
|
||||||
|
|
||||||
switch(optionsElements[i].getAttribute("sync-option")) {
|
switch(optionsElements[i].getAttribute("data-sync")) {
|
||||||
case "copyDebugInformation":
|
case "copyDebugInformation":
|
||||||
actionButton.addEventListener("click", copyDebugOutputToClipboard);
|
actionButton.addEventListener("click", copyDebugOutputToClipboard);
|
||||||
break;
|
break;
|
||||||
|
@ -176,9 +233,7 @@ async function init() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "keybind-change": {
|
case "keybind-change": {
|
||||||
const keybindButton = optionsElements[i].querySelector(".trigger-button");
|
ReactDOM.render(React.createElement(KeybindComponent, {option: option}), optionsElements[i].querySelector("div"));
|
||||||
keybindButton.addEventListener("click", () => activateKeybindChange(<HTMLElement> optionsElements[i]));
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "display": {
|
case "display": {
|
||||||
|
@ -220,10 +275,57 @@ async function init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optionsContainer.classList.remove("hidden");
|
// Tab interaction
|
||||||
|
const tabElements = document.getElementsByClassName("tab-heading");
|
||||||
|
for (let i = 0; i < tabElements.length; i++) {
|
||||||
|
const tabFor = tabElements[i].getAttribute("data-for");
|
||||||
|
|
||||||
|
if (tabElements[i].classList.contains("selected"))
|
||||||
|
document.getElementById(tabFor).classList.remove("hidden");
|
||||||
|
|
||||||
|
tabElements[i].addEventListener("click", () => {
|
||||||
|
if (!embed) location.hash = tabFor;
|
||||||
|
|
||||||
|
createStickyHeader();
|
||||||
|
|
||||||
|
document.querySelectorAll(".tab-heading").forEach(element => { element.classList.remove("selected"); });
|
||||||
|
optionsContainer.querySelectorAll(".option-group").forEach(element => { element.classList.add("hidden"); });
|
||||||
|
|
||||||
|
tabElements[i].classList.add("selected");
|
||||||
|
document.getElementById(tabFor).classList.remove("hidden");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", () => createStickyHeader());
|
||||||
|
|
||||||
optionsContainer.classList.add("animated");
|
optionsContainer.classList.add("animated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createStickyHeader() {
|
||||||
|
const container = document.getElementById("options-container");
|
||||||
|
const options = document.getElementById("options");
|
||||||
|
|
||||||
|
if (!embed && window.pageYOffset > 90 && (window.innerHeight <= 770 || window.innerWidth <= 1200)) {
|
||||||
|
if (!container.classList.contains("sticky")) {
|
||||||
|
options.style.marginTop = options.offsetTop.toString()+"px";
|
||||||
|
container.classList.add("sticky");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
options.style.marginTop = "unset";
|
||||||
|
container.classList.remove("sticky");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle special cases where an option shouldn't show
|
||||||
|
*
|
||||||
|
* @param {String} element
|
||||||
|
*/
|
||||||
|
async function shouldHideOption(element: Element): Promise<boolean> {
|
||||||
|
return (element.getAttribute("data-private-only") === "true" && !(await isIncognitoAllowed()))
|
||||||
|
|| (element.getAttribute("data-no-safari") === "true" && navigator.vendor === "Apple Computer, Inc.");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the config is updated
|
* Called when the config is updated
|
||||||
*
|
*
|
||||||
|
@ -234,7 +336,7 @@ function optionsConfigUpdateListener() {
|
||||||
const optionsElements = optionsContainer.querySelectorAll("*");
|
const optionsElements = optionsContainer.querySelectorAll("*");
|
||||||
|
|
||||||
for (let i = 0; i < optionsElements.length; i++) {
|
for (let i = 0; i < optionsElements.length; i++) {
|
||||||
switch (optionsElements[i].getAttribute("option-type")) {
|
switch (optionsElements[i].getAttribute("data-type")) {
|
||||||
case "display":
|
case "display":
|
||||||
updateDisplayElement(<HTMLElement> optionsElements[i])
|
updateDisplayElement(<HTMLElement> optionsElements[i])
|
||||||
}
|
}
|
||||||
|
@ -247,17 +349,27 @@ function optionsConfigUpdateListener() {
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
function updateDisplayElement(element: HTMLElement) {
|
function updateDisplayElement(element: HTMLElement) {
|
||||||
const displayOption = element.getAttribute("sync-option")
|
const displayOption = element.getAttribute("data-sync")
|
||||||
const displayText = Config.config[displayOption];
|
const displayText = Config.config[displayOption];
|
||||||
element.innerText = displayText;
|
element.innerText = displayText;
|
||||||
|
|
||||||
// See if anything extra must be run
|
// See if anything extra must be run
|
||||||
switch (displayOption) {
|
switch (displayOption) {
|
||||||
case "invidiousInstances":
|
case "invidiousInstances": {
|
||||||
element.innerText = displayText.join(', ');
|
element.innerText = displayText.join(', ');
|
||||||
|
let allEquals = displayText.length == invidiousList.length;
|
||||||
|
for (let i = 0; i < invidiousList.length && allEquals; i++) {
|
||||||
|
if (displayText[i] != invidiousList[i])
|
||||||
|
allEquals = false;
|
||||||
|
}
|
||||||
|
if (!allEquals) {
|
||||||
|
const resetButton = element.parentElement.querySelector(".invidious-instance-reset");
|
||||||
|
resetButton.classList.remove("hidden");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the option to add Invidious instances
|
* Initializes the option to add Invidious instances
|
||||||
|
@ -270,6 +382,8 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
||||||
const button = element.querySelector(".trigger-button");
|
const button = element.querySelector(".trigger-button");
|
||||||
|
|
||||||
const setButton = element.querySelector(".text-change-set");
|
const setButton = element.querySelector(".text-change-set");
|
||||||
|
const cancelButton = element.querySelector(".text-change-reset");
|
||||||
|
const resetButton = element.querySelector(".invidious-instance-reset");
|
||||||
setButton.addEventListener("click", async function() {
|
setButton.addEventListener("click", async function() {
|
||||||
if (textBox.value == "" || textBox.value.includes("/") || textBox.value.includes("http")) {
|
if (textBox.value == "" || textBox.value.includes("/") || textBox.value.includes("http")) {
|
||||||
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
|
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
|
||||||
|
@ -287,19 +401,26 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
|
||||||
|
|
||||||
invidiousOnClick(checkbox, "supportInvidious");
|
invidiousOnClick(checkbox, "supportInvidious");
|
||||||
|
|
||||||
textBox.value = "";
|
resetButton.classList.remove("hidden");
|
||||||
|
|
||||||
// Hide this section again
|
// Hide this section again
|
||||||
|
textBox.value = "";
|
||||||
element.querySelector(".option-hidden-section").classList.add("hidden");
|
element.querySelector(".option-hidden-section").classList.add("hidden");
|
||||||
button.classList.remove("disabled");
|
button.classList.remove("disabled");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetButton = element.querySelector(".invidious-instance-reset");
|
cancelButton.addEventListener("click", async function() {
|
||||||
|
textBox.value = "";
|
||||||
|
element.querySelector(".option-hidden-section").classList.add("hidden");
|
||||||
|
button.classList.remove("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
resetButton.addEventListener("click", function() {
|
resetButton.addEventListener("click", function() {
|
||||||
if (confirm(chrome.i18n.getMessage("resetInvidiousInstanceAlert"))) {
|
if (confirm(chrome.i18n.getMessage("resetInvidiousInstanceAlert"))) {
|
||||||
// Set to CI populated list
|
// Set to CI populated list
|
||||||
Config.config[option] = invidiousList;
|
Config.config[option] = invidiousList;
|
||||||
|
resetButton.classList.add("hidden");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -351,91 +472,6 @@ async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Pro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Will trigger the container to ask the user for a keybind.
|
|
||||||
*
|
|
||||||
* @param element
|
|
||||||
*/
|
|
||||||
function activateKeybindChange(element: HTMLElement) {
|
|
||||||
const button = element.querySelector(".trigger-button");
|
|
||||||
if (button.classList.contains("disabled")) return;
|
|
||||||
|
|
||||||
button.classList.add("disabled");
|
|
||||||
|
|
||||||
const option = element.getAttribute("sync-option");
|
|
||||||
|
|
||||||
const currentlySet = Config.config[option] !== null ? chrome.i18n.getMessage("keybindCurrentlySet") : "";
|
|
||||||
|
|
||||||
const status = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status");
|
|
||||||
status.innerText = chrome.i18n.getMessage("keybindDescription") + currentlySet;
|
|
||||||
|
|
||||||
if (Config.config[option] !== null) {
|
|
||||||
const statusKey = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status-key");
|
|
||||||
statusKey.innerText = Config.config[option];
|
|
||||||
}
|
|
||||||
|
|
||||||
element.querySelector(".option-hidden-section").classList.remove("hidden");
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => keybindKeyPressed(element, e), {once: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a key is pressed in an activiated keybind change option.
|
|
||||||
*
|
|
||||||
* @param element
|
|
||||||
* @param e
|
|
||||||
*/
|
|
||||||
function keybindKeyPressed(element: HTMLElement, e: KeyboardEvent) {
|
|
||||||
const key = e.key;
|
|
||||||
|
|
||||||
if (["Shift", "Control", "Meta", "Alt", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab"].indexOf(key) !== -1) {
|
|
||||||
|
|
||||||
// Wait for more
|
|
||||||
document.addEventListener("keydown", (e) => keybindKeyPressed(element, e), {once: true});
|
|
||||||
} else {
|
|
||||||
const button: HTMLElement = element.querySelector(".trigger-button");
|
|
||||||
const option = element.getAttribute("sync-option");
|
|
||||||
|
|
||||||
// Make sure keybind isn't used by the other listener
|
|
||||||
// TODO: If other keybindings are going to be added, we need a better way to find the other keys used.
|
|
||||||
const otherKeybind = (option === "startSponsorKeybind") ? Config.config['submitKeybind'] : Config.config['startSponsorKeybind'];
|
|
||||||
if (key === otherKeybind) {
|
|
||||||
closeKeybindOption(element, button);
|
|
||||||
|
|
||||||
alert(chrome.i18n.getMessage("theKey") + " " + key + " " + chrome.i18n.getMessage("keyAlreadyUsed"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel setting a keybind
|
|
||||||
if (key === "Escape") {
|
|
||||||
closeKeybindOption(element, button);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.config[option] = key;
|
|
||||||
|
|
||||||
const status = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status");
|
|
||||||
status.innerText = chrome.i18n.getMessage("keybindDescriptionComplete");
|
|
||||||
|
|
||||||
const statusKey = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status-key");
|
|
||||||
statusKey.innerText = key;
|
|
||||||
|
|
||||||
button.classList.remove("disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the menu for editing the keybind
|
|
||||||
*
|
|
||||||
* @param element
|
|
||||||
* @param button
|
|
||||||
*/
|
|
||||||
function closeKeybindOption(element: HTMLElement, button: HTMLElement) {
|
|
||||||
element.querySelector(".option-hidden-section").classList.add("hidden");
|
|
||||||
button.classList.remove("disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will trigger the textbox to appear to be able to change an option's text.
|
* Will trigger the textbox to appear to be able to change an option's text.
|
||||||
*
|
*
|
||||||
|
@ -448,7 +484,7 @@ function activatePrivateTextChange(element: HTMLElement) {
|
||||||
button.classList.add("disabled");
|
button.classList.add("disabled");
|
||||||
|
|
||||||
const textBox = <HTMLInputElement> element.querySelector(".option-text-box");
|
const textBox = <HTMLInputElement> element.querySelector(".option-text-box");
|
||||||
const option = element.getAttribute("sync-option");
|
const option = element.getAttribute("data-sync");
|
||||||
|
|
||||||
// See if anything extra must be done
|
// See if anything extra must be done
|
||||||
switch (option) {
|
switch (option) {
|
||||||
|
@ -476,38 +512,7 @@ function activatePrivateTextChange(element: HTMLElement) {
|
||||||
|
|
||||||
const setButton = element.querySelector(".text-change-set");
|
const setButton = element.querySelector(".text-change-set");
|
||||||
setButton.addEventListener("click", async () => {
|
setButton.addEventListener("click", async () => {
|
||||||
const confirmMessage = element.getAttribute("confirm-message");
|
setTextOption(option, element, textBox.value);
|
||||||
|
|
||||||
if (confirmMessage === null || confirm(chrome.i18n.getMessage(confirmMessage))) {
|
|
||||||
|
|
||||||
// See if anything extra must be done
|
|
||||||
switch (option) {
|
|
||||||
case "*":
|
|
||||||
try {
|
|
||||||
const newConfig = JSON.parse(textBox.value);
|
|
||||||
for (const key in newConfig) {
|
|
||||||
Config.config[key] = newConfig[key];
|
|
||||||
}
|
|
||||||
Config.convertJSON();
|
|
||||||
|
|
||||||
if (newConfig.supportInvidious) {
|
|
||||||
const checkbox = <HTMLInputElement> document.querySelector("#support-invidious > label > label > input");
|
|
||||||
|
|
||||||
checkbox.checked = true;
|
|
||||||
await invidiousOnClick(checkbox, "supportInvidious");
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.reload();
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
alert(chrome.i18n.getMessage("incorrectlyFormattedOptions"));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Config.config[option] = textBox.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// See if anything extra must be done
|
// See if anything extra must be done
|
||||||
|
@ -531,6 +536,77 @@ function activatePrivateTextChange(element: HTMLElement) {
|
||||||
element.querySelector(".option-hidden-section").classList.remove("hidden");
|
element.querySelector(".option-hidden-section").classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to run when a textbox change is submitted
|
||||||
|
*
|
||||||
|
* @param option data-sync value
|
||||||
|
* @param element main container div
|
||||||
|
* @param value new text
|
||||||
|
* @param callbackOnError function to run if confirmMessage was denied
|
||||||
|
*/
|
||||||
|
async function setTextOption(option: string, element: HTMLElement, value: string, callbackOnError?: () => void) {
|
||||||
|
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 "*":
|
||||||
|
try {
|
||||||
|
const newConfig = JSON.parse(value);
|
||||||
|
for (const key in newConfig) {
|
||||||
|
Config.config[key] = newConfig[key];
|
||||||
|
}
|
||||||
|
Config.convertJSON();
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Config.config[option] = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof callbackOnError == "function")
|
||||||
|
callbackOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadConfig() {
|
||||||
|
const file = document.createElement("a");
|
||||||
|
const jsonData = JSON.parse(JSON.stringify(Config.localConfig));
|
||||||
|
jsonData.segmentTimes = Config.encodeStoredItem(Config.localConfig.segmentTimes);
|
||||||
|
file.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData)));
|
||||||
|
file.setAttribute("download", "SponsorBlockConfig.json");
|
||||||
|
document.body.append(file);
|
||||||
|
file.click();
|
||||||
|
file.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadConfig(e) {
|
||||||
|
if (e.target.files.length == 1) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
const element = document.querySelector("[data-sync='*']") as HTMLElement;
|
||||||
|
reader.onload = function(ev) {
|
||||||
|
setTextOption("*", element, ev.target.result as string, () => {
|
||||||
|
e.target.value = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the value used for the database server address.
|
* Validates the value used for the database server address.
|
||||||
* Returns null and alerts the user if there is an issue.
|
* Returns null and alerts the user if there is an issue.
|
||||||
|
|
|
@ -125,7 +125,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||||
unwhitelistChannel();
|
unwhitelistChannel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
PageElements.whitelistForceCheck.addEventListener("click", openOptions);
|
PageElements.whitelistForceCheck.addEventListener("click", () => {openOptionsAt("behavior")});
|
||||||
PageElements.toggleSwitch.addEventListener("change", function () {
|
PageElements.toggleSwitch.addEventListener("change", function () {
|
||||||
toggleSkipping(!this.checked);
|
toggleSkipping(!this.checked);
|
||||||
});
|
});
|
||||||
|
@ -516,6 +516,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||||
chrome.runtime.sendMessage({ "message": "openConfig" });
|
chrome.runtime.sendMessage({ "message": "openConfig" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openOptionsAt(location) {
|
||||||
|
chrome.runtime.sendMessage({ "message": "openConfig", "hash": location });
|
||||||
|
}
|
||||||
|
|
||||||
function openHelp() {
|
function openHelp() {
|
||||||
chrome.runtime.sendMessage({ "message": "openHelp" });
|
chrome.runtime.sendMessage({ "message": "openHelp" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,3 +220,11 @@ export enum NoticeVisbilityMode {
|
||||||
FadedForAutoSkip = 3,
|
FadedForAutoSkip = 3,
|
||||||
FadedForAll = 4
|
FadedForAll = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Keybind = {
|
||||||
|
key: string,
|
||||||
|
code?: string,
|
||||||
|
ctrl?: boolean,
|
||||||
|
alt?: boolean,
|
||||||
|
shift?: boolean
|
||||||
|
}
|
|
@ -1,5 +1,44 @@
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
|
import { Keybind } from "../types";
|
||||||
|
|
||||||
export function showDonationLink(): boolean {
|
export function showDonationLink(): boolean {
|
||||||
return navigator.vendor !== "Apple Computer, Inc." && Config.config.showDonationLink;
|
return navigator.vendor !== "Apple Computer, Inc." && Config.config.showDonationLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function keybindEquals(first: Keybind, second: Keybind): boolean {
|
||||||
|
if (first == null || second == null ||
|
||||||
|
Boolean(first.alt) != Boolean(second.alt) || Boolean(first.ctrl) != Boolean(second.ctrl) || Boolean(first.shift) != Boolean(second.shift) ||
|
||||||
|
first.key == null && first.code == null || second.key == null && second.code == null)
|
||||||
|
return false;
|
||||||
|
if (first.code != null && second.code != null)
|
||||||
|
return first.code === second.code;
|
||||||
|
if (first.key != null && second.key != null)
|
||||||
|
return first.key.toUpperCase() === second.key.toUpperCase();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatKey(key: string): string {
|
||||||
|
if (key == null)
|
||||||
|
return "";
|
||||||
|
else if (key == " ")
|
||||||
|
return "Space";
|
||||||
|
else if (key.length == 1)
|
||||||
|
return key.toUpperCase();
|
||||||
|
else
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keybindToString(keybind: Keybind): string {
|
||||||
|
if (keybind == null || keybind.key == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
let ret = "";
|
||||||
|
if (keybind.ctrl)
|
||||||
|
ret += "Ctrl+";
|
||||||
|
if (keybind.alt)
|
||||||
|
ret += "Alt+";
|
||||||
|
if (keybind.shift)
|
||||||
|
ret += "Shift+";
|
||||||
|
|
||||||
|
return ret += formatKey(keybind.key);
|
||||||
|
}
|
Loading…
Reference in a new issue