mirror of https://github.com/FreeTubeApp/FreeTube
update linters & add stylelint (#3023)
* update linters, add stylelint, switch from sass to scss * remove unused babel-eslint module * fix spacing in scss files * dont use npm in script calls * dont error for `:deep` selector in css
This commit is contained in:
parent
bc44e27469
commit
43a25f8738
32
.eslintrc.js
32
.eslintrc.js
|
@ -11,13 +11,24 @@ module.exports = {
|
|||
// https://eslint.org/docs/user-guide/configuring#specifying-parser
|
||||
parser: 'vue-eslint-parser',
|
||||
|
||||
// https://vuejs.github.io/eslint-plugin-vue/user-guide/#faq
|
||||
// https://eslint.vuejs.org/user-guide/#faq
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
ecmaVersion: 2018,
|
||||
parser: '@babel/eslint-parser',
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module'
|
||||
},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'no-tabs': 'off',
|
||||
'comma-spacing': 'off'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// https://eslint.org/docs/user-guide/configuring#extending-configuration-files
|
||||
// order matters: from least important to most important in terms of overriding
|
||||
// Prettier + Vue: https://medium.com/@gogl.alex/how-to-properly-set-up-eslint-with-prettier-for-vue-or-nuxt-in-vscode-e42532099a9c
|
||||
|
@ -25,12 +36,13 @@ module.exports = {
|
|||
'prettier',
|
||||
'eslint:recommended',
|
||||
'plugin:vue/recommended',
|
||||
'standard'
|
||||
'standard',
|
||||
'plugin:jsonc/recommended-with-json',
|
||||
// 'plugin:vuejs-accessibility/recommended' // uncomment once issues are fixed
|
||||
],
|
||||
|
||||
// https://eslint.org/docs/user-guide/configuring#configuring-plugins
|
||||
plugins: ['vue', 'vuejs-accessibility'],
|
||||
plugins: ['vue', 'vuejs-accessibility', 'n', 'unicorn'],
|
||||
|
||||
rules: {
|
||||
'space-before-function-paren': 'off',
|
||||
|
@ -39,6 +51,7 @@ module.exports = {
|
|||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-unused-vars': 'warn',
|
||||
'no-undef': 'warn',
|
||||
'object-shorthand': 'off',
|
||||
'vue/no-template-key': 'warn',
|
||||
'vue/no-useless-template-attributes': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
|
@ -47,6 +60,13 @@ module.exports = {
|
|||
required: {
|
||||
some: ['nesting', 'id']
|
||||
}
|
||||
}]
|
||||
}],
|
||||
'n/no-callback-literal': 'warn',
|
||||
'n/no-path-concat': 'warn',
|
||||
'unicorn/better-regex': 'error',
|
||||
'unicorn/no-array-push-push': 'error',
|
||||
'unicorn/prefer-keyboard-event-key': 'error',
|
||||
'unicorn/prefer-regexp-test': 'error',
|
||||
'unicorn/prefer-string-replace-all': 'error'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
src/data/
|
||||
src/datastores/
|
||||
src/main/
|
||||
src/renderer/videoJS.css
|
||||
dist/
|
||||
static/
|
||||
node_modules/
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"plugins": ["stylelint-high-performance-animation", "@double-great/stylelint-a11y"],
|
||||
"extends": ["stylelint-config-standard", "stylelint-config-sass-guidelines"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.scss"],
|
||||
"customSyntax": "postcss-scss",
|
||||
"rules": {
|
||||
"max-nesting-depth": null,
|
||||
"selector-max-compound-selectors": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.css"],
|
||||
"rules": {
|
||||
"a11y/media-prefers-reduced-motion": true,
|
||||
"a11y/no-outline-none": true,
|
||||
"a11y/selector-pseudo-class-focus": true,
|
||||
"a11y/font-size-is-readable": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"selector-class-pattern": null,
|
||||
"selector-id-pattern": null,
|
||||
"plugin/no-low-performance-animation-properties": true,
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoClasses": ["deep"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ const config = {
|
|||
loader: 'vue-loader',
|
||||
},
|
||||
{
|
||||
test: /\.s(c|a)ss$/,
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
|
@ -62,11 +62,7 @@ const config = {
|
|||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
// eslint-disable-next-line
|
||||
implementation: require('sass'),
|
||||
sassOptions: {
|
||||
indentedSyntax: true
|
||||
}
|
||||
implementation: require('sass')
|
||||
}
|
||||
},
|
||||
],
|
||||
|
|
|
@ -39,7 +39,7 @@ const config = {
|
|||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.s(c|a)ss$/,
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
|
@ -53,11 +53,7 @@ const config = {
|
|||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
// eslint-disable-next-line
|
||||
implementation: require('sass'),
|
||||
sassOptions: {
|
||||
indentedSyntax: true
|
||||
}
|
||||
implementation: require('sass')
|
||||
}
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
# Refer for explanation to following link:
|
||||
# https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
|
||||
pre-commit:
|
||||
|
@ -12,8 +11,6 @@ pre-commit:
|
|||
skip:
|
||||
- rebase
|
||||
|
||||
|
||||
|
||||
# EXAMPLE USAGE
|
||||
#
|
||||
# pre-push:
|
||||
|
|
31
package.json
31
package.json
|
@ -31,8 +31,15 @@
|
|||
"dev": "run-s rebuild:electron dev-runner",
|
||||
"dev:web": "node _scripts/dev-runner.js --web",
|
||||
"dev-runner": "node _scripts/dev-runner.js",
|
||||
"lint-all": "run-p lint lint-json lint-style",
|
||||
"lint-fix": "eslint --fix --ext .js,.vue ./",
|
||||
"lint": "eslint --ext .js,.vue ./",
|
||||
"lint-json": "eslint --ext .json ./",
|
||||
"lint-style": "run-p lint-style:scss lint-style:css",
|
||||
"lint-style:scss": "stylelint \"**/*.scss\"",
|
||||
"lint-style:css": "stylelint \"**/*.css\"",
|
||||
"lint-style-fix:scss": "stylelint --fix \"**/*.scss\"",
|
||||
"lint-style-fix:css": "stylelint --fix \"**/*.css\"",
|
||||
"pack": "run-p pack:main pack:renderer",
|
||||
"pack:main": "webpack --mode=production --node-env=production --config _scripts/webpack.main.config.js",
|
||||
"pack:renderer": "webpack --mode=production --node-env=production --config _scripts/webpack.renderer.config.js",
|
||||
|
@ -81,23 +88,25 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"@double-great/stylelint-a11y": "^2.0.2",
|
||||
"babel-loader": "^9.1.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||
"electron": "^22.0.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsonc": "^2.5.0",
|
||||
"eslint-plugin-n": "^15.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-unicorn": "^45.0.2",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.0.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
|
@ -106,10 +115,16 @@
|
|||
"lefthook": "^1.2.4",
|
||||
"mini-css-extract-plugin": "^2.7.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.20",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.57.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"stylelint": "^14.16.1",
|
||||
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-high-performance-animation": "^1.7.0",
|
||||
"tree-kill": "1.2.2",
|
||||
"vue-devtools": "^5.1.4",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
|
|
|
@ -51,7 +51,7 @@ export class ImageCache {
|
|||
* @returns a timestamp in seconds
|
||||
*/
|
||||
export function extractExpiryTimestamp(headers) {
|
||||
const maxAgeRegex = /max-age=([0-9]+)/
|
||||
const maxAgeRegex = /max-age=(\d+)/
|
||||
|
||||
const cacheControl = headers['cache-control']
|
||||
if (cacheControl && maxAgeRegex.test(cacheControl)) {
|
||||
|
|
|
@ -283,7 +283,7 @@ function runApp() {
|
|||
|
||||
session.defaultSession.webRequest.onBeforeSendHeaders(innertubeRequestFilter, ({ requestHeaders }, callback) => {
|
||||
requestHeaders.referer = 'https://www.youtube.com'
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback({ requestHeaders })
|
||||
})
|
||||
|
||||
|
@ -298,7 +298,7 @@ function runApp() {
|
|||
if (imageCache.has(url)) {
|
||||
const cached = imageCache.get(url)
|
||||
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback({
|
||||
mimeType: cached.mimeType,
|
||||
data: cached.data
|
||||
|
@ -336,7 +336,7 @@ function runApp() {
|
|||
|
||||
imageCache.add(url, mimeType, data, expiryTimestamp)
|
||||
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback({
|
||||
mimeType,
|
||||
data: data
|
||||
|
@ -364,7 +364,7 @@ function runApp() {
|
|||
return value
|
||||
})
|
||||
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback({
|
||||
statusCode: response.statusCode ?? 400,
|
||||
mimeType: 'application/json',
|
||||
|
@ -385,12 +385,12 @@ function runApp() {
|
|||
// the requests made by the imagecache:// handler to fetch the image,
|
||||
// are allowed through, as their resourceType is 'other'
|
||||
if (details.resourceType === 'image') {
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback({
|
||||
redirectURL: `imagecache://${encodeURIComponent(details.url)}`
|
||||
})
|
||||
} else {
|
||||
// eslint-disable-next-line node/no-callback-literal
|
||||
// eslint-disable-next-line n/no-callback-literal
|
||||
callback({})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -228,7 +228,7 @@ export default Vue.extend({
|
|||
let count = 0
|
||||
|
||||
const ytsubs = youtubeSubscriptions.slice(1).map(yt => {
|
||||
const splitCSVRegex = /(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^",\n]*|(?:\n|$))/g
|
||||
const splitCSVRegex = /(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^\n",]*|(?:\n|$))/g
|
||||
return [...yt.matchAll(splitCSVRegex)].map(s => {
|
||||
let newVal = s[1]
|
||||
if (newVal.startsWith('"')) {
|
||||
|
@ -623,11 +623,11 @@ export default Vue.extend({
|
|||
|
||||
this.profileList[0].subscriptions.forEach((channel) => {
|
||||
const escapedName = channel.name
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll('\'', ''')
|
||||
|
||||
const channelOpmlString = `<outline text="${escapedName}" title="${escapedName}" type="rss" xmlUrl="https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}"/>`
|
||||
opmlData += channelOpmlString
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
.folderDisplay
|
||||
width: 50vh
|
|
@ -0,0 +1,3 @@
|
|||
.folderDisplay {
|
||||
width: 50vh;
|
||||
}
|
|
@ -44,4 +44,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./download-settings.js" />
|
||||
<style scoped lang="sass" src="./download-settings.sass" />
|
||||
<style scoped lang="scss" src="./download-settings.scss" />
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
.ft-age-restricted
|
||||
color: var(--primary-text-color)
|
||||
h2
|
||||
width: 100%
|
||||
text-align: center
|
||||
background-color: var(--card-bg-color)
|
||||
padding: 10px 0
|
||||
.frown
|
||||
width: 100%
|
||||
text-align: center
|
||||
background-color: var(--card-bg-color)
|
||||
font-size: 10em
|
||||
padding: 20px 0
|
||||
height: 100%
|
|
@ -0,0 +1,19 @@
|
|||
.ft-age-restricted {
|
||||
color: var(primary-text-color);
|
||||
|
||||
h2 {
|
||||
background-color: var(card-bg-color);
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.frown {
|
||||
background-color: var(card-bg-color);
|
||||
font-size: 10em;
|
||||
height: 100%;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -12,4 +12,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-age-restricted.js" />
|
||||
<style scoped lang="sass" src="./ft-age-restricted.sass" />
|
||||
<style scoped lang="scss" src="./ft-age-restricted.scss" />
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
.ft-auto-grid
|
||||
&.grid
|
||||
display: grid
|
||||
grid-template-columns: repeat(auto-fill, minmax(262px, 1fr) )
|
||||
justify-content: space-evenly
|
||||
grid-gap: 8px
|
||||
|
||||
&.list
|
||||
display: grid
|
||||
grid-gap: 4px
|
|
@ -0,0 +1,13 @@
|
|||
.ft-auto-grid {
|
||||
&.grid {
|
||||
display: grid;
|
||||
grid-gap: 8px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(262px, 1fr));
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
&.list {
|
||||
display: grid;
|
||||
grid-gap: 4px;
|
||||
}
|
||||
}
|
|
@ -11,4 +11,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-auto-grid.js" />
|
||||
<style scoped lang="sass" src="./ft-auto-grid.sass" />
|
||||
<style scoped lang="scss" src="./ft-auto-grid.scss" />
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
.ftIconButton
|
||||
display: flex
|
||||
flex-flow: row wrap
|
||||
justify-content: space-evenly
|
||||
position: relative
|
||||
user-select: none
|
||||
|
||||
.iconButton
|
||||
width: 1em
|
||||
height: 1em
|
||||
border-radius: 50%
|
||||
cursor: pointer
|
||||
transition: background 0.15s ease-out
|
||||
|
||||
&.shadow
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.5)
|
||||
|
||||
&.base
|
||||
background-color: var(--card-bg-color)
|
||||
color: var(--primary-text-color)
|
||||
|
||||
&:hover
|
||||
background-color: var(--side-nav-hover-color)
|
||||
|
||||
&:active
|
||||
background-color: var(--side-nav-active-color)
|
||||
|
||||
&.base-no-default
|
||||
&:hover
|
||||
background-color: var(--side-nav-hover-color)
|
||||
|
||||
&:active
|
||||
background-color: var(--side-nav-active-color)
|
||||
|
||||
&.primary
|
||||
background-color: var(--primary-color)
|
||||
color: var(--text-with-main-color)
|
||||
|
||||
&:hover
|
||||
background-color: var(--primary-color-hover)
|
||||
|
||||
&:active
|
||||
background-color: var(--primary-color-active)
|
||||
|
||||
&.secondary
|
||||
background-color: var(--accent-color)
|
||||
color: var(--text-with-accent-color)
|
||||
|
||||
&:hover
|
||||
background-color: var(--accent-color-hover)
|
||||
|
||||
&:active
|
||||
background-color: var(--accent-color-active)
|
||||
|
||||
&.favorite
|
||||
color: var(--favorite-icon-color)
|
||||
|
||||
.iconDropdown
|
||||
display: inline
|
||||
position: absolute
|
||||
text-align: center
|
||||
list-style-type: none
|
||||
z-index: 3
|
||||
margin-top: 45px
|
||||
font-size: 12px
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.5)
|
||||
background-color: var(--side-nav-color)
|
||||
color: var(--secondary-text-color)
|
||||
user-select: none
|
||||
|
||||
&.left
|
||||
right: calc(50% - 10px)
|
||||
|
||||
&.right
|
||||
left: calc(50% - 10px)
|
||||
|
||||
.list
|
||||
margin: 0
|
||||
padding: 0
|
||||
list-style-type: none
|
||||
|
||||
.listItem
|
||||
padding: 8px 10px
|
||||
margin: 0
|
||||
white-space: nowrap
|
||||
cursor: pointer
|
||||
transition: background 0.2s ease-out
|
||||
|
||||
&:hover, &:focus
|
||||
background-color: var(--side-nav-hover-color)
|
||||
transition: background 0.2s ease-in
|
||||
|
||||
&:active
|
||||
background-color: var(--side-nav-active-color)
|
||||
transition: background 0.1s ease-in
|
||||
|
||||
.listItemDivider
|
||||
width: 95%
|
||||
margin: 1px auto
|
||||
border-top: 1px solid var(--tertiary-text-color)
|
||||
// Too "visible" with current color
|
||||
opacity: 50%
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
.ftIconButton {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-evenly;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconButton {
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: 1em;
|
||||
transition: background 0.15s ease-out;
|
||||
width: 1em;
|
||||
|
||||
&.shadow {
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
&.base {
|
||||
background-color: var(card-bg-color);
|
||||
color: var(primary-text-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(side-nav-hover-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(side-nav-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.base-no-default {
|
||||
&:hover {
|
||||
background-color: var(side-nav-hover-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(side-nav-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: var(primary-color);
|
||||
color: var(text-with-main-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(primary-color-hover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(primary-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background-color: var(accent-color);
|
||||
color: var(text-with-accent-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(accent-color-hover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(accent-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
&.favorite {
|
||||
color: var(favorite-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
.iconDropdown {
|
||||
background-color: var(side-nav-color);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 50%);
|
||||
color: var(secondary-text-color);
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
list-style-type: none;
|
||||
margin-top: 45px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
z-index: 3;
|
||||
|
||||
&.left {
|
||||
right: calc(50% - 10px);
|
||||
}
|
||||
|
||||
&.right {
|
||||
left: calc(50% - 10px);
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
transition: background 0.2s ease-out;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(side-nav-hover-color);
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(side-nav-active-color);
|
||||
transition: background 0.1s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.listItemDivider {
|
||||
border-top: 1px solid var(tertiary-text-color);
|
||||
margin: 1px auto;
|
||||
// Too "visible" with current color
|
||||
opacity: 0.5;
|
||||
width: 95%;
|
||||
}
|
||||
}
|
|
@ -61,4 +61,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-icon-button.js" />
|
||||
<style scoped lang="sass" src="./ft-icon-button.sass" />
|
||||
<style scoped lang="scss" src="./ft-icon-button.scss" />
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@use "../../sass-partials/_ft-list-item"
|
|
@ -0,0 +1 @@
|
|||
@use '../../scss-partials/_ft-list-item';
|
|
@ -48,4 +48,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-list-channel.js" />
|
||||
<style scoped lang="sass" src="./ft-list-channel.sass" />
|
||||
<style scoped lang="scss" src="./ft-list-channel.scss" />
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@use "../../sass-partials/_ft-list-item"
|
|
@ -0,0 +1 @@
|
|||
@use '../../scss-partials/_ft-list-item';
|
|
@ -52,4 +52,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-list-playlist.js" />
|
||||
<style scoped lang="sass" src="./ft-list-playlist.sass" />
|
||||
<style scoped lang="scss" src="./ft-list-playlist.scss" />
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
@use "../../sass-partials/_ft-list-item"
|
||||
|
||||
.thumbnailLink:hover
|
||||
outline: 3px solid var(--side-nav-hover-color)
|
|
@ -0,0 +1,5 @@
|
|||
@use '../../scss-partials/_ft-list-item';
|
||||
|
||||
.thumbnailLink:hover {
|
||||
outline: 3px solid var(side-nav-hover-color);
|
||||
}
|
|
@ -120,4 +120,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-list-video.js" />
|
||||
<style scoped src="./ft-list-video.sass" lang="sass" />
|
||||
<style scoped src="./ft-list-video.scss" lang="scss" />
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
.settingsSection
|
||||
background-color: var(--card-bg-color)
|
||||
width: 85%
|
||||
margin: 0 auto
|
||||
@media only screen and (max-width: 800px)
|
||||
width: 100%
|
||||
|
||||
&[open]
|
||||
padding-bottom: 15px
|
||||
|
||||
> div
|
||||
width: 100%
|
||||
padding: 0px 20px
|
||||
box-sizing: border-box
|
||||
> div:not(:last-child):not(.ft-flex-box)
|
||||
@media only screen and (max-width: 800px)
|
||||
margin-bottom: 20px
|
||||
|
||||
.sectionLine
|
||||
width: 100%
|
||||
height: 2px
|
||||
border: 0
|
||||
margin-top: -1px
|
||||
background-color: var(--primary-color)
|
||||
|
||||
.sectionHeader
|
||||
display: block
|
||||
cursor: pointer
|
||||
padding: 1px
|
||||
|
||||
.sectionTitle
|
||||
margin-left: 2%
|
||||
|
||||
:deep(.switchGrid)
|
||||
display: grid
|
||||
grid-template-columns: auto auto
|
||||
justify-content: space-evenly
|
||||
align-items: center
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
grid-template-columns: auto
|
||||
|
||||
:deep(.switchColumnGrid)
|
||||
@extend :deep(.switchGrid)
|
||||
align-items: start
|
||||
|
||||
:deep(.switchColumn)
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-items: start
|
||||
|
||||
:deep(.center)
|
||||
text-align: center
|
||||
|
||||
@media only screen and (max-width: 460px)
|
||||
:deep(.settingsFlexStart460px)
|
||||
justify-content: flex-start
|
||||
|
||||
@media only screen and (max-width: 500px)
|
||||
:deep(.settingsFlexStart500px)
|
||||
justify-content: flex-start
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
.settingsSection
|
||||
> div
|
||||
:deep(.text.bottom)
|
||||
left: -85px
|
||||
:deep(.switch-ctn.containsTooltip)
|
||||
left: -10px
|
||||
margin-right: 5px
|
||||
padding: 0px 10px 0px 10px
|
||||
:not(.select, .selectLabel)
|
||||
> :deep(.tooltip)
|
||||
display: inline-block
|
||||
position: absolute
|
||||
right: -25px
|
||||
top: 12px
|
||||
.settingsFlexStart460px :deep(.tooltip)
|
||||
right: 0px
|
||||
top: -2px
|
||||
:deep(.switch-ctn)
|
||||
margin: 10px 7px
|
|
@ -0,0 +1,116 @@
|
|||
.settingsSection {
|
||||
background-color: var(card-bg-color);
|
||||
margin: 0 auto;
|
||||
width: 85%;
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
> div {
|
||||
box-sizing: border-box;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> div:not(:last-child, .ft-flex-box) {
|
||||
@media only screen and (max-width: 800px) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sectionLine {
|
||||
background-color: var(primary-color);
|
||||
border: 0;
|
||||
height: 2px;
|
||||
margin-top: -1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
margin-left: 2%;
|
||||
}
|
||||
|
||||
:deep(.switchGrid) {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: space-evenly;
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.switchColumnGrid) {
|
||||
@extend :deep(.switchGrid);
|
||||
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
:deep(.switchColumn) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
:deep(.center) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 460px) {
|
||||
:deep(.settingsFlexStart460px) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
:deep(.settingsFlexStart500px) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
.settingsSection {
|
||||
> div {
|
||||
:deep(.text.bottom) {
|
||||
left: -85px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.switch-ctn.containsTooltip) {
|
||||
left: -10px;
|
||||
margin-right: 5px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
:not(.select, .selectLabel) {
|
||||
> :deep(.tooltip) {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: -25px;
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.settingsFlexStart460px :deep(.tooltip) {
|
||||
right: 0;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
:deep(.switch-ctn) {
|
||||
margin: 10px 7px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,4 +11,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-settings-section.js" />
|
||||
<style scoped src="./ft-settings-section.sass" lang="sass" />
|
||||
<style scoped src="./ft-settings-section.scss" lang="scss" />
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
.shareLinks
|
||||
display: grid
|
||||
grid-template-rows: auto auto
|
||||
grid-auto-flow: column
|
||||
padding: 12px
|
||||
width: max-content
|
||||
|
||||
.header
|
||||
font-size: 18px
|
||||
font-weight: bold
|
||||
margin: 4px 0px 8px
|
||||
color: var(--primary-text-color)
|
||||
|
||||
.buttons
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.action
|
||||
padding: 6px
|
||||
|
||||
.divider
|
||||
grid-row: span 3
|
||||
margin: 0px 12px
|
||||
width: 1px
|
||||
background: var(--tertiary-text-color)
|
||||
|
||||
.youtubeLogo
|
||||
height: 18px
|
||||
width: auto
|
||||
|
||||
@at-root
|
||||
.dark &, .system[data-system-theme*='dark'] &
|
||||
filter: brightness(0.868)
|
||||
|
||||
.black &
|
||||
filter: brightness(0.933)
|
||||
|
||||
/* no changes for the dracula theme */
|
||||
|
||||
.light &, .system[data-system-theme*='light'] &
|
||||
filter: invert(0.87)
|
||||
|
||||
.invidious
|
||||
display: flex
|
||||
justify-content: center
|
||||
letter-spacing: -0.4px
|
||||
|
||||
.invidiousLogo
|
||||
display: inline-block
|
||||
width: 20px
|
||||
height: 20px
|
||||
background-size: cover
|
||||
margin-right: 2px
|
||||
|
||||
@at-root
|
||||
.dark &,
|
||||
.black &,
|
||||
.dracula &,
|
||||
.CatppuccinMocha &,
|
||||
.system[data-system-theme*='dark'] &
|
||||
background-image: url(../../assets/img/invidious-logo-dark.svg)
|
||||
|
||||
.light &, .system[data-system-theme*='light'] &
|
||||
background-image: url(../../assets/img/invidious-logo-light.svg)
|
|
@ -0,0 +1,82 @@
|
|||
.shareLinks {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: auto auto;
|
||||
padding: 12px;
|
||||
width: max-content;
|
||||
|
||||
.header {
|
||||
color: var(primary-text-color);
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin: 4px 0 8px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.action {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
background: var(tertiary-text-color);
|
||||
grid-row: span 3;
|
||||
margin: 0 12px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.youtubeLogo {
|
||||
height: 18px;
|
||||
width: auto;
|
||||
|
||||
@at-root {
|
||||
.dark &,
|
||||
.system[data-system-theme*='dark'] & {
|
||||
filter: brightness(0.868);
|
||||
}
|
||||
|
||||
.black & {
|
||||
filter: brightness(0.933);
|
||||
}
|
||||
|
||||
/* no changes for the dracula theme */
|
||||
|
||||
.light &,
|
||||
.system[data-system-theme*='light'] & {
|
||||
filter: invert(0.87);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.invidious {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
letter-spacing: -0.4px;
|
||||
|
||||
.invidiousLogo {
|
||||
background-size: cover;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
margin-right: 2px;
|
||||
width: 20px;
|
||||
|
||||
@at-root {
|
||||
.dark &,
|
||||
.black &,
|
||||
.dracula &,
|
||||
.CatppuccinMocha &,
|
||||
.system[data-system-theme*='dark'] & {
|
||||
background-image: url(../../assets/img/invidious-logo-dark.svg);
|
||||
}
|
||||
|
||||
.light &,
|
||||
.system[data-system-theme*='light'] & {
|
||||
background-image: url(../../assets/img/invidious-logo-light.svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -119,4 +119,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-share-button.js" />
|
||||
<style scoped lang="sass" src="./ft-share-button.sass" />
|
||||
<style scoped lang="scss" src="./ft-share-button.scss" />
|
||||
|
|
|
@ -34,7 +34,7 @@ export default Vue.extend({
|
|||
colorNames: function () {
|
||||
return this.colorValues.map(colorVal => {
|
||||
// add spaces before capital letters
|
||||
const colorName = colorVal.replace(/([A-Z])/g, ' $1').trim()
|
||||
const colorName = colorVal.replaceAll(/([A-Z])/g, ' $1').trim()
|
||||
return this.$t(`Settings.Theme Settings.Main Color Theme.${colorName}`)
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.sponsorBlockCategory
|
||||
margin-top: 30px
|
||||
padding: 0 10px
|
||||
@media only screen and (max-width: 680px)
|
||||
width: 100%
|
||||
.sponsorTitle
|
||||
font-size: x-large
|
|
@ -0,0 +1,12 @@
|
|||
.sponsorBlockCategory {
|
||||
margin-top: 30px;
|
||||
padding: 0 10px;
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sponsorTitle {
|
||||
font-size: x-large;
|
||||
}
|
||||
}
|
|
@ -20,4 +20,4 @@
|
|||
</div>
|
||||
</template>
|
||||
<script src="./ft-sponsor-block-category.js" />
|
||||
<style scoped lang="sass" src="./ft-sponsor-block-category.sass" />
|
||||
<style scoped lang="scss" src="./ft-sponsor-block-category.scss" />
|
||||
|
|
|
@ -20,7 +20,7 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
detectTimestamps: function (input) {
|
||||
return input.replace(/(\d+(:\d+)+)/g, '<a href="#" onclick="this.dispatchEvent(new CustomEvent(\'timestamp-clicked\',{bubbles:true, detail:\'$1\'}))">$1</a>')
|
||||
return input.replaceAll(/(\d+(:\d+)+)/g, '<a href="#" onclick="this.dispatchEvent(new CustomEvent(\'timestamp-clicked\',{bubbles:true, detail:\'$1\'}))">$1</a>')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
/* Thanks to Guus Lieben for the Material Design Switch */
|
||||
|
||||
.switch-ctn
|
||||
margin: 20px 16px
|
||||
position: relative
|
||||
|
||||
&.compact
|
||||
margin: 0
|
||||
|
||||
.disabled
|
||||
.switch-label
|
||||
cursor: not-allowed
|
||||
|
||||
.switch-label-text
|
||||
opacity: 0.4
|
||||
|
||||
.switch-input
|
||||
-moz-appearance: none
|
||||
-webkit-appearance: none
|
||||
appearance: none
|
||||
height: 20px
|
||||
left: -3px
|
||||
position: absolute
|
||||
top: calc(50% - 3px)
|
||||
-ms-transform: translate(0, (-50%))
|
||||
-webkit-transform: translate(0, -50%)
|
||||
transform: translate(0, -50%)
|
||||
width: 34px
|
||||
|
||||
.switch-label
|
||||
position: relative
|
||||
display: inline-block
|
||||
cursor: pointer
|
||||
font-weight: 500
|
||||
text-align: left
|
||||
padding: 12px 0 12px 44px
|
||||
|
||||
&:before, &:after
|
||||
content: ""
|
||||
position: absolute
|
||||
margin: 0
|
||||
outline: 0
|
||||
top: 50%
|
||||
-ms-transform: translate(0, -50%)
|
||||
-webkit-transform: translate(0, -50%)
|
||||
transform: translate(0, -50%)
|
||||
-webkit-transition: all 0.3s ease
|
||||
transition: all 0.3s ease
|
||||
|
||||
&:before
|
||||
left: 1px
|
||||
width: 34px
|
||||
height: 14px
|
||||
background-color: #9E9E9E
|
||||
border-radius: 8px
|
||||
|
||||
.switch-input:checked + &
|
||||
background-color: var(--accent-color-light)
|
||||
|
||||
.switch-input:disabled + &
|
||||
background-color: #9E9E9E
|
||||
|
||||
&:after
|
||||
left: 0
|
||||
width: 20px
|
||||
height: 20px
|
||||
background-color: #FAFAFA
|
||||
border-radius: 50%
|
||||
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.14), 0 2px 2px 0 rgba(0, 0, 0, 0.098), 0 1px 5px 0 rgba(0, 0, 0, 0.084)
|
||||
|
||||
.switch-input:checked + &
|
||||
background-color: var(--accent-color)
|
||||
-ms-transform: translate(80%, -50%)
|
||||
-webkit-transform: translate(80%, -50%)
|
||||
transform: translate(80%, -50%)
|
||||
|
||||
.switch-input:disabled + &
|
||||
background-color: #BDBDBD
|
||||
|
||||
@media (max-width: 680px)
|
||||
max-width: 250px
|
|
@ -0,0 +1,88 @@
|
|||
/* Thanks to Guus Lieben for the Material Design Switch */
|
||||
|
||||
.switch-ctn {
|
||||
margin: 20px 16px;
|
||||
position: relative;
|
||||
|
||||
&.compact {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-input {
|
||||
appearance: none;
|
||||
height: 20px;
|
||||
left: -3px;
|
||||
position: absolute;
|
||||
top: calc(50% - 3px);
|
||||
transform: translate(0, -50%);
|
||||
width: 34px;
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
padding: 12px 0 12px 44px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: #9e9e9e;
|
||||
border-radius: 8px;
|
||||
height: 14px;
|
||||
left: 1px;
|
||||
width: 34px;
|
||||
|
||||
.switch-input:checked + & {
|
||||
background-color: var(accent-color-light);
|
||||
}
|
||||
|
||||
.switch-input:disabled + & {
|
||||
background-color: #9e9e9e;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
background-color: #fafafa;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 3px 1px -2px rgb(0 0 0 / 14%), 0 2px 2px 0 rgb(0 0 0 / 9.8%), 0 1px 5px 0 rgb(0 0 0 / 8.4%);
|
||||
height: 20px;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
|
||||
.switch-input:checked + & {
|
||||
background-color: var(accent-color);
|
||||
transform: translate(80%, -50%);
|
||||
}
|
||||
|
||||
.switch-input:disabled + & {
|
||||
background-color: #bdbdbd;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
.switch-label {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.switch-label-text {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
|
@ -35,4 +35,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./ft-toggle-switch.js" />
|
||||
<style scoped lang="sass" src="./ft-toggle-switch.sass" />
|
||||
<style scoped lang="scss" src="./ft-toggle-switch.scss" />
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
.select
|
||||
min-width: 240px
|
||||
width: auto
|
||||
|
||||
// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors
|
||||
.select:deep(.select-text)
|
||||
min-width: 240px
|
||||
width: auto
|
|
@ -0,0 +1,9 @@
|
|||
.select {
|
||||
min-width: 240px;
|
||||
width: auto;
|
||||
}
|
||||
// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors
|
||||
.select:deep(.select-text) {
|
||||
min-width: 240px;
|
||||
width: auto;
|
||||
}
|
|
@ -141,4 +141,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./general-settings.js" />
|
||||
<style scoped lang="sass" src="./general-settings.sass" />
|
||||
<style scoped lang="scss" src="./general-settings.scss" />
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
.screenshotFolderContainer
|
||||
width: 95%
|
||||
margin: 0 auto
|
||||
align-items: center
|
||||
column-gap: 1rem
|
||||
|
||||
.screenshotFolderLabel, .screenshotFolderButton, .screenshotFilenamePatternTitle
|
||||
flex-grow: 0
|
||||
|
||||
.screenshotFolderPath, .screenshotFilenamePatternInput, .screenshotFilenamePatternExample
|
||||
flex-grow: 1
|
||||
margin-top: 10px
|
|
@ -0,0 +1,19 @@
|
|||
.screenshotFolderContainer {
|
||||
align-items: center;
|
||||
column-gap: 1rem;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
|
||||
.screenshotFolderLabel,
|
||||
.screenshotFolderButton,
|
||||
.screenshotFilenamePatternTitle {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.screenshotFolderPath,
|
||||
.screenshotFilenamePatternInput,
|
||||
.screenshotFilenamePatternExample {
|
||||
flex-grow: 1;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
|
@ -246,4 +246,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./player-settings.js" />
|
||||
<style scoped lang="sass" src="./player-settings.sass" />
|
||||
<style scoped lang="scss" src="./player-settings.scss" />
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
.playListThumbnail
|
||||
width: 100%
|
||||
|
||||
.playlistThumbnail img
|
||||
width: 100%
|
||||
cursor: pointer
|
||||
|
||||
@media only screen and (max-width: 800px)
|
||||
display: none
|
||||
|
||||
.playlistStats
|
||||
font-size: 15px
|
||||
|
||||
.playlistStats p
|
||||
color: var(--secondary-text-color)
|
||||
margin: 0
|
||||
|
||||
.playlistTitle
|
||||
margin-bottom: 0.1em
|
||||
|
||||
.playlistDescription
|
||||
max-height: 20vh
|
||||
overflow-y: auto
|
||||
white-space: break-spaces
|
||||
|
||||
@media only screen and (max-width: 500px)
|
||||
max-height: 10vh
|
||||
|
||||
.playlistChannel
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 8px
|
||||
height: 40px
|
||||
text-decoration: none
|
||||
color: inherit
|
||||
|
||||
.channelThumbnail
|
||||
width: 40px
|
||||
float: left
|
||||
border-radius: 200px 200px 200px 200px
|
||||
-webkit-border-radius: 200px 200px 200px 200px
|
||||
|
||||
.channelName
|
||||
margin: 0
|
||||
font-size: 15px
|
|
@ -0,0 +1,55 @@
|
|||
.playListThumbnail {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.playlistThumbnail img {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.playlistStats {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.playlistStats p {
|
||||
color: var(secondary-text-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.playlistTitle {
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
.playlistDescription {
|
||||
max-height: 20vh;
|
||||
overflow-y: auto;
|
||||
white-space: break-spaces;
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
max-height: 10vh;
|
||||
}
|
||||
}
|
||||
|
||||
.playlistChannel {
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: 40px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.channelThumbnail {
|
||||
border-radius: 200px;
|
||||
float: left;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.channelName {
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
}
|
|
@ -66,4 +66,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./playlist-info.js" />
|
||||
<style scoped lang="sass" src="./playlist-info.sass" />
|
||||
<style scoped lang="scss" src="./playlist-info.scss" />
|
||||
|
|
|
@ -109,7 +109,7 @@ export default Vue.extend({
|
|||
colorNames: function () {
|
||||
return this.colorValues.map(colorVal => {
|
||||
// add spaces before capital letters
|
||||
const colorName = colorVal.replace(/([A-Z])/g, ' $1').trim()
|
||||
const colorName = colorVal.replaceAll(/([A-Z])/g, ' $1').trim()
|
||||
return this.$t(`Settings.Theme Settings.Main Color Theme.${colorName}`)
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
@mixin top-nav-is-colored
|
||||
@at-root
|
||||
.topNavBarColor &, .topNavBarColor#{&}
|
||||
@content
|
||||
|
||||
.topNav
|
||||
position: sticky
|
||||
z-index: 4
|
||||
left: 0
|
||||
right: 0
|
||||
top: 0
|
||||
height: 60px
|
||||
width: 100%
|
||||
line-height: 60px
|
||||
background-color: var(--card-bg-color)
|
||||
-webkit-box-shadow: 0px 2px 1px 0px var(--primary-shadow-color)
|
||||
display: flex
|
||||
align-items: center
|
||||
align-content: center
|
||||
|
||||
@media only screen and (min-width: 961px)
|
||||
display: grid
|
||||
grid-template-columns: 1fr 440px 1fr
|
||||
|
||||
@include top-nav-is-colored
|
||||
background-color: var(--primary-color)
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
position: fixed
|
||||
|
||||
.menuIcon // the hamburger button
|
||||
@media only screen and (max-width: 680px)
|
||||
display: none
|
||||
|
||||
.navIcon // all icons in the top navigation
|
||||
font-size: 20px
|
||||
padding: 10px
|
||||
cursor: pointer
|
||||
color: var(--primary-text-color)
|
||||
border-radius: 50%
|
||||
transition: background 0.2s ease-out
|
||||
width: 1em
|
||||
height: 1em
|
||||
|
||||
&.fa-arrow-left, &.fa-arrow-right
|
||||
color: gray
|
||||
opacity: 0.5
|
||||
pointer-events: none
|
||||
user-select: none
|
||||
|
||||
@include top-nav-is-colored
|
||||
color: var(--text-with-main-color)
|
||||
|
||||
&:hover
|
||||
background-color: var(--primary-color-hover)
|
||||
|
||||
&:hover
|
||||
background-color: var(--side-nav-hover-color)
|
||||
transition: background 0.2s ease-in
|
||||
|
||||
&:active
|
||||
background-color: var(--tertiary-text-color)
|
||||
transition: background 0.2s ease-in
|
||||
|
||||
@include top-nav-is-colored
|
||||
background-color: var(--primary-color-active)
|
||||
|
||||
.navFilterIcon // Filter icon
|
||||
$effect-distance: 10px
|
||||
|
||||
margin-left: $effect-distance
|
||||
|
||||
&.filterChanged // When filter value changed from default
|
||||
box-shadow: 0 0 $effect-distance var(--primary-color)
|
||||
|
||||
@include top-nav-is-colored
|
||||
box-shadow: 0 0 $effect-distance var(--text-with-main-color)
|
||||
|
||||
.side // parts of the top nav either side of the search bar
|
||||
display: flex
|
||||
gap: 3px
|
||||
margin: 0 6px
|
||||
align-items: center
|
||||
|
||||
&.profiles
|
||||
justify-content: flex-end
|
||||
|
||||
.navSearchIcon
|
||||
@media only screen and (min-width: 681px)
|
||||
display: none
|
||||
|
||||
.navNewWindowIcon
|
||||
@media only screen and (max-width: 680px)
|
||||
display: none
|
||||
|
||||
.logo // parts that make up the logo
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 0px 25px 0px 10px
|
||||
cursor: pointer
|
||||
|
||||
&:active
|
||||
background-color: var(--tertiary-text-color)
|
||||
transition: background 0.2s ease-in
|
||||
@include top-nav-is-colored
|
||||
background-color: var(--primary-color-active)
|
||||
.logoIcon
|
||||
background-image: var(--logo-icon)
|
||||
background-repeat: no-repeat
|
||||
background-position: right top
|
||||
background-size: 25px
|
||||
width: 25px
|
||||
height: 25px
|
||||
|
||||
@include top-nav-is-colored
|
||||
background-image: var(--logo-icon-bar-color)
|
||||
|
||||
.logoText
|
||||
margin-left: 5px
|
||||
position: relative
|
||||
top: -3px
|
||||
background-image: var(--logo-text)
|
||||
background-repeat: no-repeat
|
||||
background-position: right top
|
||||
background-size: 100px
|
||||
width: 100px
|
||||
height: 40px
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
display: none
|
||||
|
||||
@include top-nav-is-colored
|
||||
background-image: var(--logo-text-bar-color)
|
||||
|
||||
.middle // the middle part of the top nav which contains the search bar
|
||||
max-width: 440px
|
||||
flex: 1
|
||||
|
||||
.searchContainer
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
position: fixed
|
||||
left: 0
|
||||
right: 0
|
||||
top: 60px
|
||||
background-color: var(--side-nav-color)
|
||||
|
||||
@include top-nav-is-colored
|
||||
background-color: var(--primary-color-hover)
|
||||
|
||||
.searchInput
|
||||
flex: 1
|
||||
|
||||
.searchFilters
|
||||
position: absolute
|
||||
left: 0
|
||||
right: 0
|
||||
margin: 10px 20px 20px 220px
|
||||
transition: margin 150ms ease-in-out
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
left: 0
|
||||
right: 0
|
||||
margin: 95px 10px 0px
|
|
@ -0,0 +1,210 @@
|
|||
@mixin top-nav-is-colored {
|
||||
@at-root {
|
||||
.topNavBarColor &,
|
||||
.topNavBarColor#{&} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topNav {
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
background-color: var(card-bg-color);
|
||||
box-shadow: 0 2px 1px 0 var(primary-shadow-color);
|
||||
display: flex;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
line-height: 60px;
|
||||
position: sticky;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 4;
|
||||
|
||||
@media only screen and (min-width: 961px) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 440px 1fr;
|
||||
}
|
||||
|
||||
@include top-nav-is-colored {
|
||||
background-color: var(primary-color);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
.menuIcon {
|
||||
@media only screen and (max-width: 680px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navIcon {
|
||||
border-radius: 50%;
|
||||
color: var(primary-text-color);
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
height: 1em;
|
||||
padding: 10px;
|
||||
transition: background 0.2s ease-out;
|
||||
width: 1em;
|
||||
|
||||
@include top-nav-is-colored {
|
||||
color: var(text-with-main-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(primary-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.fa-arrow-left,
|
||||
&.fa-arrow-right {
|
||||
color: gray;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(side-nav-hover-color);
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(tertiary-text-color);
|
||||
transition: background 0.2s ease-in;
|
||||
|
||||
@include top-nav-is-colored {
|
||||
background-color: var(primary-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navFilterIcon {
|
||||
$effect-distance: 10px;
|
||||
|
||||
margin-left: $effect-distance;
|
||||
|
||||
&.filterChanged {
|
||||
box-shadow: 0 0 $effect-distance var(primary-color);
|
||||
|
||||
@include top-nav-is-colored {
|
||||
box-shadow: 0 0 $effect-distance var(text-with-main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
margin: 0 6px;
|
||||
|
||||
&.profiles {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.navSearchIcon {
|
||||
@media only screen and (min-width: 681px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navNewWindowIcon {
|
||||
@media only screen and (max-width: 680px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 0 25px 0 10px;
|
||||
|
||||
&:active {
|
||||
background-color: var(tertiary-text-color);
|
||||
transition: background 0.2s ease-in;
|
||||
|
||||
@include top-nav-is-colored {
|
||||
background-color: var(primary-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
.logoIcon {
|
||||
background-image: var(logo-icon);
|
||||
background-position: right top;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 25px;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
|
||||
@include top-nav-is-colored {
|
||||
background-image: var(logo-icon-bar-color);
|
||||
}
|
||||
}
|
||||
|
||||
.logoText {
|
||||
background-image: var(logo-text);
|
||||
background-position: right top;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100px;
|
||||
height: 40px;
|
||||
margin-left: 5px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
width: 100px;
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include top-nav-is-colored {
|
||||
background-image: var(logo-text-bar-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.middle {
|
||||
flex: 1;
|
||||
max-width: 440px;
|
||||
|
||||
.searchContainer {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
background-color: var(side-nav-color);
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 60px;
|
||||
|
||||
@include top-nav-is-colored {
|
||||
background-color: var(primary-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.searchFilters {
|
||||
left: 0;
|
||||
margin: 10px 20px 20px 220px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: margin 150ms ease-in-out;
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
left: 0;
|
||||
margin: 95px 10px 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -108,4 +108,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./top-nav.js" />
|
||||
<style scoped lang="sass" src="./top-nav.sass" />
|
||||
<style scoped lang="scss" src="./top-nav.scss" />
|
||||
|
|
|
@ -53,25 +53,25 @@ export default Vue.extend({
|
|||
this.$emit('timestamp-event', timestamp)
|
||||
},
|
||||
parseDescriptionHtml: function (descriptionText) {
|
||||
descriptionText = descriptionText.replace(/target="_blank"/g, '')
|
||||
descriptionText = descriptionText.replace(/\/redirect.+?(?=q=)/g, '')
|
||||
descriptionText = descriptionText.replace(/q=/g, '')
|
||||
descriptionText = descriptionText.replace(/rel="nofollow\snoopener"/g, '')
|
||||
descriptionText = descriptionText.replace(/class=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replace(/id=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replace(/data-target-new-window=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replace(/data-url=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replace(/data-sessionlink=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replace(/&/g, '&')
|
||||
descriptionText = descriptionText.replace(/%3A/g, ':')
|
||||
descriptionText = descriptionText.replace(/%2F/g, '/')
|
||||
descriptionText = descriptionText.replace(/&v.+?(?=")/g, '')
|
||||
descriptionText = descriptionText.replace(/&redirect-token.+?(?=")/g, '')
|
||||
descriptionText = descriptionText.replace(/&redir_token.+?(?=")/g, '')
|
||||
descriptionText = descriptionText.replace(/href="\//g, 'href="https://www.youtube.com/')
|
||||
descriptionText = descriptionText.replaceAll('target="_blank"', '')
|
||||
descriptionText = descriptionText.replaceAll(/\/redirect.+?(?=q=)/g, '')
|
||||
descriptionText = descriptionText.replaceAll('q=', '')
|
||||
descriptionText = descriptionText.replaceAll(/rel="nofollow\snoopener"/g, '')
|
||||
descriptionText = descriptionText.replaceAll(/class=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replaceAll(/id=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replaceAll(/data-target-new-window=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replaceAll(/data-url=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replaceAll(/data-sessionlink=.+?(?=")./g, '')
|
||||
descriptionText = descriptionText.replaceAll('&', '&')
|
||||
descriptionText = descriptionText.replaceAll('%3A', ':')
|
||||
descriptionText = descriptionText.replaceAll('%2F', '/')
|
||||
descriptionText = descriptionText.replaceAll(/&v.+?(?=")/g, '')
|
||||
descriptionText = descriptionText.replaceAll(/&redirect-token.+?(?=")/g, '')
|
||||
descriptionText = descriptionText.replaceAll(/&redir_token.+?(?=")/g, '')
|
||||
descriptionText = descriptionText.replaceAll('href="/', 'href="https://www.youtube.com/')
|
||||
// TODO: Implement hashtag support
|
||||
descriptionText = descriptionText.replace(/href="\/hashtag\//g, 'href="freetube://')
|
||||
descriptionText = descriptionText.replace(/yt\.www\.watch\.player\.seekTo/g, 'changeDuration')
|
||||
descriptionText = descriptionText.replaceAll('href="/hashtag/', 'href="freetube://')
|
||||
descriptionText = descriptionText.replaceAll('yt.www.watch.player.seekTo', 'changeDuration')
|
||||
|
||||
return descriptionText
|
||||
}
|
||||
|
|
|
@ -264,7 +264,7 @@ export default Vue.extend({
|
|||
const locale = this.currentLocale.replace('_', '-')
|
||||
const localeDateString = new Intl.DateTimeFormat([locale, 'en'], { dateStyle: 'medium' }).format(date)
|
||||
// replace spaces with no break spaces to make the date act as a single entity while wrapping
|
||||
return `${localeDateString}`.replace(/ /g, '\u00A0')
|
||||
return `${localeDateString}`.replaceAll(' ', '\u00A0')
|
||||
},
|
||||
|
||||
publishedString() {
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
.watchVideoInfo
|
||||
display: grid
|
||||
grid-template-columns: auto minmax(min-content, 1fr)
|
||||
column-gap: 15px
|
||||
padding: 16px
|
||||
|
||||
@media screen and (max-width: 680px)
|
||||
grid-template-columns: auto
|
||||
|
||||
.videoTitle
|
||||
font-size: 22px
|
||||
margin: 0 0 24px
|
||||
word-break: break-word
|
||||
display: block
|
||||
margin-block-end: 1em
|
||||
margin-inline-start: 0px
|
||||
margin-inline-end: 0px
|
||||
font-weight: normal
|
||||
|
||||
.channelInformation
|
||||
.profileRow
|
||||
display: flex
|
||||
|
||||
.channelThumbnail
|
||||
border-radius: 50%
|
||||
margin-right: 10px
|
||||
cursor: pointer
|
||||
width: 56px
|
||||
|
||||
.channelName
|
||||
margin-left: 6px
|
||||
cursor: pointer
|
||||
position: relative
|
||||
top: -2px
|
||||
display: block
|
||||
color: inherit
|
||||
text-decoration: inherit
|
||||
|
||||
.subscribeButton
|
||||
margin-top: 6px
|
||||
margin-left: 6px
|
||||
padding: 6px
|
||||
font-size: 14px
|
||||
|
||||
.viewCount, .datePublished
|
||||
color: var(--secondary-text-color)
|
||||
text-align: right
|
||||
font-size: 15px
|
||||
|
||||
@media screen and (max-width: 680px)
|
||||
text-align: left
|
||||
|
||||
.viewCount
|
||||
margin: 18px 0px 0px
|
||||
|
||||
.datePublished
|
||||
margin: 4px 0px 0px
|
||||
|
||||
@media screen and (max-width: 680px)
|
||||
margin-top: 16px
|
||||
|
||||
.likeSection
|
||||
margin-top: 4px
|
||||
font-size: 16px
|
||||
color: var(--tertiary-text-color)
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin-left: auto
|
||||
text-align: right
|
||||
max-width: 210px
|
||||
|
||||
@media screen and (max-width: 680px)
|
||||
margin-left: 0
|
||||
text-align: left
|
||||
|
||||
.likeBar
|
||||
height: 8px
|
||||
border-radius: 4px
|
||||
margin-bottom: 4px
|
||||
|
||||
.likeCount
|
||||
margin-right: 0px
|
||||
|
||||
.videoOptions
|
||||
margin-top: 16px
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
|
||||
.option:not(:first-child)
|
||||
margin-left: 4px
|
||||
|
||||
@media screen and (max-width: 680px)
|
||||
justify-content: flex-start
|
||||
|
||||
:deep(.iconDropdown)
|
||||
left: calc(50% - 20px)
|
||||
right: auto
|
|
@ -0,0 +1,119 @@
|
|||
.watchVideoInfo {
|
||||
column-gap: 15px;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(min-content, 1fr);
|
||||
padding: 16px;
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.videoTitle {
|
||||
display: block;
|
||||
font-size: 22px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 24px;
|
||||
margin-block-end: 1em;
|
||||
margin-inline-end: 0;
|
||||
margin-inline-start: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.channelInformation {
|
||||
.profileRow {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.channelThumbnail {
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
margin-right: 10px;
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
.channelName {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-left: 6px;
|
||||
position: relative;
|
||||
text-decoration: inherit;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.subscribeButton {
|
||||
font-size: 14px;
|
||||
margin-left: 6px;
|
||||
margin-top: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.viewCount,
|
||||
.datePublished {
|
||||
color: var(secondary-text-color);
|
||||
font-size: 15px;
|
||||
text-align: right;
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.viewCount {
|
||||
margin: 18px 0 0;
|
||||
}
|
||||
|
||||
.datePublished {
|
||||
margin: 4px 0 0;
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.likeSection {
|
||||
color: var(tertiary-text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
margin-left: auto;
|
||||
margin-top: 4px;
|
||||
max-width: 210px;
|
||||
text-align: right;
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
margin-left: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.likeBar {
|
||||
border-radius: 4px;
|
||||
height: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.likeCount {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.videoOptions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
|
||||
.option:not(:first-child) {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
justify-content: flex-start;
|
||||
|
||||
:deep(.iconDropdown) {
|
||||
left: calc(50% - 20px);
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -130,4 +130,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./watch-video-info.js" />
|
||||
<style scoped src="./watch-video-info.sass" lang="sass" />
|
||||
<style scoped src="./watch-video-info.scss" lang="scss" />
|
||||
|
|
|
@ -40,5 +40,5 @@ export function handleDropdownKeyboardEvent(event, target, afterElement) {
|
|||
}
|
||||
|
||||
export function sanitizeForHtmlId(attribute) {
|
||||
return attribute.replace(/\s+/g, '')
|
||||
return attribute.replaceAll(/\s+/g, '')
|
||||
}
|
||||
|
|
|
@ -361,7 +361,7 @@ export function createWebURL(path) {
|
|||
|
||||
// strip html tags but keep <br>, <b>, </b> <s>, </s>, <i>, </i>
|
||||
export function stripHTML(value) {
|
||||
return value.replace(/(<(?!br|\/?(?:b|s|i)>)([^>]+)>)/ig, '')
|
||||
return value.replaceAll(/(<(?!br|\/?[bis]>)([^>]+)>)/gi, '')
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -519,14 +519,14 @@ export function getVideoParamsFromUrl(url) {
|
|||
},
|
||||
// youtu.be
|
||||
function () {
|
||||
if (urlObject.host === 'youtu.be' && urlObject.pathname.match(/^\/[A-Za-z0-9_-]+$/)) {
|
||||
if (urlObject.host === 'youtu.be' && /^\/[\w-]+$/.test(urlObject.pathname)) {
|
||||
extractParams(urlObject.pathname.slice(1))
|
||||
return paramsObject
|
||||
}
|
||||
},
|
||||
// youtube.com/embed
|
||||
function () {
|
||||
if (urlObject.pathname.match(/^\/embed\/[A-Za-z0-9_-]+$/)) {
|
||||
if (/^\/embed\/[\w-]+$/.test(urlObject.pathname)) {
|
||||
const urlTail = urlObject.pathname.replace('/embed/', '')
|
||||
if (urlTail === 'videoseries') {
|
||||
paramsObject.playlistId = urlObject.searchParams.get('list')
|
||||
|
@ -538,14 +538,14 @@ export function getVideoParamsFromUrl(url) {
|
|||
},
|
||||
// youtube.com/shorts
|
||||
function () {
|
||||
if (urlObject.pathname.match(/^\/shorts\/[A-Za-z0-9_-]+$/)) {
|
||||
if (/^\/shorts\/[\w-]+$/.test(urlObject.pathname)) {
|
||||
extractParams(urlObject.pathname.replace('/shorts/', ''))
|
||||
return paramsObject
|
||||
}
|
||||
},
|
||||
// cloudtube
|
||||
function () {
|
||||
if (urlObject.host.match(/^cadence\.(gq|moe)$/) && urlObject.pathname.match(/^\/cloudtube\/video\/[A-Za-z0-9_-]+$/)) {
|
||||
if (/^cadence\.(gq|moe)$/.test(urlObject.host) && /^\/cloudtube\/video\/[\w-]+$/.test(urlObject.pathname)) {
|
||||
extractParams(urlObject.pathname.slice('/cloudtube/video/'.length))
|
||||
return paramsObject
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class CustomVueI18n extends VueI18n {
|
|||
// locales are only compressed in our production Electron builds
|
||||
try {
|
||||
// decompress brotli compressed json file and then load it
|
||||
// eslint-disable-next-line node/no-path-concat
|
||||
// eslint-disable-next-line n/no-path-concat
|
||||
const compressed = await readFile(`${__dirname}/static/locales/${locale}.json.br`)
|
||||
const decompressed = await brotliDecompressAsync(compressed)
|
||||
const data = JSON.parse(decompressed.toString())
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
$thumbnail-overlay-opacity: 0.85
|
||||
$watched-transition-duration: 0.5s
|
||||
|
||||
@mixin is-result
|
||||
@at-root
|
||||
.result#{&}
|
||||
@content
|
||||
|
||||
@mixin is-watch-playlist-item
|
||||
@at-root
|
||||
.watchPlaylistItem#{&}
|
||||
@content
|
||||
|
||||
@mixin is-recommendation
|
||||
@at-root
|
||||
.recommendation#{&}
|
||||
@content
|
||||
|
||||
@mixin is-sidebar-item
|
||||
@at-root
|
||||
.watchPlaylistItem#{&}, .recommendation#{&}
|
||||
@content
|
||||
|
||||
@mixin low-contrast-when-watched($col)
|
||||
color: $col
|
||||
|
||||
@at-root
|
||||
.watched &, .watched#{&}
|
||||
color: var(--tertiary-text-color)
|
||||
transition-duration: $watched-transition-duration
|
||||
|
||||
.watched:hover &, .watched:hover#{&}
|
||||
color: $col
|
||||
transition-duration: $watched-transition-duration
|
||||
|
||||
.ft-list-item
|
||||
padding: 6px
|
||||
|
||||
&.watched
|
||||
background-color: var(--bg-color)
|
||||
@include low-contrast-when-watched(var(--primary-text-color))
|
||||
|
||||
.thumbnailImage
|
||||
opacity: 0.3
|
||||
transition-duration: $watched-transition-duration
|
||||
|
||||
&:hover .thumbnailImage
|
||||
opacity: 1
|
||||
transition-duration: $watched-transition-duration
|
||||
|
||||
.videoThumbnail
|
||||
position: relative
|
||||
|
||||
.thumbnailLink
|
||||
display: flex
|
||||
|
||||
.thumbnailImage
|
||||
|
||||
@include is-sidebar-item
|
||||
height: 75px
|
||||
|
||||
@include is-recommendation
|
||||
width: 163px
|
||||
height: auto
|
||||
|
||||
.videoWatched
|
||||
position: absolute
|
||||
top:0
|
||||
padding: 2px
|
||||
opacity: $thumbnail-overlay-opacity
|
||||
color: var(--primary-text-color)
|
||||
background-color: var(--bg-color)
|
||||
pointer-events: none
|
||||
|
||||
.videoDuration
|
||||
position: absolute
|
||||
bottom: 4px
|
||||
right: 4px
|
||||
padding: 3px 4px
|
||||
line-height: 1.2
|
||||
font-size: 15px
|
||||
border-radius: 5px
|
||||
margin: 0
|
||||
opacity: $thumbnail-overlay-opacity
|
||||
color: var(--primary-text-color)
|
||||
background-color: var(--card-bg-color)
|
||||
pointer-events: none
|
||||
|
||||
&.live
|
||||
background-color: #f22
|
||||
color: #fff
|
||||
|
||||
@include is-watch-playlist-item
|
||||
font-size: 12px
|
||||
|
||||
.externalPlayerIcon
|
||||
position: absolute
|
||||
bottom: 4px
|
||||
left: 4px
|
||||
font-size: 17px
|
||||
opacity: $thumbnail-overlay-opacity
|
||||
|
||||
.favoritesIcon
|
||||
position: absolute
|
||||
top: 3px
|
||||
right: 3px
|
||||
font-size: 17px
|
||||
opacity: $thumbnail-overlay-opacity
|
||||
|
||||
.watchedProgressBar
|
||||
height: 2px
|
||||
position: absolute
|
||||
bottom: 0px
|
||||
background-color: var(--primary-color)
|
||||
z-index: 2
|
||||
max-width: 100%
|
||||
|
||||
.videoCountContainer
|
||||
position: absolute
|
||||
right: 0
|
||||
top: 0
|
||||
bottom: 0
|
||||
width: 60px
|
||||
font-size: 20px
|
||||
|
||||
.background, .inner
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
|
||||
.background
|
||||
background-color: var(--bg-color)
|
||||
opacity: 0.9
|
||||
|
||||
.inner
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
align-items: center
|
||||
color: var(--primary-text-color)
|
||||
|
||||
.channelThumbnail
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
.channelImage
|
||||
height: 130px
|
||||
border-radius: 50%
|
||||
|
||||
.info
|
||||
flex: 1
|
||||
position: relative
|
||||
|
||||
.optionsButton
|
||||
float: right // ohhhh man, float was finally the right choice for something
|
||||
|
||||
.externalPlayerButton
|
||||
float: right
|
||||
|
||||
.title
|
||||
font-size: 20px
|
||||
@include low-contrast-when-watched(var(--primary-text-color))
|
||||
text-decoration: none
|
||||
word-wrap: break-word
|
||||
word-break: break-word
|
||||
|
||||
@include is-sidebar-item
|
||||
font-size: 15px
|
||||
|
||||
.infoLine
|
||||
margin-top: 5px
|
||||
font-size: 14px
|
||||
|
||||
@include is-sidebar-item
|
||||
font-size: 12px
|
||||
|
||||
&
|
||||
@include low-contrast-when-watched(var(--secondary-text-color))
|
||||
|
||||
.channelName
|
||||
@include low-contrast-when-watched(var(--secondary-text-color))
|
||||
|
||||
.description
|
||||
font-size: 14px
|
||||
max-height: 50px
|
||||
overflow-y: hidden
|
||||
@include low-contrast-when-watched(var(--secondary-text-color))
|
||||
|
||||
&.list
|
||||
display: flex
|
||||
align-items: flex-start
|
||||
|
||||
.videoThumbnail, .channelThumbnail
|
||||
margin-right: 20px
|
||||
|
||||
.channelThumbnail
|
||||
width: 231px
|
||||
|
||||
@include is-sidebar-item
|
||||
.videoThumbnail
|
||||
margin-right: 10px
|
||||
|
||||
&.grid
|
||||
display: flex
|
||||
flex-direction: column
|
||||
min-height: 230px
|
||||
padding-bottom: 20px
|
||||
|
||||
.videoThumbnail, .channelThumbnail
|
||||
margin-bottom: 12px
|
||||
|
||||
.thumbnailImage
|
||||
width: 100%
|
||||
|
||||
.title
|
||||
font-size: 18px
|
||||
|
||||
.infoLine
|
||||
margin-top: 8px
|
||||
font-size: 13px
|
||||
|
||||
.videoWatched, .live
|
||||
text-transform: uppercase
|
|
@ -0,0 +1,292 @@
|
|||
$thumbnail-overlay-opacity: 0.85;
|
||||
$watched-transition-duration: 0.5s;
|
||||
|
||||
@mixin is-result {
|
||||
@at-root {
|
||||
.result#{&} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin is-watch-playlist-item {
|
||||
@at-root {
|
||||
.watchPlaylistItem#{&} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin is-recommendation {
|
||||
@at-root {
|
||||
.recommendation#{&} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin is-sidebar-item {
|
||||
@at-root {
|
||||
.watchPlaylistItem#{&},
|
||||
.recommendation#{&} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin low-contrast-when-watched($col) {
|
||||
color: $col;
|
||||
|
||||
@at-root {
|
||||
.watched &,
|
||||
.watched#{&} {
|
||||
color: var(tertiary-text-color);
|
||||
transition-duration: $watched-transition-duration;
|
||||
}
|
||||
.watched:hover &,
|
||||
.watched:hover#{&} {
|
||||
color: $col;
|
||||
transition-duration: $watched-transition-duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ft-list-item {
|
||||
padding: 6px;
|
||||
|
||||
&.watched {
|
||||
@include low-contrast-when-watched(var(primary-text-color));
|
||||
|
||||
background-color: var(bg-color);
|
||||
|
||||
.thumbnailImage {
|
||||
opacity: 0.3;
|
||||
transition-duration: $watched-transition-duration;
|
||||
}
|
||||
|
||||
&:hover .thumbnailImage {
|
||||
opacity: 1;
|
||||
transition-duration: $watched-transition-duration;
|
||||
}
|
||||
}
|
||||
|
||||
.videoThumbnail {
|
||||
position: relative;
|
||||
|
||||
.thumbnailLink {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.thumbnailImage {
|
||||
@include is-sidebar-item {
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
@include is-recommendation {
|
||||
height: auto;
|
||||
width: 163px;
|
||||
}
|
||||
}
|
||||
|
||||
.videoWatched {
|
||||
background-color: var(bg-color);
|
||||
color: var(primary-text-color);
|
||||
opacity: $thumbnail-overlay-opacity;
|
||||
padding: 2px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.videoDuration {
|
||||
background-color: var(card-bg-color);
|
||||
border-radius: 5px;
|
||||
bottom: 4px;
|
||||
color: var(primary-text-color);
|
||||
font-size: 15px;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
opacity: $thumbnail-overlay-opacity;
|
||||
padding: 3px 4px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
|
||||
@include is-watch-playlist-item {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.live {
|
||||
background-color: #f22;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.externalPlayerIcon {
|
||||
bottom: 4px;
|
||||
font-size: 17px;
|
||||
left: 4px;
|
||||
opacity: $thumbnail-overlay-opacity;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.favoritesIcon {
|
||||
font-size: 17px;
|
||||
opacity: $thumbnail-overlay-opacity;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.watchedProgressBar {
|
||||
background-color: var(primary-color);
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
max-width: 100%;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.videoCountContainer {
|
||||
bottom: 0;
|
||||
font-size: 20px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
|
||||
.background,
|
||||
.inner {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.background {
|
||||
background-color: var(bg-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.inner {
|
||||
align-items: center;
|
||||
color: var(primary-text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channelThumbnail {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.channelImage {
|
||||
border-radius: 50%;
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.optionsButton {
|
||||
float: right; // ohhhh man, float was finally the right choice for something;
|
||||
}
|
||||
|
||||
.externalPlayerButton {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include low-contrast-when-watched(var(primary-text-color));
|
||||
|
||||
font-size: 20px;
|
||||
text-decoration: none;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
|
||||
@include is-sidebar-item {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.infoLine {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
|
||||
@include is-sidebar-item {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
& {
|
||||
@include low-contrast-when-watched(var(secondary-text-color));
|
||||
}
|
||||
|
||||
.channelName {
|
||||
@include low-contrast-when-watched(var(secondary-text-color));
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
@include low-contrast-when-watched(var(secondary-text-color));
|
||||
|
||||
font-size: 14px;
|
||||
max-height: 50px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.list {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
|
||||
@include is-sidebar-item {
|
||||
.videoThumbnail {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.videoThumbnail,
|
||||
.channelThumbnail {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.channelThumbnail {
|
||||
width: 231px;
|
||||
}
|
||||
}
|
||||
|
||||
&.grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 230px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.videoThumbnail,
|
||||
.channelThumbnail {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.thumbnailImage {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.infoLine {
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.videoWatched,
|
||||
.live {
|
||||
text-transform: uppercase;
|
||||
}
|
|
@ -8,7 +8,7 @@ const modules = {}
|
|||
|
||||
files.keys().forEach(key => {
|
||||
if (key === './index.js') return
|
||||
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
|
||||
modules[key.replaceAll(/(\.\/|\.js)/g, '')] = files(key).default
|
||||
})
|
||||
|
||||
export default modules
|
||||
|
|
|
@ -314,7 +314,7 @@ const actions = {
|
|||
const typePatterns = new Map([
|
||||
['playlist', /^(\/playlist\/?|\/embed(\/?videoseries)?)$/],
|
||||
['search', /^\/results\/?$/],
|
||||
['hashtag', /^\/hashtag\/([^/?&#]+)$/],
|
||||
['hashtag', /^\/hashtag\/([^#&/?]+)$/],
|
||||
['channel', channelPattern]
|
||||
])
|
||||
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
.card
|
||||
width: 85%
|
||||
margin: 0 auto
|
||||
margin-bottom: 60px
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
width: 90%
|
||||
|
||||
.brand
|
||||
text-align: center
|
||||
|
||||
.logo
|
||||
width: 500px
|
||||
max-width: 100%
|
||||
|
||||
.version
|
||||
font-size: 2em
|
||||
|
||||
.about-chunks
|
||||
max-width: 860px
|
||||
margin: 80px auto
|
||||
display: grid
|
||||
grid-template-columns: 1fr 1fr
|
||||
grid-gap: 16px
|
||||
|
||||
@media only screen and (max-width: 650px)
|
||||
grid-template-columns: 1fr
|
||||
|
||||
.chunk
|
||||
background-color: var(--bg-color)
|
||||
margin: 0
|
||||
padding: 18px
|
||||
border-radius: 8px
|
||||
display: grid
|
||||
grid-template: "icon title" auto "icon content" 1fr / auto 1fr
|
||||
justify-content: start
|
||||
align-items: start
|
||||
grid-gap: 6px 14px
|
||||
word-break: break-word
|
||||
box-shadow: 0px 1px 4px -1px rgba(0, 0, 0, 0.5)
|
||||
|
||||
@each $area in icon title content
|
||||
.#{$area}
|
||||
grid-area: $area
|
||||
|
||||
.icon
|
||||
font-size: 24px
|
||||
|
||||
.title
|
||||
margin: 0
|
|
@ -0,0 +1,62 @@
|
|||
.card {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 60px;
|
||||
width: 85%;
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 100%;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.version {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.about-chunks {
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin: 80px auto;
|
||||
max-width: 860px;
|
||||
|
||||
@media only screen and (max-width: 650px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.chunk {
|
||||
align-items: start;
|
||||
background-color: var(bg-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px -1px rgb(0 0 0 / 50%);
|
||||
display: grid;
|
||||
grid-gap: 6px 14px;
|
||||
grid-template: 'icon title' auto 'icon content' 1fr / auto 1fr;
|
||||
justify-content: start;
|
||||
margin: 0;
|
||||
padding: 18px;
|
||||
word-break: break-word;
|
||||
|
||||
@each $area in icon title content {
|
||||
.#{$area} {
|
||||
grid-area: $area;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -37,4 +37,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./About.js" />
|
||||
<style scoped src="./About.sass" lang="sass" />
|
||||
<style scoped src="./About.scss" lang="scss" />
|
||||
|
|
|
@ -23,7 +23,7 @@ export default Vue.extend({
|
|||
subscribedChannels: [],
|
||||
filteredChannels: [],
|
||||
re: {
|
||||
url: /(.+=\w{1})\d+(.+)/,
|
||||
url: /(.+=\w)\d+(.+)/,
|
||||
ivToIv: /^.+(ggpht.+)/,
|
||||
ivToYt: /^.+ggpht\/(.+)/,
|
||||
ytToIv: /^.+ggpht\.com\/(.+)/
|
||||
|
@ -115,7 +115,7 @@ export default Vue.extend({
|
|||
return
|
||||
}
|
||||
|
||||
const escapedQuery = this.query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const escapedQuery = this.query.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&')
|
||||
const re = new RegExp(escapedQuery, 'i')
|
||||
this.filteredChannels = this.subscribedChannels.filter(channel => {
|
||||
return re.test(channel.name)
|
||||
|
|
|
@ -260,9 +260,9 @@ export default Vue.extend({
|
|||
let skipIndex
|
||||
errorScreen.subreason.runs.forEach((message, index) => {
|
||||
if (index !== skipIndex) {
|
||||
if (message.text.match(/<a.*>/)) {
|
||||
if (/<a.*>/.test(message.text)) {
|
||||
skipIndex = index + 1
|
||||
} else if (!message.text.match(/<\/a>/)) {
|
||||
} else if (!/<\/a>/.test(message.text)) {
|
||||
if (typeof subReason === 'undefined') {
|
||||
subReason = message.text
|
||||
} else {
|
||||
|
@ -719,7 +719,7 @@ export default Vue.extend({
|
|||
// MM:SS - Text
|
||||
// HH:MM:SS - HH:MM:SS - Text // end timestamp is ignored, separator is one of '-', '–', '—'
|
||||
// HH:MM - HH:MM - Text // end timestamp is ignored
|
||||
const chapterMatches = result.description.matchAll(/^(?<timestamp>((?<hours>[0-9]+):)?(?<minutes>[0-9]+):(?<seconds>[0-9]+))(\s*[-–—]\s*(?:[0-9]+:)?[0-9]+:[0-9]+)?\s+([-–•—]\s*)?(?<title>.+)$/gm)
|
||||
const chapterMatches = result.description.matchAll(/^(?<timestamp>((?<hours>\d+):)?(?<minutes>\d+):(?<seconds>\d+))(\s*[–—-]\s*(?:\d+:){1,2}\d+)?\s+([–—•-]\s*)?(?<title>.+)$/gm)
|
||||
|
||||
for (const { groups } of chapterMatches) {
|
||||
let start = 60 * Number(groups.minutes) + Number(groups.seconds)
|
||||
|
@ -860,7 +860,7 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
processDescriptionPart(part, fallbackDescription) {
|
||||
const timestampRegex = /^([0-9]+:)?[0-9]+:[0-9]+$/
|
||||
const timestampRegex = /^(\d+:)?\d+:\d+$/
|
||||
|
||||
if (typeof part.navigationEndpoint === 'undefined' || part.navigationEndpoint === null || part.text.startsWith('#')) {
|
||||
return part.text
|
||||
|
@ -1314,7 +1314,7 @@ export default Vue.extend({
|
|||
/* eslint-disable-next-line */
|
||||
const [width, height, count, sWidth, sHeight, interval, _, sigh] = storyboard.split('#')
|
||||
storyboardArray.push({
|
||||
url: baseUrl.replace('$L', i + 1).replace('$N', 'M0').replace(/<\/?sub>/g, '') + '&sigh=' + sigh,
|
||||
url: baseUrl.replace('$L', i + 1).replace('$N', 'M0').replaceAll(/<\/?sub>/g, '') + '&sigh=' + sigh,
|
||||
width: Number(width), // Width of one sub image
|
||||
height: Number(height), // Height of one sub image
|
||||
sWidth: Number(sWidth), // Number of images vertically (if full)
|
||||
|
@ -1412,7 +1412,7 @@ export default Vue.extend({
|
|||
// The character '#' needs to be percent-encoded in a (data) URI
|
||||
// because it signals an identifier, which means anything after it
|
||||
// is automatically removed when the URI is used as a source
|
||||
let vtt = text.replace(/#/g, '%23')
|
||||
let vtt = text.replaceAll('#', '%23')
|
||||
|
||||
// A lot of videos have messed up caption positions that need to be removed
|
||||
// This can be either because this format isn't really used by YouTube
|
||||
|
@ -1424,9 +1424,9 @@ export default Vue.extend({
|
|||
// In addition, all aligns seem to be fixed to "start" when they do pop up in normal captions
|
||||
// If it's prominent enough that people start to notice, it can be removed then
|
||||
if (caption.kind === 'asr') {
|
||||
vtt = vtt.replace(/ align:start| position:\d{1,3}%/g, '')
|
||||
vtt = vtt.replaceAll(/ align:start| position:\d{1,3}%/g, '')
|
||||
} else {
|
||||
vtt = vtt.replace(/ position:\d{1,3}%/g, '')
|
||||
vtt = vtt.replaceAll(/ position:\d{1,3}%/g, '')
|
||||
}
|
||||
|
||||
caption.baseUrl = `data:${caption.type};${caption.charset},${vtt}`
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
=dual-column-template
|
||||
grid-template: "video video sidebar" 0fr "info info sidebar" auto "info info sidebar" 1fr / 1fr 1fr 1fr
|
||||
|
||||
=theatre-mode-template
|
||||
grid-template: "video video video" auto "info info sidebar" auto "info info sidebar" auto / 1fr 1fr 1fr
|
||||
|
||||
=single-column-template
|
||||
grid-template: "video" auto "info" auto "sidebar" auto / auto
|
||||
.ageRestricted
|
||||
max-width: calc(80vh * 1.78)
|
||||
display: inline-block
|
||||
+single-column-template
|
||||
@media only screen and (min-width: 901px)
|
||||
width: 300%
|
||||
|
||||
.videoLayout
|
||||
display: grid
|
||||
align-items: start
|
||||
+dual-column-template
|
||||
|
||||
@media only screen and (max-width: 1350px)
|
||||
+theatre-mode-template
|
||||
|
||||
@media only screen and (min-width: 901px)
|
||||
&.useTheatreMode
|
||||
+theatre-mode-template
|
||||
|
||||
@media only screen and (max-width: 900px)
|
||||
+single-column-template
|
||||
|
||||
&.isLoading, &.noSidebar
|
||||
+single-column-template
|
||||
|
||||
.videoArea
|
||||
grid-area: video
|
||||
|
||||
.videoAreaMargin
|
||||
margin: 0 0 16px
|
||||
|
||||
.videoPlayer
|
||||
grid-column: 1
|
||||
max-width: calc(80vh * 1.78)
|
||||
margin: 0 auto
|
||||
position: relative
|
||||
|
||||
.upcomingThumbnail
|
||||
width: 100%
|
||||
|
||||
.premiereDate
|
||||
color: #FFFFFF
|
||||
background-color: rgba(0, 0, 0, 0.8)
|
||||
height: 60px
|
||||
border-radius: 5px
|
||||
position: absolute
|
||||
bottom: 12px
|
||||
left: 12px
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 0 12px
|
||||
|
||||
.premiereIcon
|
||||
float: left
|
||||
font-size: 25px
|
||||
margin: 0 12px
|
||||
|
||||
.premiereText
|
||||
min-width: 200px
|
||||
margin: 0 12px
|
||||
|
||||
.premiereTextTimestamp
|
||||
font-size: 0.85em
|
||||
font-weight: bold
|
||||
|
||||
.watchVideo
|
||||
margin: 0 0 16px
|
||||
grid-column: 1
|
||||
|
||||
.infoArea
|
||||
grid-area: info
|
||||
position: relative
|
||||
|
||||
@media only screen and (min-width: 901px)
|
||||
.infoArea
|
||||
scroll-margin-top: 76px
|
||||
|
||||
.infoAreaSticky
|
||||
position: sticky
|
||||
top: 76px
|
||||
|
||||
.sidebarArea
|
||||
grid-area: sidebar
|
||||
|
||||
@media only screen and (min-width: 901px)
|
||||
min-width: 380px
|
||||
|
||||
@at-root .noSidebar#{&}
|
||||
grid-area: auto
|
||||
|
||||
.watchVideoPlaylist, .watchVideoSidebar, .theatrePlaylist
|
||||
margin: 0 8px 16px
|
||||
|
||||
.watchVideoSidebar, .watchVideoPlaylist
|
||||
height: 500px
|
||||
|
||||
.watchVideoRecommendations, .theatreRecommendations
|
||||
margin: 0 0 16px
|
||||
|
||||
@media only screen and (min-width: 901px)
|
||||
margin: 0 8px 16px
|
|
@ -0,0 +1,149 @@
|
|||
@mixin dual-column-template {
|
||||
grid-template: 'video video sidebar' 0fr 'info info sidebar' auto 'info info sidebar' 1fr / 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
@mixin theatre-mode-template {
|
||||
grid-template: 'video video video' auto 'info info sidebar' auto 'info info sidebar' auto / 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
@mixin single-column-template {
|
||||
grid-template: 'video' auto 'info' auto 'sidebar' auto / auto;
|
||||
}
|
||||
|
||||
.ageRestricted {
|
||||
@include single-column-template;
|
||||
|
||||
display: inline-block;
|
||||
max-width: calc(80vh * 1.78);
|
||||
|
||||
@media only screen and (min-width: 901px) {
|
||||
width: 300%;
|
||||
}
|
||||
}
|
||||
|
||||
.videoLayout {
|
||||
@include dual-column-template;
|
||||
|
||||
align-items: start;
|
||||
display: grid;
|
||||
|
||||
&.isLoading,
|
||||
&.noSidebar {
|
||||
@include single-column-template;
|
||||
}
|
||||
|
||||
.videoArea {
|
||||
grid-area: video;
|
||||
|
||||
.videoAreaMargin {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.videoPlayer {
|
||||
grid-column: 1;
|
||||
margin: 0 auto;
|
||||
max-width: calc(80vh * 1.78);
|
||||
position: relative;
|
||||
|
||||
.upcomingThumbnail {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.premiereDate {
|
||||
align-items: center;
|
||||
background-color: rgb(0 0 0 / 80%);
|
||||
border-radius: 5px;
|
||||
bottom: 12px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
left: 12px;
|
||||
padding: 0 12px;
|
||||
position: absolute;
|
||||
|
||||
.premiereIcon {
|
||||
float: left;
|
||||
font-size: 25px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.premiereText {
|
||||
margin: 0 12px;
|
||||
min-width: 200px;
|
||||
|
||||
.premiereTextTimestamp {
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.watchVideo {
|
||||
grid-column: 1;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.infoArea {
|
||||
grid-area: info;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebarArea {
|
||||
grid-area: sidebar;
|
||||
|
||||
@media only screen and (min-width: 901px) {
|
||||
min-width: 380px;
|
||||
}
|
||||
|
||||
@at-root .noSidebar#{&} {
|
||||
grid-area: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.watchVideoPlaylist,
|
||||
.watchVideoSidebar,
|
||||
.theatrePlaylist {
|
||||
margin: 0 8px 16px;
|
||||
}
|
||||
|
||||
.watchVideoSidebar,
|
||||
.watchVideoPlaylist {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.watchVideoRecommendations,
|
||||
.theatreRecommendations {
|
||||
margin: 0 0 16px;
|
||||
|
||||
@media only screen and (min-width: 901px) {
|
||||
margin: 0 8px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1350px) {
|
||||
@include theatre-mode-template;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 901px) {
|
||||
&.useTheatreMode {
|
||||
@include theatre-mode-template;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
@include single-column-template;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 901px) {
|
||||
.infoArea {
|
||||
scroll-margin-top: 76px;
|
||||
}
|
||||
|
||||
.infoAreaSticky {
|
||||
position: sticky;
|
||||
top: 76px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -185,4 +185,4 @@
|
|||
</template>
|
||||
|
||||
<script src="./Watch.js" />
|
||||
<style scoped src="./Watch.sass" lang="sass" />
|
||||
<style scoped src="./Watch.scss" lang="scss" />
|
||||
|
|
|
@ -193,5 +193,3 @@
|
|||
{"id":894,"name":"Замбия","alpha2":"zm","alpha3":"zmb"},
|
||||
{"id":716,"name":"Зимбабве","alpha2":"zw","alpha3":"zwe"},
|
||||
{"id":158,"name":"Тайван","alpha2":"tw","alpha3":"twn"}]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue