diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index 2857ad4f5870..464ec55de035 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -75,24 +75,25 @@ body:
required: true
- type: dropdown
attributes:
- label: 'Installation Method (If applicable)'
+ label: Installation Method
options:
+ - .apk
- .AppImage
+ - AUR
+ - Chocolatey
- .deb
- .dmg
- .exe
- Flathub
+ - .pacman
- Portable
+ - PortableApps
- .rpm
+ - winget
- .zip
- other
validations:
- required: false
- - type: input
- attributes:
- label: 'Last Known Working FreeTube Version (If Any)'
- description: What is the last version of FreeTube this worked in, if applicable?
- placeholder: v0.14.0
+ required: true
- type: dropdown
attributes:
label: Primary API used
@@ -102,7 +103,12 @@ body:
- Local API
- Invidious API
validations:
- required: false
+ required: true
+ - type: input
+ attributes:
+ label: 'Last Known Working FreeTube Version (If Any)'
+ description: What is the last version of FreeTube this worked in, if applicable?
+ placeholder: v0.14.0
- type: textarea
attributes:
label: Additional Information
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 199861da0bcd..4b21f07a9f52 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -9,6 +9,8 @@ We may remove your pull request if you do not use this provided PR template corr
Please select what type of pull request this is:
- [ ] Bugfix
- [ ] Feature Implementation
+- [ ] Documentation
+- [ ] Other
**Related issue**
Please link the issue your pull request is referring to. If this pull request fully resolves the relevant issue, put "closes" before the issue number. Example: "closes #123456".
diff --git a/.github/workflows/autoLabelIssue.yaml b/.github/workflows/autoLabelIssue.yaml
index 4eb6736f52fb..adf9a90c23e9 100644
--- a/.github/workflows/autoLabelIssue.yaml
+++ b/.github/workflows/autoLabelIssue.yaml
@@ -10,5 +10,5 @@ jobs:
- uses: Naturalclar/issue-action@v2.0.2
with:
body: "both"
- parameters: '[ {"keywords": ["visual bug"], "labels": ["B: visual"]}, {"keywords": ["keyboard control not working"], "labels": ["B: keyboard control"]}, {"keywords": ["text/string issue"], "labels": ["B: text/string"]}, {"keywords": ["content not loading"], "labels": ["B: content not loading"]}, {"keywords": ["accessibility issue"], "labels": ["B: accessibility"]}, {"keywords": ["usability issue"], "labels": ["B: usability"]}, {"keywords": ["causes crash"], "labels": ["B: crash"]}, {"keywords": ["feature stopped working"], "labels": ["B: feature stopped working"]}, {"keywords": ["inconsistent behavior"], "labels": ["B: inconsistent behavior"]}, {"keywords": ["data loss"], "labels": ["B: data loss"]}, {"keywords": ["race condition"], "labels": ["B: race condition"]}, {"keywords": ["API issue"], "labels": ["B: API issue"]}, {"keywords": ["only happens in developer mode"], "labels": ["B: developer mode"]}, {"keywords": ["improvement to existing feature"], "labels": ["E: improvement existing feature"]}, {"keywords": ["new optional setting"], "labels": ["E: new optional setting"]}, {"keywords": ["visual improvement"], "labels": ["E: visual improvement"]}, {"keywords": ["display more information to user"], "labels": ["E: display more information"]}, {"keywords": ["ease of use improvement"], "labels": ["E: ease of use improvement"]}, {"keywords": ["support for external software"], "labels": ["E: support external software"]}, {"keywords": ["new feature"], "labels": ["E: new feature"]}, {"keywords": ["new keyboard shortcut"], "labels": ["E: keyboard shortcut"]}]'
+ parameters: '[ {"keywords": ["visual bug"], "labels": ["B: visual"]}, {"keywords": ["AUR", "Chocolatey", "PortableApps", "winget"], "labels": ["B: Unofficial Download"]}, {"keywords": ["keyboard control not working"], "labels": ["B: keyboard control"]}, {"keywords": ["text/string issue"], "labels": ["B: text/string"]}, {"keywords": ["content not loading"], "labels": ["B: content not loading"]}, {"keywords": ["accessibility issue"], "labels": ["B: accessibility"]}, {"keywords": ["usability issue"], "labels": ["B: usability"]}, {"keywords": ["causes crash"], "labels": ["B: crash"]}, {"keywords": ["feature stopped working"], "labels": ["B: feature stopped working"]}, {"keywords": ["inconsistent behavior"], "labels": ["B: inconsistent behavior"]}, {"keywords": ["data loss"], "labels": ["B: data loss"]}, {"keywords": ["race condition"], "labels": ["B: race condition"]}, {"keywords": ["API issue"], "labels": ["B: API issue"]}, {"keywords": ["only happens in developer mode"], "labels": ["B: developer mode"]}, {"keywords": ["improvement to existing feature"], "labels": ["E: improvement existing feature"]}, {"keywords": ["new optional setting"], "labels": ["E: new optional setting"]}, {"keywords": ["visual improvement"], "labels": ["E: visual improvement"]}, {"keywords": ["display more information to user"], "labels": ["E: display more information"]}, {"keywords": ["ease of use improvement"], "labels": ["E: ease of use improvement"]}, {"keywords": ["support for external software"], "labels": ["E: support external software"]}, {"keywords": ["new feature"], "labels": ["E: new feature"]}, {"keywords": ["new keyboard shortcut"], "labels": ["E: keyboard shortcut"]}]'
github-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8dc0f5f8cf07..77b99a85fcbe 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,7 +15,7 @@ jobs:
runtime: [ linux-x64, linux-arm64, win-x64, osx-x64 ]
include:
- runtime: linux-x64
- os: ubuntu-18.04
+ os: ubuntu-latest
- runtime: linux-arm64
os: ubuntu-latest
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index fe72c0b3df81..3b9015b9c9ae 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -13,7 +13,7 @@ jobs:
# This workflow contains a single job called "build"
lint:
# The type of runner that the job will run on
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
diff --git a/README.md b/README.md
index 8eb6f8450fd9..a7acefd3eccd 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,12 @@ addressed.
### Browser Extension
-FreeTube is supported by the [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect) extension, which will allow you to open YouTube links into FreeTube. You must enable the option within the advanced settings for it to work.
+FreeTube is supported by the [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect) and [LibRedirect](https://github.com/libredirect/libredirect) extension, which will allow you to open YouTube links into FreeTube. You must enable the option within the advanced settings for it to work.
Download Privacy Redirect for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/privacy-redirect/) or [Google Chrome](https://chrome.google.com/webstore/detail/privacy-redirect/pmcmeagblkinmogikoikkdjiligflglb).
+Download LibRedirect for [Firefox](https://addons.mozilla.org/firefox/addon/libredirect/) or [Google Chrome](https://github.com/libredirect/libredirect/blob/master/chromium.md).
+
Disclaimer: Learn more about why a browser extension is bad for your [privacy](https://www.privacyguides.org/browsers/#extensions).
If you have issues with the extension working with FreeTube, please create an issue in this repository instead of the extension repository.
@@ -62,7 +64,7 @@ Arch User Repository (AUR): [Download](https://aur.archlinux.org/packages/freetu
Chocolatey: [Download](https://chocolatey.org/packages/freetube/)
-Windows Portable: [Download](https://github.com/rddim/FreeTubePortable/releases) [Source](https://github.com/rddim/FreeTubePortable)
+PortableApps (Windows Only): [Download](https://github.com/rddim/FreeTubePortable/releases) [Source](https://github.com/rddim/FreeTubePortable)
Windows Package Manager (winget): [Usage](https://docs.microsoft.com/en-us/windows/package-manager/winget/)
diff --git a/_icons/iconCatppuccinMochaDarkSmall.png b/_icons/iconCatppuccinMochaDarkSmall.png
new file mode 100644
index 000000000000..bfc93ad5690d
Binary files /dev/null and b/_icons/iconCatppuccinMochaDarkSmall.png differ
diff --git a/_icons/iconCatppuccinMochaLightSmall.png b/_icons/iconCatppuccinMochaLightSmall.png
new file mode 100644
index 000000000000..021f62670c98
Binary files /dev/null and b/_icons/iconCatppuccinMochaLightSmall.png differ
diff --git a/_icons/textCatppuccinMochaDarkSmall.png b/_icons/textCatppuccinMochaDarkSmall.png
new file mode 100644
index 000000000000..e2fba035e852
Binary files /dev/null and b/_icons/textCatppuccinMochaDarkSmall.png differ
diff --git a/_icons/textCatppuccinMochaLightSmall.png b/_icons/textCatppuccinMochaLightSmall.png
new file mode 100644
index 000000000000..70e96901016f
Binary files /dev/null and b/_icons/textCatppuccinMochaLightSmall.png differ
diff --git a/_scripts/build.js b/_scripts/build.js
index 7d92b13b4670..6b762cc76772 100644
--- a/_scripts/build.js
+++ b/_scripts/build.js
@@ -53,7 +53,28 @@ const config = {
]
}
],
- files: ['_icons/iconColor.*', 'icon.svg', './dist/**/*', '!./dist/web/**/*'],
+ files: [
+ '_icons/iconColor.*',
+ 'icon.svg',
+ './dist/**/*',
+ '!dist/web/*',
+ '!**/node_modules/**/.*',
+ '!**/node_modules/**/index.html',
+ '!**/{.github,Jenkinsfile}',
+ '!**/{CHANGES.md,CODE_OF_CONDUCT.md,CONTRIBUTING.md,CONTRIBUTION.md,DEVELOPMENT.md,docs,docs.md,docs.mli,examples,History.md,HISTORY.md,README.md,TODO.md,UPGRADE_GUIDE.md,UPGRADING.md}',
+ '!**/{commitlint.config.js,.editorconfig,.eslintignore,.eslintrc.{js,yml},.gitmodules,.huskyrc,.lintstagedrc,.nvmrc,.nycrc{,.json},.prettierrc{,.yaml},tslint.json}',
+ '!**/{.babelrc,bower.json,Gruntfile.js,Makefile,.npmrc.proregistry,rollup.config.js,.tm_properties,.tool-versions,tsconfig.json,webpack.config.js}',
+ '!**/*.{{,c,m}js,min,ts}.map',
+ '!**/*.d.ts',
+
+ // only exclude the src directory for specific packages
+ // as some of them have their dist code in there and we don't want to exclude those
+ '!**/node_modules/{@fortawesome/vue-fontawesome,agent-base,jquery,localforage,m3u8-parser,marked,mpd-parser,performance-now,video.js,vue,vue-i18n,vue-router}/src/*',
+ '!**/node_modules/**/{bin,man,scripts}/*',
+ '!**/node_modules/jquery/dist/jquery.slim*.js',
+ '!**/node_modules/video.js/dist/{alt/*,video.js}',
+ '!**/node_modules/@videojs/*/src'
+ ],
dmg: {
contents: [
{
@@ -125,6 +146,7 @@ builder
.build({
targets,
config,
+ publish: 'never'
})
.then(m => {
console.log(m)
diff --git a/_scripts/webpack.main.config.js b/_scripts/webpack.main.config.js
index c87c3cbb71fc..d7316fd74293 100644
--- a/_scripts/webpack.main.config.js
+++ b/_scripts/webpack.main.config.js
@@ -23,7 +23,7 @@ const config = {
module: {
rules: [
{
- test: /\.(j|t)s$/,
+ test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
@@ -76,21 +76,24 @@ if (isDevMode) {
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/web/static'),
globOptions: {
- ignore: ['.*', 'pwabuilder-sw.js'],
+ dot: true,
+ ignore: ['**/.*', '**/pwabuilder-sw.js', '**/dashFiles/**', '**/storyboards/**'],
},
},
{
from: path.join(__dirname, '../_icons'),
to: path.join(__dirname, '../dist/_icons'),
globOptions: {
- ignore: ['.*'],
+ dot: true,
+ ignore: ['**/.*'],
},
},
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/images'),
globOptions: {
- ignore: ['.*'],
+ dot: true,
+ ignore: ['**/.*'],
},
},
]
diff --git a/_scripts/webpack.renderer.config.js b/_scripts/webpack.renderer.config.js
index e8da417952cd..4f73ce068158 100644
--- a/_scripts/webpack.renderer.config.js
+++ b/_scripts/webpack.renderer.config.js
@@ -37,7 +37,7 @@ const config = {
module: {
rules: [
{
- test: /\.(j|t)s$/,
+ test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
@@ -165,28 +165,32 @@ if (isDevMode) {
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/web/static'),
globOptions: {
- ignore: ['.*', 'pwabuilder-sw.js'],
+ dot: true,
+ ignore: ['**/.*', '**/pwabuilder-sw.js', '**/dashFiles/**', '**/storyboards/**'],
},
},
{
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/static'),
globOptions: {
- ignore: ['.*', 'pwabuilder-sw.js'],
+ dot: true,
+ ignore: ['**/.*', '**/pwabuilder-sw.js', '**/dashFiles/**', '**/storyboards/**'],
},
},
{
from: path.join(__dirname, '../_icons'),
to: path.join(__dirname, '../dist/web/_icons'),
globOptions: {
- ignore: ['.*'],
+ dot: true,
+ ignore: ['**/.*'],
},
},
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/web/images'),
globOptions: {
- ignore: ['.*'],
+ dot: true,
+ ignore: ['**/.*'],
},
},
]
diff --git a/_scripts/webpack.web.config.js b/_scripts/webpack.web.config.js
index a95913d5f58f..1b15f319f080 100644
--- a/_scripts/webpack.web.config.js
+++ b/_scripts/webpack.web.config.js
@@ -23,7 +23,7 @@ const config = {
module: {
rules: [
{
- test: /\.(j|t)s$/,
+ test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
@@ -163,21 +163,24 @@ if (isDevMode) {
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/web/static'),
globOptions: {
- ignore: ['.*', 'pwabuilder-sw.js'],
+ dot: true,
+ ignore: ['**/.*', '**/pwabuilder-sw.js', '**/dashFiles/**', '**/storyboards/**'],
},
},
{
from: path.join(__dirname, '../_icons'),
to: path.join(__dirname, '../dist/web/_icons'),
globOptions: {
- ignore: ['.*'],
+ dot: true,
+ ignore: ['**/.*'],
},
},
{
from: path.join(__dirname, '../src/renderer/assets/img'),
to: path.join(__dirname, '../dist/web/images'),
globOptions: {
- ignore: ['.*'],
+ dot: true,
+ ignore: ['**/.*'],
},
},
]
diff --git a/_scripts/webpack.workers.config.js b/_scripts/webpack.workers.config.js
index 7b6bb8421425..7ca42754f518 100644
--- a/_scripts/webpack.workers.config.js
+++ b/_scripts/webpack.workers.config.js
@@ -26,7 +26,7 @@ const config = {
module: {
rules: [
{
- test: /\.(j|t)s$/,
+ test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
diff --git a/package.json b/package.json
index a63a0b8e45fe..e8861bcbe62e 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "freetube",
"productName": "FreeTube",
"description": "A private YouTube client",
- "version": "0.17.0",
+ "version": "0.17.1",
"license": "AGPL-3.0-or-later",
"main": "./dist/main.js",
"private": true,
@@ -25,7 +25,7 @@
"build-release": "node _scripts/build.js",
"build-release:arm64": "node _scripts/build.js arm64",
"build-release:arm32": "node _scripts/build.js arm32",
- "clean": "rimraf build/ dashFiles/ dist/ storyboards/",
+ "clean": "rimraf build/ static/dashFiles/ dist/ static/storyboards/",
"debug": "run-s rebuild:electron debug-runner",
"debug-runner": "node _scripts/dev-runner.js --remote-debug",
"dev": "run-s rebuild:electron dev-runner",
@@ -60,7 +60,7 @@
"js-yaml": "^4.1.0",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
- "marked": "^4.0.15",
+ "marked": "^4.0.17",
"nedb-promises": "^5.0.1",
"opml-to-json": "^1.0.1",
"rss-parser": "^3.12.0",
@@ -79,7 +79,7 @@
"yt-channel-info": "^3.0.4",
"yt-dash-manifest-generator": "1.1.0",
"yt-trending-scraper": "^2.0.1",
- "ytdl-core": "^4.11.0",
+ "ytdl-core": "git+https://github.com/absidue/node-ytdl-core#temp-fix-11-08-2022",
"ytpl": "^2.3.0",
"ytsr": "^3.8.0"
},
@@ -92,7 +92,7 @@
"copy-webpack-plugin": "^9.0.1",
"css-loader": "5.2.6",
"electron": "^16.2.7",
- "electron-builder": "^22.11.7",
+ "electron-builder": "^23.0.3",
"electron-builder-squirrel-windows": "^22.13.1",
"electron-debug": "^3.2.0",
"eslint": "^7.32.0",
@@ -104,11 +104,9 @@
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^7.17.0",
- "fast-glob": "^3.2.7",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.2",
"mini-css-extract-plugin": "^2.2.2",
- "node-abi": "^2.30.1",
"node-loader": "^2.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.2",
diff --git a/src/main/index.js b/src/main/index.js
index ca7e0f3e27b2..7c733b7c3c71 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -185,6 +185,8 @@ function runApp() {
return '#000000'
case 'dracula':
return '#282a36'
+ case 'catppuccin-mocha':
+ return '#1e1e2e'
case 'system':
default:
return nativeTheme.shouldUseDarkColors ? '#212121' : '#f1f1f1'
@@ -816,8 +818,12 @@ function runApp() {
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'resetzoom' },
- { role: 'zoomin' },
+ { role: 'resetzoom', accelerator: 'CmdOrCtrl+num0', visible: false },
+ { role: 'zoomin', accelerator: 'CmdOrCtrl+Plus' },
+ { role: 'zoomin', accelerator: 'CmdOrCtrl+=', visible: false },
+ { role: 'zoomin', accelerator: 'CmdOrCtrl+numadd', visible: false },
{ role: 'zoomout' },
+ { role: 'zoomout', accelerator: 'CmdOrCtrl+numsub', visible: false },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
diff --git a/src/renderer/components/distraction-settings/distraction-settings.js b/src/renderer/components/distraction-settings/distraction-settings.js
index 790368b43dd4..891cf7021550 100644
--- a/src/renderer/components/distraction-settings/distraction-settings.js
+++ b/src/renderer/components/distraction-settings/distraction-settings.js
@@ -45,6 +45,18 @@ export default Vue.extend({
},
hideActiveSubscriptions: function () {
return this.$store.getters.getHideActiveSubscriptions
+ },
+ hideVideoDescription: function () {
+ return this.$store.getters.getHideVideoDescription
+ },
+ hideComments: function () {
+ return this.$store.getters.getHideComments
+ },
+ hideLiveStreams: function() {
+ return this.$store.getters.getHideLiveStreams
+ },
+ hideSharingActions: function() {
+ return this.$store.getters.getHideSharingActions
}
},
methods: {
@@ -68,7 +80,11 @@ export default Vue.extend({
'updateHideLiveChat',
'updateHideActiveSubscriptions',
'updatePlayNextVideo',
- 'updateDefaultTheatreMode'
+ 'updateDefaultTheatreMode',
+ 'updateHideVideoDescription',
+ 'updateHideComments',
+ 'updateHideLiveStreams',
+ 'updateHideSharingActions'
])
}
})
diff --git a/src/renderer/components/distraction-settings/distraction-settings.vue b/src/renderer/components/distraction-settings/distraction-settings.vue
index aef21062d13a..4c59582d6f22 100644
--- a/src/renderer/components/distraction-settings/distraction-settings.vue
+++ b/src/renderer/components/distraction-settings/distraction-settings.vue
@@ -38,6 +38,18 @@
:default-value="hideActiveSubscriptions"
@change="updateHideActiveSubscriptions"
/>
+
+
+
+
diff --git a/src/renderer/components/external-player-settings/external-player-settings.js b/src/renderer/components/external-player-settings/external-player-settings.js
index 04d7bd260740..f847a98748db 100644
--- a/src/renderer/components/external-player-settings/external-player-settings.js
+++ b/src/renderer/components/external-player-settings/external-player-settings.js
@@ -24,7 +24,10 @@ export default Vue.extend({
},
externalPlayerNames: function () {
- return this.$store.getters.getExternalPlayerNames
+ const fallbackNames = this.$store.getters.getExternalPlayerNames
+ const nameTranslationKeys = this.$store.getters.getExternalPlayerNameTranslationKeys
+
+ return nameTranslationKeys.map((translationKey, idx) => this.$te(translationKey) ? this.$t(translationKey) : fallbackNames[idx])
},
externalPlayerValues: function () {
return this.$store.getters.getExternalPlayerValues
diff --git a/src/renderer/components/ft-age-restricted/ft-age-restricted.js b/src/renderer/components/ft-age-restricted/ft-age-restricted.js
new file mode 100644
index 000000000000..5769012b54a8
--- /dev/null
+++ b/src/renderer/components/ft-age-restricted/ft-age-restricted.js
@@ -0,0 +1,22 @@
+import Vue from 'vue'
+
+export default Vue.extend({
+ name: 'FtAgeRestricted',
+ props: {
+ contentTypeString: {
+ type: String,
+ required: true
+ }
+ },
+ computed: {
+ emoji: function () {
+ const emojis = ['😵', '😦', '🙁', '☹️', '😦', '🤫', '😕']
+ return emojis[Math.floor(Math.random() * emojis.length)]
+ },
+
+ restrictedMessage: function () {
+ const contentType = this.$t('Age Restricted.Type.' + this.contentTypeString)
+ return this.$t('Age Restricted.This $contentType is age restricted').replace('$contentType', contentType)
+ }
+ }
+})
diff --git a/src/renderer/components/ft-age-restricted/ft-age-restricted.sass b/src/renderer/components/ft-age-restricted/ft-age-restricted.sass
new file mode 100644
index 000000000000..58d44107401f
--- /dev/null
+++ b/src/renderer/components/ft-age-restricted/ft-age-restricted.sass
@@ -0,0 +1,14 @@
+.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%
diff --git a/src/renderer/components/ft-age-restricted/ft-age-restricted.vue b/src/renderer/components/ft-age-restricted/ft-age-restricted.vue
new file mode 100644
index 000000000000..abc2eac85c25
--- /dev/null
+++ b/src/renderer/components/ft-age-restricted/ft-age-restricted.vue
@@ -0,0 +1,15 @@
+
+
+
+ {{ restrictedMessage }}
+
+
+ {{ emoji }}
+
+
+
+
+
+
diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css
index fc4e51d777ef..940f3a266c60 100644
--- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css
+++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css
@@ -1,9 +1,14 @@
.bubblePadding {
position: relative;
width: 100px;
- height: 115px;
+ height: 100px;
padding: 10px;
cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ overflow: hidden;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
@@ -20,10 +25,8 @@
.bubble {
width: 50px;
height: 50px;
- margin-bottom: 5px;
- margin-left: 25px;
- border-radius: 200px 200px 200px 200px;
- -webkit-border-radius: 200px 200px 200px 200px;
+ border-radius: 100%;
+ -webkit-border-radius: 100%;
}
.selected {
@@ -41,8 +44,10 @@
}
.channelName {
+ display: block;
font-size: 13px;
- height: 60px;
overflow: hidden;
text-align: center;
+ text-overflow: ellipsis;
+ width: 100%;
}
diff --git a/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.js b/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.js
index 5ff6c2f7513c..237ce32a4e56 100644
--- a/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.js
+++ b/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.js
@@ -33,6 +33,11 @@ export default Vue.extend({
visible: this.firstScreen
}
},
+ computed: {
+ hideLiveStreams: function() {
+ return this.$store.getters.getHideLiveStreams
+ }
+ },
methods: {
onVisibilityChanged: function (visible) {
this.visible = visible
diff --git a/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue b/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue
index e49915e2db4b..48f7057aa49b 100644
--- a/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue
+++ b/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue
@@ -1,5 +1,6 @@
+
+
+
+ {{ $t("Settings.Parental Control Settings.Parental Control Settings") }}
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/playlist-info/playlist-info.js b/src/renderer/components/playlist-info/playlist-info.js
index d59c30a54d48..f6edc105c7d3 100644
--- a/src/renderer/components/playlist-info/playlist-info.js
+++ b/src/renderer/components/playlist-info/playlist-info.js
@@ -35,6 +35,10 @@ export default Vue.extend({
}
},
computed: {
+ hideSharingActions: function() {
+ return this.$store.getters.getHideSharingActions
+ },
+
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance
},
diff --git a/src/renderer/components/playlist-info/playlist-info.vue b/src/renderer/components/playlist-info/playlist-info.vue
index 1fc1bfbf0d4c..b3361cd03088 100644
--- a/src/renderer/components/playlist-info/playlist-info.vue
+++ b/src/renderer/components/playlist-info/playlist-info.vue
@@ -47,6 +47,7 @@
+
+
+
+
+
+ {{ $t("Channels.Channels") }}
+
+
+
+
+
+
+
+ {{ $t("Channels.Channels") }}
+
+
-
-
-
-
-
-
-
-
diff --git a/src/renderer/components/theme-settings/theme-settings.js b/src/renderer/components/theme-settings/theme-settings.js
index 25a0a1368740..d91a6e0191c1 100644
--- a/src/renderer/components/theme-settings/theme-settings.js
+++ b/src/renderer/components/theme-settings/theme-settings.js
@@ -33,7 +33,8 @@ export default Vue.extend({
'light',
'dark',
'black',
- 'dracula'
+ 'dracula',
+ 'catppuccinMocha'
]
}
},
@@ -91,7 +92,8 @@ export default Vue.extend({
this.$t('Settings.Theme Settings.Base Theme.Light'),
this.$t('Settings.Theme Settings.Base Theme.Dark'),
this.$t('Settings.Theme Settings.Base Theme.Black'),
- this.$t('Settings.Theme Settings.Base Theme.Dracula')
+ this.$t('Settings.Theme Settings.Base Theme.Dracula'),
+ this.$t('Settings.Theme Settings.Base Theme.Catppuccin Mocha')
]
},
diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js
index c857a3736893..74791a8d634d 100644
--- a/src/renderer/components/top-nav/top-nav.js
+++ b/src/renderer/components/top-nav/top-nav.js
@@ -32,6 +32,10 @@ export default Vue.extend({
return this.$store.getters.getUsingElectron
},
+ hideSearchBar: function () {
+ return this.$store.getters.getHideSearchBar
+ },
+
enableSearchSuggestions: function () {
return this.$store.getters.getEnableSearchSuggestions
},
@@ -170,10 +174,11 @@ export default Vue.extend({
}
case 'channel': {
- const { channelId, subPath } = result
+ const { channelId, idType, subPath } = result
this.$router.push({
- path: `/channel/${channelId}/${subPath}`
+ path: `/channel/${channelId}/${subPath}`,
+ query: { idType }
})
break
}
diff --git a/src/renderer/components/top-nav/top-nav.vue b/src/renderer/components/top-nav/top-nav.vue
index 450807f919fe..257b3ff2c5ad 100644
--- a/src/renderer/components/top-nav/top-nav.vue
+++ b/src/renderer/components/top-nav/top-nav.vue
@@ -33,6 +33,7 @@
@keypress="historyForward"
/>
diff --git a/src/renderer/components/watch-video-info/watch-video-info.js b/src/renderer/components/watch-video-info/watch-video-info.js
index f47f93179a89..d89da7e55ab8 100644
--- a/src/renderer/components/watch-video-info/watch-video-info.js
+++ b/src/renderer/components/watch-video-info/watch-video-info.js
@@ -126,6 +126,14 @@ export default Vue.extend({
return this.$store.getters.getCurrentInvidiousInstance
},
+ hideSharingActions: function() {
+ return this.$store.getters.getHideSharingActions
+ },
+
+ hideUnsubscribeButton: function() {
+ return this.$store.getters.getHideUnsubscribeButton
+ },
+
currentLocale: function () {
return this.$store.getters.getCurrentLocale
},
diff --git a/src/renderer/components/watch-video-info/watch-video-info.vue b/src/renderer/components/watch-video-info/watch-video-info.vue
index 15b6bc0e1226..db0bce524a4f 100644
--- a/src/renderer/components/watch-video-info/watch-video-info.vue
+++ b/src/renderer/components/watch-video-info/watch-video-info.vue
@@ -27,6 +27,7 @@
{{ channelName }}
h(App)
})
-// to avoid accesing electron api from web app build
+// to avoid accessing electron api from web app build
if (window && window.process && window.process.type === 'renderer') {
const { ipcRenderer } = require('electron')
diff --git a/src/renderer/router/index.js b/src/renderer/router/index.js
index 8c9029c92d65..3b956dea7732 100644
--- a/src/renderer/router/index.js
+++ b/src/renderer/router/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue'
import Router from 'vue-router'
import Subscriptions from '../views/Subscriptions/Subscriptions.vue'
+import SubscribedChannels from '../views/SubscribedChannels/SubscribedChannels.vue'
import ProfileSettings from '../views/ProfileSettings/ProfileSettings.vue'
import ProfileEdit from '../views/ProfileEdit/ProfileEdit.vue'
import Trending from '../views/Trending/Trending.vue'
@@ -14,9 +15,41 @@ import Playlist from '../views/Playlist/Playlist.vue'
import Channel from '../views/Channel/Channel.vue'
import Watch from '../views/Watch/Watch.vue'
-Vue.use(Router)
+class CustomRouter extends Router {
+ push(location) {
+ // only navigates if the location is not identical to the current location
-const router = new Router({
+ const currentQueryUSP = new URLSearchParams(router.currentRoute.query)
+ let newPath = ''
+ let newQueryUSP = new URLSearchParams()
+
+ if (typeof location === 'string') {
+ if (location.includes('?')) {
+ const urlParts = location.split('?')
+ newPath = urlParts[0]
+ newQueryUSP = new URLSearchParams(urlParts[1])
+ } else {
+ newPath = location
+ // newQueryUSP already empty
+ }
+ } else {
+ newPath = location.path
+ newQueryUSP = new URLSearchParams(location.query)
+ }
+
+ const pathsAreDiff = router.currentRoute.path !== newPath
+ // Comparing `URLSearchParams` objects directly will always be different
+ const queriesAreDiff = newQueryUSP.toString() !== currentQueryUSP.toString()
+
+ if (pathsAreDiff || queriesAreDiff) {
+ return super.push(location)
+ }
+ }
+}
+
+Vue.use(CustomRouter)
+
+const router = new CustomRouter({
routes: [
{
path: '/',
@@ -34,6 +67,14 @@ const router = new Router({
},
component: Subscriptions
},
+ {
+ path: '/subscribedchannels',
+ meta: {
+ title: 'Channels.Title',
+ icon: 'fa-home'
+ },
+ component: SubscribedChannels
+ },
{
path: '/settings/profile',
meta: {
@@ -142,7 +183,7 @@ const router = new Router({
component: Watch
}
],
- scrollBehavior (to, from, savedPosition) {
+ scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (savedPosition !== null) {
diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js
index 3d63793ddcb6..2d5011ed839b 100644
--- a/src/renderer/store/modules/settings.js
+++ b/src/renderer/store/modules/settings.js
@@ -192,11 +192,17 @@ const state = {
hideActiveSubscriptions: false,
hideChannelSubscriptions: false,
hideCommentLikes: false,
+ hideComments: false,
+ hideVideoDescription: false,
hideLiveChat: false,
+ hideLiveStreams: false,
hidePlaylists: false,
hidePopularVideos: false,
hideRecommendedVideos: false,
+ hideSearchBar: false,
+ hideSharingActions: false,
hideTrendingVideos: false,
+ hideUnsubscribeButton: false,
hideVideoLikesAndDislikes: false,
hideVideoViews: false,
hideWatchedSubs: false,
@@ -213,6 +219,7 @@ const state = {
rememberHistory: true,
removeVideoMetaFiles: true,
saveWatchedProgress: true,
+ showFamilyFriendlyOnly: false,
sponsorBlockShowSkippedToast: true,
sponsorBlockUrl: 'https://sponsor.ajay.app',
sponsorBlockSponsor: {
diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js
index 911385c81457..5e7d120ab016 100644
--- a/src/renderer/store/modules/utils.js
+++ b/src/renderer/store/modules/utils.js
@@ -50,7 +50,22 @@ const state = {
'DraculaPink',
'DraculaPurple',
'DraculaRed',
- 'DraculaYellow'
+ 'DraculaYellow',
+ 'CatppuccinMochaRosewater',
+ 'CatppuccinMochaFlamingo',
+ 'CatppuccinMochaPink',
+ 'CatppuccinMochaMauve',
+ 'CatppuccinMochaRed',
+ 'CatppuccinMochaMaroon',
+ 'CatppuccinMochaPeach',
+ 'CatppuccinMochaYellow',
+ 'CatppuccinMochaGreen',
+ 'CatppuccinMochaTeal',
+ 'CatppuccinMochaSky',
+ 'CatppuccinMochaSapphire',
+ 'CatppuccinMochaBlue',
+ 'CatppuccinMochaLavender'
+
],
colorValues: [
'#d50000',
@@ -75,9 +90,24 @@ const state = {
'#FF79C6',
'#BD93F9',
'#FF5555',
- '#F1FA8C'
+ '#F1FA8C',
+ '#F5E0DC',
+ '#F2CDCD',
+ '#F5C2E7',
+ '#CBA6F7',
+ '#F38BA8',
+ '#EBA0AC',
+ '#FAB387',
+ '#F9E2AF',
+ '#A6E3A1',
+ '#94E2D5',
+ '#89DCEB',
+ '#74C7EC',
+ '#89B4FA',
+ '#B4BEFE'
],
externalPlayerNames: [],
+ externalPlayerNameTranslationKeys: [],
externalPlayerValues: [],
externalPlayerCmdArguments: {}
}
@@ -139,6 +169,10 @@ const getters = {
return state.externalPlayerNames
},
+ getExternalPlayerNameTranslationKeys () {
+ return state.externalPlayerNameTranslationKeys
+ },
+
getExternalPlayerValues () {
return state.externalPlayerValues
},
@@ -592,7 +626,7 @@ const actions = {
let urlType = 'unknown'
const channelPattern =
- /^\/(?:(c|channel|user)\/)?(?[^/]+)(?:\/(join|featured|videos|playlists|about|community|channels))?\/?$/
+ /^\/(?:(?channel|user|c)\/)?(?[^/]+)(?:\/(join|featured|videos|playlists|about|community|channels))?\/?$/
const typePatterns = new Map([
['playlist', /^\/playlist\/?$/],
@@ -690,7 +724,9 @@ const actions = {
*/
case 'channel': {
- const channelId = url.pathname.match(channelPattern).groups.channelId
+ const match = url.pathname.match(channelPattern)
+ const channelId = match.groups.channelId
+ const idType = ['channel', 'user', 'c'].indexOf(match.groups.type) + 1
if (!channelId) {
throw new Error('Channel: could not extract id')
}
@@ -712,6 +748,7 @@ const actions = {
return {
urlType: 'channel',
channelId,
+ idType,
subPath
}
}
@@ -907,10 +944,11 @@ const actions = {
}
const externalPlayerMap = JSON.parse(fileData).map((entry) => {
- return { name: entry.name, value: entry.value, cmdArguments: entry.cmdArguments }
+ return { name: entry.name, nameTranslationKey: entry.nameTranslationKey, value: entry.value, cmdArguments: entry.cmdArguments }
})
const externalPlayerNames = externalPlayerMap.map((entry) => { return entry.name })
+ const externalPlayerNameTranslationKeys = externalPlayerMap.map((entry) => { return entry.nameTranslationKey })
const externalPlayerValues = externalPlayerMap.map((entry) => { return entry.value })
const externalPlayerCmdArguments = externalPlayerMap.reduce((result, item) => {
result[item.value] = item.cmdArguments
@@ -918,6 +956,7 @@ const actions = {
}, {})
commit('setExternalPlayerNames', externalPlayerNames)
+ commit('setExternalPlayerNameTranslationKeys', externalPlayerNameTranslationKeys)
commit('setExternalPlayerValues', externalPlayerValues)
commit('setExternalPlayerCmdArguments', externalPlayerCmdArguments)
},
@@ -1130,6 +1169,10 @@ const mutations = {
state.externalPlayerNames = value
},
+ setExternalPlayerNameTranslationKeys (state, value) {
+ state.externalPlayerNameTranslationKeys = value
+ },
+
setExternalPlayerValues (state, value) {
state.externalPlayerValues = value
},
diff --git a/src/renderer/themes.css b/src/renderer/themes.css
index 0e7b1f2ab5ca..09af4504045b 100644
--- a/src/renderer/themes.css
+++ b/src/renderer/themes.css
@@ -112,6 +112,30 @@
--logo-text: url("~../../_icons/textDraculaLightSmall.png");
}
+.catppuccinMocha {
+ --primary-text-color: #cdd6f4;
+ --secondary-text-color: #bac2de;
+ --tertiary-text-color: #a6adc8;
+ --primary-input-color: rgba(0, 0, 0, 0.50);
+ --primary-shadow-color: rgba(0, 0, 0, 0.75);
+ --title-color: var(--accent-color);
+ --bg-color: #1e1e2e;
+ --link-color: var(--accent-color);
+ --link-visited-color: var(--accent-color-visited);
+ --favorite-icon-color: #f9e2af;
+ --card-bg-color: #181825;
+ --secondary-card-bg-color: #1e1e2e;
+ --scrollbar-color: #313244;
+ --scrollbar-color-hover: #3D4051;
+ --side-nav-color: #181825;
+ --side-nav-hover-color: #11111b;
+ --side-nav-active-color: #11111b;
+ --search-bar-color: #313244;
+ --instance-menu-color: var(--search-bar-color);
+ --logo-icon: url("~../../_icons/iconCatppuccinMochaLightSmall.png");
+ --logo-text: url("~../../_icons/textCatppuccinMochaLightSmall.png");
+}
+
.mainRed {
--primary-color: #f44336;
--primary-color-hover: #e53935;
@@ -319,6 +343,132 @@
--logo-text-bar-color: url("~../../_icons/textDraculaDarkSmall.png");
}
+.mainCatppuccinMochaRosewater {
+ --primary-color: #f5e0dc;
+ --primary-color-hover: #fceeec;
+ --primary-color-active: #e1c8c3;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaFlamingo {
+ --primary-color: #f2cdcd;
+ --primary-color-hover: #f2e1e1;
+ --primary-color-active: #ddb7b7;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaPink {
+ --primary-color: #f5c2e7;
+ --primary-color-hover: #f3d2ea;
+ --primary-color-active: #dca3cd;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaMauve {
+ --primary-color: #cba6f7;
+ --primary-color-hover: #d4b7f8;
+ --primary-color-active: #b38fdf;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaRed {
+ --primary-color: #f38ba8;
+ --primary-color-hover: #f0a4b9;
+ --primary-color-active: #de7693;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaMaroon {
+ --primary-color: #eba0ac;
+ --primary-color-hover: #eabbc3;
+ --primary-color-active: #d68895;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaPeach {
+ --primary-color: #fab387;
+ --primary-color-hover: #f7c7a9;
+ --primary-color-active: #e1996d;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaYellow {
+ --primary-color: #f9e2af;
+ --primary-color-hover: #feeecd;
+ --primary-color-active: #dec48d;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaGreen {
+ --primary-color: #a6e3a1;
+ --primary-color-hover: #bfebbb;
+ --primary-color-active: #86c780;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaTeal {
+ --primary-color: #94e2d5;
+ --primary-color-hover: #aceae0;
+ --primary-color-active: #6fc5b7;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaSky {
+ --primary-color: #89dceb;
+ --primary-color-hover: #a3e4f0;
+ --primary-color-active: #68bcca;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaSapphire {
+ --primary-color: #74c7ec;
+ --primary-color-hover: #93d1ed;
+ --primary-color-active: #59a9cf;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaBlue {
+ --primary-color: #89b4fa;
+ --primary-color-hover: #a6c8ff;
+ --primary-color-active: #6593df;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
+.mainCatppuccinMochaLavender {
+ --primary-color: #b4befe;
+ --primary-color-hover: #c9d0ff;
+ --primary-color-active: #8d98e4;
+ --text-with-main-color: #1e1e2e;
+ --logo-icon-bar-color: url("~../../_icons/iconCatppuccinMochaDarkSmall.png");
+ --logo-text-bar-color: url("~../../_icons/textCatppuccinMochaDarkSmall.png");
+}
+
.secRed {
--accent-color: #f44336;
--accent-color-hover: #e53935;
@@ -618,6 +768,188 @@
--accent-color-opacity4: rgba(98,114,164,0.24);
}
+.secCatppuccinMochaRosewater {
+ --accent-color: #f5e0dc;
+ --accent-color-hover: #fceeec;
+ --accent-color-active: #e1c8c3;
+ --accent-color-light: #F8EAE7;
+ --accent-color-visited: #D3A197;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(245,224,220,0.04);
+ --accent-color-opacity2: rgba(245,224,220,0.12);
+ --accent-color-opacity3: rgba(245,224,220,0.16);
+ --accent-color-opacity4: rgba(245,224,220,0.24);
+}
+
+.secCatppuccinMochaFlamingo {
+ --accent-color: #f2cdcd;
+ --accent-color-hover: #f3d7d7;
+ --accent-color-active: #ddb7b7;
+ --accent-color-light: #F7DFDF;
+ --accent-color-visited: #cf9898;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(242,205,205,0.04);
+ --accent-color-opacity2: rgba(242,205,205,0.12);
+ --accent-color-opacity3: rgba(242,205,205,0.16);
+ --accent-color-opacity4: rgba(242,205,205,0.24);
+}
+
+.secCatppuccinMochaPink {
+ --accent-color: #f5c2e7;
+ --accent-color-hover: #f3cee9;
+ --accent-color-active: #dca3cd;
+ --accent-color-light: #f4dbed;
+ --accent-color-visited: #d28fc0;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(245,194,231,0.04);
+ --accent-color-opacity2: rgba(245,194,231,0.12);
+ --accent-color-opacity3: rgba(245,194,231,0.16);
+ --accent-color-opacity4: rgba(245,194,231,0.24);
+}
+
+.secCatppuccinMochaMauve {
+ --accent-color: #cba6f7;
+ --accent-color-hover: #d4b7f8;
+ --accent-color-active: #b38fdf;
+ --accent-color-light: #D6B9F9;
+ --accent-color-visited: #A171DA;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(203,166,247,0.04);
+ --accent-color-opacity2: rgba(203,166,247,0.12);
+ --accent-color-opacity3: rgba(203,166,247,0.16);
+ --accent-color-opacity4: rgba(203,166,247,0.24);
+}
+
+.secCatppuccinMochaRed {
+ --accent-color: #f38ba8;
+ --accent-color-hover: #f399b2;
+ --accent-color-active: #de7693;
+ --accent-color-light: #F5A3BA;
+ --accent-color-visited: #D56C89;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(243,139,168,0.04);
+ --accent-color-opacity2: rgba(243,139,168,0.12);
+ --accent-color-opacity3: rgba(243,139,168,0.16);
+ --accent-color-opacity4: rgba(243,139,168,0.24);
+}
+
+.secCatppuccinMochaMaroon {
+ --accent-color: #eba0ac;
+ --accent-color-hover: #ebb4bd;
+ --accent-color-active: #d68895;
+ --accent-color-light: #F0B7C0;
+ --accent-color-visited: #C86A79;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(235,160,172,0.04);
+ --accent-color-opacity2: rgba(235,160,172,0.12);
+ --accent-color-opacity3: rgba(235,160,172,0.16);
+ --accent-color-opacity4: rgba(235,160,172,0.24);
+}
+
+.secCatppuccinMochaPeach {
+ --accent-color: #fab387;
+ --accent-color-hover: #f7bd99;
+ --accent-color-active: #e1996d;
+ --accent-color-light: #FBC4A2;
+ --accent-color-visited: #D78A5B;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(250,179,135,0.04);
+ --accent-color-opacity2: rgba(250,179,135,0.12);
+ --accent-color-opacity3: rgba(250,179,135,0.16);
+ --accent-color-opacity4: rgba(250,179,135,0.24);
+}
+
+.secCatppuccinMochaYellow {
+ --accent-color: #f9e2af;
+ --accent-color-hover: #f9e7bf;
+ --accent-color-active: #dec48d;
+ --accent-color-light: #FBECCB;
+ --accent-color-visited: #D5B05D;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(249,226,175,0.04);
+ --accent-color-opacity2: rgba(249,226,175,0.12);
+ --accent-color-opacity3: rgba(249,226,175,0.16);
+ --accent-color-opacity4: rgba(249,226,175,0.24);
+}
+
+.secCatppuccinMochaGreen {
+ --accent-color: #a6e3a1;
+ --accent-color-hover: #b6e3b2;
+ --accent-color-active: #86c780;
+ --accent-color-light: #BCEAB8;
+ --accent-color-visited: #6ED166;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(166,227,161,0.04);
+ --accent-color-opacity2: rgba(166,227,161,0.12);
+ --accent-color-opacity3: rgba(166,227,161,0.16);
+ --accent-color-opacity4: rgba(166,227,161,0.24);
+}
+
+.secCatppuccinMochaTeal {
+ --accent-color: #94e2d5;
+ --accent-color-hover: #a1dfd5;
+ --accent-color-active: #6fc5b7;
+ --accent-color-light: #AFE9DF;
+ --accent-color-visited: #5CCCB9;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(148,226,213,0.04);
+ --accent-color-opacity2: rgba(148,226,213,0.12);
+ --accent-color-opacity3: rgba(148,226,213,0.16);
+ --accent-color-opacity4: rgba(148,226,213,0.24);
+}
+
+.secCatppuccinMochaSky {
+ --accent-color: #89dceb;
+ --accent-color-hover: #99dfeb;
+ --accent-color-active: #68bcca;
+ --accent-color-light: #9FE3EF;
+ --accent-color-visited: #64C2D3;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(137,220,235,0.04);
+ --accent-color-opacity2: rgba(137,220,235,0.12);
+ --accent-color-opacity3: rgba(137,220,235,0.16);
+ --accent-color-opacity4: rgba(137,220,235,0.24);
+}
+
+.secCatppuccinMochaSapphire {
+ --accent-color: #74c7ec;
+ --accent-color-hover: #84c7e6;
+ --accent-color-active: #59a9cf;
+ --accent-color-light: #93D4F0;
+ --accent-color-visited: #6AB6D7;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(116,199,236,0.04);
+ --accent-color-opacity2: rgba(116,199,236,0.12);
+ --accent-color-opacity3: rgba(116,199,236,0.16);
+ --accent-color-opacity4: rgba(116,199,236,0.24);
+}
+
+.secCatppuccinMochaBlue {
+ --accent-color: #89b4fa;
+ --accent-color-hover: #9bbef6;
+ --accent-color-active: #6593df;
+ --accent-color-light: #A7C7FB;
+ --accent-color-visited: #739CDD;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(137,220,235,0.04);
+ --accent-color-opacity2: rgba(137,220,235,0.12);
+ --accent-color-opacity3: rgba(137,220,235,0.16);
+ --accent-color-opacity4: rgba(137,220,235,0.24);
+}
+
+.secCatppuccinMochaLavender {
+ --accent-color: #b4befe;
+ --accent-color-hover: #c9d0ff;
+ --accent-color-active: #8d98e4;
+ --accent-color-light: #D2D8FE;
+ --accent-color-visited: #96A1E9;
+ --text-with-accent-color: #1e1e2e;
+ --accent-color-opacity1: rgba(180,190,254,0.04);
+ --accent-color-opacity2: rgba(180,190,254,0.12);
+ --accent-color-opacity3: rgba(180,190,254,0.16);
+ --accent-color-opacity4: rgba(180,190,254,0.24);
+}
+
body {
margin: 0;
min-height: 100vh;
diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js
index 6c9197d2c3a7..c8bc6e7f5d81 100644
--- a/src/renderer/views/Channel/Channel.js
+++ b/src/renderer/views/Channel/Channel.js
@@ -8,6 +8,7 @@ import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
+import FtAgeRestricted from '../../components/ft-age-restricted/ft-age-restricted.vue'
import ytch from 'yt-channel-info'
import autolinker from 'autolinker'
@@ -23,7 +24,8 @@ export default Vue.extend({
'ft-flex-box': FtFlexBox,
'ft-channel-bubble': FtChannelBubble,
'ft-loader': FtLoader,
- 'ft-element-list': FtElementList
+ 'ft-element-list': FtElementList,
+ 'ft-age-restricted': FtAgeRestricted
},
data: function () {
return {
@@ -31,6 +33,7 @@ export default Vue.extend({
isElementListLoading: false,
currentTab: 'videos',
id: '',
+ idType: 0,
channelName: '',
bannerUrl: '',
thumbnailUrl: '',
@@ -50,6 +53,7 @@ export default Vue.extend({
searchResults: [],
shownElementList: [],
apiUsed: '',
+ isFamilyFriendly: false,
errorMessage: '',
videoSelectValues: [
'newest',
@@ -75,6 +79,14 @@ export default Vue.extend({
return this.$store.getters.getBackendFallback
},
+ hideUnsubscribeButton: function() {
+ return this.$store.getters.getHideUnsubscribeButton
+ },
+
+ showFamilyFriendlyOnly: function() {
+ return this.$store.getters.getShowFamilyFriendlyOnly
+ },
+
currentInvidiousInstance: function () {
return this.$store.getters.getCurrentInvidiousInstance
},
@@ -159,7 +171,9 @@ export default Vue.extend({
watch: {
$route() {
// react to route changes...
+ this.originalId = this.$route.params.id
this.id = this.$route.params.id
+ this.idType = this.$route.query.idType ? Number(this.$route.query.idType) : 0
this.currentTab = this.$route.params.currentTab ?? 'videos'
this.latestVideosPage = 2
this.searchPage = 2
@@ -221,7 +235,9 @@ export default Vue.extend({
}
},
mounted: function () {
+ this.originalId = this.$route.params.id
this.id = this.$route.params.id
+ this.idType = this.$route.query.idType ? Number(this.$route.query.idType) : 0
this.currentTab = this.$route.params.currentTab ?? 'videos'
this.isLoading = true
@@ -248,14 +264,14 @@ export default Vue.extend({
getChannelInfoLocal: function () {
this.apiUsed = 'local'
- const expectedId = this.id
- ytch.getChannelInfo({ channelId: expectedId }).then((response) => {
+ const expectedId = this.originalId
+ ytch.getChannelInfo({ channelId: this.id, channelIdType: this.idType }).then((response) => {
if (response.alertMessage) {
this.setErrorMessage(response.alertMessage)
return
}
this.errorMessage = ''
- if (expectedId !== this.id) {
+ if (expectedId !== this.originalId) {
return
}
@@ -263,7 +279,10 @@ export default Vue.extend({
const channelName = response.author
const channelThumbnailUrl = response.authorThumbnails[2].url
this.id = channelId
+ // set the id type to 1 so that searching and sorting work
+ this.idType = 1
this.channelName = channelName
+ this.isFamilyFriendly = response.isFamilyFriendly
document.title = `${this.channelName} - ${process.env.PRODUCT_NAME}`
if (this.hideChannelSubscriptions || response.subscriberCount === 0) {
this.subCount = null
@@ -320,9 +339,9 @@ export default Vue.extend({
getChannelVideosLocal: function () {
this.isElementListLoading = true
- const expectedId = this.id
- ytch.getChannelVideos({ channelId: expectedId, sortBy: this.videoSortBy }).then((response) => {
- if (expectedId !== this.id) {
+ const expectedId = this.originalId
+ ytch.getChannelVideos({ channelId: this.id, channelIdType: this.idType, sortBy: this.videoSortBy }).then((response) => {
+ if (expectedId !== this.originalId) {
return
}
@@ -371,9 +390,9 @@ export default Vue.extend({
this.isLoading = true
this.apiUsed = 'invidious'
- const expectedId = this.id
- this.invidiousGetChannelInfo(expectedId).then((response) => {
- if (expectedId !== this.id) {
+ const expectedId = this.originalId
+ this.invidiousGetChannelInfo(this.id).then((response) => {
+ if (expectedId !== this.originalId) {
return
}
@@ -383,6 +402,7 @@ export default Vue.extend({
this.channelName = channelName
document.title = `${this.channelName} - ${process.env.PRODUCT_NAME}`
this.id = channelId
+ this.isFamilyFriendly = response.isFamilyFriendly
if (this.hideChannelSubscriptions) {
this.subCount = null
} else {
@@ -450,9 +470,9 @@ export default Vue.extend({
},
getPlaylistsLocal: function () {
- const expectedId = this.id
- ytch.getChannelPlaylistInfo({ channelId: expectedId, sortBy: this.playlistSortBy }).then((response) => {
- if (expectedId !== this.id) {
+ const expectedId = this.originalId
+ ytch.getChannelPlaylistInfo({ channelId: this.id, channelIdType: this.idType, sortBy: this.playlistSortBy }).then((response) => {
+ if (expectedId !== this.originalId) {
return
}
@@ -719,7 +739,7 @@ export default Vue.extend({
searchChannelLocal: function () {
if (this.searchContinuationString === '') {
- ytch.searchChannel({ channelId: this.id, query: this.lastSearchQuery }).then((response) => {
+ ytch.searchChannel({ channelId: this.id, channelIdType: this.idType, query: this.lastSearchQuery }).then((response) => {
console.log(response)
this.searchResults = response.items
this.isElementListLoading = false
diff --git a/src/renderer/views/Channel/Channel.vue b/src/renderer/views/Channel/Channel.vue
index 1d6c3195898b..e423191e497e 100644
--- a/src/renderer/views/Channel/Channel.vue
+++ b/src/renderer/views/Channel/Channel.vue
@@ -7,7 +7,7 @@
:fullscreen="true"
/>
+
diff --git a/src/renderer/views/Search/Search.js b/src/renderer/views/Search/Search.js
index c7f93076de26..49509339d186 100644
--- a/src/renderer/views/Search/Search.js
+++ b/src/renderer/views/Search/Search.js
@@ -37,6 +37,13 @@ export default Vue.extend({
backendFallback: function () {
return this.$store.getters.getBackendFallback
+ },
+
+ hideLiveStreams: function() {
+ return this.$store.getters.getHideLiveStreams
+ },
+ showFamilyFriendlyOnly: function() {
+ return this.$store.getters.getShowFamilyFriendlyOnly
}
},
watch: {
@@ -94,6 +101,7 @@ export default Vue.extend({
if (sameSearch.length > 0) {
console.log(sameSearch)
+
// Replacing the data right away causes a strange error where the data
// Shown is mixed from 2 different search results. So we'll wait a moment
// Before showing the results.
@@ -118,6 +126,8 @@ export default Vue.extend({
payload.options.pages = 1
}
+ payload.options.safeSearch = this.showFamilyFriendlyOnly
+
this.ytSearch(payload).then((result) => {
console.log(result)
if (!result) {
diff --git a/src/renderer/views/Settings/Settings.js b/src/renderer/views/Settings/Settings.js
index df4a1b02eb34..6cef643f752c 100644
--- a/src/renderer/views/Settings/Settings.js
+++ b/src/renderer/views/Settings/Settings.js
@@ -12,6 +12,7 @@ import DataSettings from '../../components/data-settings/data-settings.vue'
import DistractionSettings from '../../components/distraction-settings/distraction-settings.vue'
import ProxySettings from '../../components/proxy-settings/proxy-settings.vue'
import SponsorBlockSettings from '../../components/sponsor-block-settings/sponsor-block-settings.vue'
+import ParentControlSettings from '../../components/parental-control-settings/parental-control-settings.vue'
export default Vue.extend({
name: 'Settings',
@@ -28,7 +29,8 @@ export default Vue.extend({
'distraction-settings': DistractionSettings,
'proxy-settings': ProxySettings,
'sponsor-block-settings': SponsorBlockSettings,
- 'download-settings': DownloadSettings
+ 'download-settings': DownloadSettings,
+ 'parental-control-settings': ParentControlSettings
},
computed: {
usingElectron: function () {
diff --git a/src/renderer/views/Settings/Settings.vue b/src/renderer/views/Settings/Settings.vue
index e06e772e19ea..0039eeacdc70 100644
--- a/src/renderer/views/Settings/Settings.vue
+++ b/src/renderer/views/Settings/Settings.vue
@@ -20,6 +20,8 @@
+
+
diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.css b/src/renderer/views/SubscribedChannels/SubscribedChannels.css
new file mode 100644
index 000000000000..49d70617aff7
--- /dev/null
+++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.css
@@ -0,0 +1,79 @@
+.card {
+ width: 85%;
+ margin: 0 auto;
+ margin-bottom: 60px;
+}
+
+.message {
+ color: var(--tertiary-text-color);
+}
+
+.count {
+ margin-top: 1rem;
+}
+
+.channels {
+ width: 100%;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr) );
+ gap: 2.5rem;
+ margin-top: 2rem;
+}
+
+.channel {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ row-gap: 0.75rem;
+ padding: 0.5rem;
+}
+
+.thumbnailContainer {
+ flex-grow: 0;
+ display: flex;
+ align-items: center;
+}
+
+.channelThumbnail {
+ height: 120px;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.channelName {
+ flex-grow: 1;
+ cursor: pointer;
+ font-size: 1.1rem;
+ text-decoration: none;
+ text-align: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ padding: 0 4px;
+}
+
+.unsubscribeContainer {
+ flex-grow: 0;
+ display: flex;
+ align-items: center;
+}
+
+.unsubscribeContainer .btn {
+ padding: 5px 10px;
+}
+
+@media only screen and (max-width: 680px) {
+ .card {
+ width: 90%;
+ }
+
+ .channels {
+ gap: 1.5rem;
+ }
+
+ .channelThumbnail {
+ height: 80px;
+ }
+}
diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.js b/src/renderer/views/SubscribedChannels/SubscribedChannels.js
new file mode 100644
index 000000000000..b3c3d69b021f
--- /dev/null
+++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.js
@@ -0,0 +1,215 @@
+import Vue from 'vue'
+import { mapActions } from 'vuex'
+import FtButton from '../../components/ft-button/ft-button.vue'
+import FtCard from '../../components/ft-card/ft-card.vue'
+import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
+import FtInput from '../../components/ft-input/ft-input.vue'
+import FtPrompt from '../../components/ft-prompt/ft-prompt.vue'
+import ytch from 'yt-channel-info'
+
+export default Vue.extend({
+ name: 'SubscribedChannels',
+ components: {
+ 'ft-button': FtButton,
+ 'ft-card': FtCard,
+ 'ft-flex-box': FtFlexBox,
+ 'ft-input': FtInput,
+ 'ft-prompt': FtPrompt
+ },
+ data: function () {
+ return {
+ query: '',
+ subscribedChannels: [],
+ filteredChannels: [],
+ re: {
+ url: /(.+=\w{1})\d+(.+)/,
+ ivToIv: /^.+(ggpht.+)/,
+ ivToYt: /^.+ggpht\/(.+)/,
+ ytToIv: /^.+ggpht\.com\/(.+)/
+ },
+ thumbnailSize: 176,
+ ytBaseURL: 'https://yt3.ggpht.com',
+ showUnsubscribePrompt: false,
+ unsubscribePromptValues: [
+ 'yes',
+ 'no'
+ ],
+ channelToUnsubscribe: null,
+ errorCount: 0
+ }
+ },
+ computed: {
+ activeProfile: function () {
+ return this.$store.getters.getActiveProfile
+ },
+
+ activeProfileId: function () {
+ return this.activeProfile._id
+ },
+
+ activeSubscriptionList: function () {
+ return this.activeProfile.subscriptions
+ },
+
+ channelList: function () {
+ if (this.query !== '') {
+ return this.filteredChannels
+ } else {
+ return this.subscribedChannels
+ }
+ },
+
+ locale: function () {
+ return this.$store.getters.getCurrentLocale.replace('_', '-')
+ },
+
+ backendPreference: function () {
+ return this.$store.getters.getBackendPreference
+ },
+
+ currentInvidiousInstance: function () {
+ return this.$store.getters.getCurrentInvidiousInstance
+ },
+
+ unsubscribePromptNames: function () {
+ return [
+ this.$t('Yes'),
+ this.$t('No')
+ ]
+ }
+ },
+ watch: {
+ activeProfileId: function() {
+ this.query = ''
+ this.getSubscription()
+ },
+
+ activeSubscriptionList: function() {
+ this.getSubscription()
+ this.filterChannels()
+ }
+ },
+ mounted: function () {
+ this.getSubscription()
+ },
+ methods: {
+ getSubscription: function () {
+ this.subscribedChannels = this.activeSubscriptionList.slice().sort((a, b) => {
+ return a.name.localeCompare(b.name, this.locale)
+ })
+ },
+
+ handleInput: function(input) {
+ this.query = input
+ this.filterChannels()
+ },
+
+ filterChannels: function () {
+ if (this.query === '') {
+ this.filteredChannels = []
+ return
+ }
+
+ const escapedQuery = this.query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ const re = new RegExp(escapedQuery, 'i')
+ this.filteredChannels = this.subscribedChannels.filter(channel => {
+ return re.test(channel.name)
+ })
+ },
+
+ handleUnsubscribeButtonClick: function(channel) {
+ this.channelToUnsubscribe = channel
+ this.showUnsubscribePrompt = true
+ },
+
+ handleUnsubscribePromptClick: function(value) {
+ this.showUnsubscribePrompt = false
+ if (value !== 'yes') {
+ this.channelToUnsubscribe = null
+ return
+ }
+ this.unsubscribeChannel()
+ },
+
+ unsubscribeChannel: function () {
+ const currentProfile = JSON.parse(JSON.stringify(this.activeProfile))
+ let index = currentProfile.subscriptions.findIndex(channel => {
+ return channel.id === this.channelToUnsubscribe.id
+ })
+ currentProfile.subscriptions.splice(index, 1)
+
+ this.updateProfile(currentProfile)
+ this.showToast({
+ message: this.$t('Channels.Unsubscribed').replace('$', this.channelToUnsubscribe.name)
+ })
+
+ index = this.subscribedChannels.findIndex(channel => {
+ return channel.id === this.channelToUnsubscribe.id
+ })
+ this.subscribedChannels.splice(index, 1)
+
+ index = this.filteredChannels.findIndex(channel => {
+ return channel.id === this.channelToUnsubscribe.id
+ })
+ if (index !== -1) {
+ this.filteredChannels.splice(index, 1)
+ }
+
+ this.channelToUnsubscribe = null
+ },
+
+ thumbnailURL: function(originalURL) {
+ let newURL = originalURL
+ if (originalURL.indexOf('ggpht.com') > -1) {
+ if (this.backendPreference === 'invidious') { // YT to IV
+ newURL = originalURL.replace(this.re.ytToIv, `${this.currentInvidiousInstance}/ggpht/$1`)
+ }
+ } else {
+ if (this.backendPreference === 'local') { // IV to YT
+ newURL = originalURL.replace(this.re.ivToYt, `${this.ytBaseURL}/$1`)
+ } else { // IV to IV
+ newURL = originalURL.replace(this.re.ivToIv, `${this.currentInvidiousInstance}/$1`)
+ }
+ }
+
+ return newURL.replace(this.re.url, `$1${this.thumbnailSize}$2`)
+ },
+
+ updateThumbnail: function(channel) {
+ this.errorCount += 1
+ if (this.backendPreference === 'local') {
+ // avoid too many concurrent requests
+ setTimeout(() => {
+ ytch.getChannelInfo({ channelId: channel.id }).then(response => {
+ this.updateSubscriptionDetails({
+ channelThumbnailUrl: this.thumbnailURL(response.authorThumbnails[0].url),
+ channelName: channel.name,
+ channelId: channel.id
+ })
+ })
+ }, this.errorCount * 500)
+ } else {
+ setTimeout(() => {
+ this.invidiousGetChannelInfo(channel.id).then(response => {
+ this.updateSubscriptionDetails({
+ channelThumbnailUrl: this.thumbnailURL(response.authorThumbnails[0].url),
+ channelName: channel.name,
+ channelId: channel.id
+ })
+ })
+ }, this.errorCount * 500)
+ }
+ },
+
+ goToChannel: function (id) {
+ this.$router.push({ path: `/channel/${id}` })
+ },
+
+ ...mapActions([
+ 'showToast',
+ 'updateProfile',
+ 'updateSubscriptionDetails',
+ 'invidiousGetChannelInfo'
+ ])
+ }
+})
diff --git a/src/renderer/views/SubscribedChannels/SubscribedChannels.vue b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue
new file mode 100644
index 000000000000..288f9715ad6f
--- /dev/null
+++ b/src/renderer/views/SubscribedChannels/SubscribedChannels.vue
@@ -0,0 +1,70 @@
+
+
+
+ {{ $t('Channels.Title') }}
+
+
+
+ {{ $t('Channels.Empty') }}
+
+
+
+
+ {{ $t('Channels.Count').replace('$', channelList.length) }}
+
+
+
+
+
+
+
+ {{ channel.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/views/Subscriptions/Subscriptions.js b/src/renderer/views/Subscriptions/Subscriptions.js
index 7400ba56aff0..ec64b804fe53 100644
--- a/src/renderer/views/Subscriptions/Subscriptions.js
+++ b/src/renderer/views/Subscriptions/Subscriptions.js
@@ -86,6 +86,10 @@ export default Vue.extend({
activeSubscriptionList: function () {
return this.activeProfile.subscriptions
+ },
+
+ hideLiveStreams: function() {
+ return this.$store.getters.getHideLiveStreams
}
},
watch: {
@@ -178,7 +182,11 @@ export default Vue.extend({
videoList = await Promise.all(videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
}))
-
+ if (this.hideLiveStreams) {
+ videoList = videoList.filter(item => {
+ return (!item.liveNow && !item.isUpcoming)
+ })
+ }
const profileSubscriptions = {
activeProfile: this.activeProfile._id,
videoList: videoList,
diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js
index b490833e69b2..e4f86aa679af 100644
--- a/src/renderer/views/Watch/Watch.js
+++ b/src/renderer/views/Watch/Watch.js
@@ -13,6 +13,7 @@ import WatchVideoComments from '../../components/watch-video-comments/watch-vide
import WatchVideoLiveChat from '../../components/watch-video-live-chat/watch-video-live-chat.vue'
import WatchVideoPlaylist from '../../components/watch-video-playlist/watch-video-playlist.vue'
import WatchVideoRecommendations from '../../components/watch-video-recommendations/watch-video-recommendations.vue'
+import FtAgeRestricted from '../../components/ft-age-restricted/ft-age-restricted.vue'
export default Vue.extend({
name: 'Watch',
@@ -26,7 +27,8 @@ export default Vue.extend({
'watch-video-comments': WatchVideoComments,
'watch-video-live-chat': WatchVideoLiveChat,
'watch-video-playlist': WatchVideoPlaylist,
- 'watch-video-recommendations': WatchVideoRecommendations
+ 'watch-video-recommendations': WatchVideoRecommendations,
+ 'ft-age-restricted': FtAgeRestricted
},
beforeRouteLeave: function (to, from, next) {
this.handleRouteChange(this.videoId)
@@ -42,6 +44,7 @@ export default Vue.extend({
showLegacyPlayer: false,
showYouTubeNoCookieEmbed: false,
hidePlayer: false,
+ isFamilyFriendly: false,
isLive: false,
isLiveContent: false,
isUpcoming: false,
@@ -138,6 +141,15 @@ export default Vue.extend({
hideLiveChat: function () {
return this.$store.getters.getHideLiveChat
},
+ hideComments: function () {
+ return this.$store.getters.getHideComments
+ },
+ hideVideoDescription: function () {
+ return this.$store.getters.getHideVideoDescription
+ },
+ showFamilyFriendlyOnly: function() {
+ return this.$store.getters.getShowFamilyFriendlyOnly
+ },
youtubeNoCookieEmbeddedFrame: function () {
return ``
@@ -304,6 +316,7 @@ export default Vue.extend({
break
}
+ this.isFamilyFriendly = result.videoDetails.isFamilySafe
this.recommendedVideos = result.related_videos.map((video) => {
video.videoId = video.id
video.authorId = video.author.id
@@ -590,6 +603,7 @@ export default Vue.extend({
return format
})
this.isLive = result.liveNow
+ this.isFamilyFriendly = result.isFamilyFriendly
this.captionHybridList = result.captions.map(caption => {
caption.url = this.currentInvidiousInstance + caption.url
caption.type = ''
@@ -1126,7 +1140,7 @@ export default Vue.extend({
interval: Number(interval) // How long one image is used
})
})
- // TODO: MAKE A VARIABLE WHICH CAN CHOOSE BETWEEN STROYBOARD ARRAY ELEMENTS
+ // TODO: MAKE A VARIABLE WHICH CAN CHOOSE BETWEEN STORYBOARD ARRAY ELEMENTS
this.buildVTTFileLocally(storyboardArray[1]).then(async (results) => {
const userData = await this.getUserDataPath()
let fileLocation
diff --git a/src/renderer/views/Watch/Watch.sass b/src/renderer/views/Watch/Watch.sass
index 511b8206ce7e..0382def2c87b 100644
--- a/src/renderer/views/Watch/Watch.sass
+++ b/src/renderer/views/Watch/Watch.sass
@@ -6,6 +6,12 @@
=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
diff --git a/src/renderer/views/Watch/Watch.vue b/src/renderer/views/Watch/Watch.vue
index 628151c4f6b7..241d7dd00625 100644
--- a/src/renderer/views/Watch/Watch.vue
+++ b/src/renderer/views/Watch/Watch.vue
@@ -11,7 +11,10 @@
v-if="isLoading"
:fullscreen="true"
/>
-
-