mirror of https://github.com/FreeTubeApp/FreeTube
Merge branch 'FreeTubeApp:development' into Open-'featured'-links
This commit is contained in:
commit
5ac1eab36c
|
@ -3,7 +3,7 @@ Title
|
||||||
---
|
---
|
||||||
|
|
||||||
**Important note**
|
**Important note**
|
||||||
Please note that only PrestoN is able to merge Pull Requests into master.
|
We may remove your pull request if you do not use this provided PR template correctly.
|
||||||
|
|
||||||
**Pull Request Type**
|
**Pull Request Type**
|
||||||
Please select what type of pull request this is:
|
Please select what type of pull request this is:
|
||||||
|
@ -11,7 +11,7 @@ Please select what type of pull request this is:
|
||||||
- [ ] Feature Implementation
|
- [ ] Feature Implementation
|
||||||
|
|
||||||
**Related issue**
|
**Related issue**
|
||||||
Please link the issue your pull request is referring to.
|
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".
|
||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
Please write a clear and concise description of what the pull request does.
|
Please write a clear and concise description of what the pull request does.
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
minApprovals:
|
||||||
|
COLLABORATOR: 2
|
||||||
|
maxRequestedChanges:
|
||||||
|
COLLABORATOR: 0
|
||||||
|
mergeMethod: squash
|
||||||
|
requiredBaseBranches:
|
||||||
|
- development
|
||||||
|
- test
|
||||||
|
reportStatus: true
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: Auto Merge PR
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, reopened, auto_merge_disabled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Auto Merge PR
|
||||||
|
if: contains(${{ github.event.pull_request.base.ref }}, 'development') || contains(${{ github.event.pull_request.base.ref }}, 'RC')
|
||||||
|
run: |
|
||||||
|
echo ${{ secrets.PUSH_TOKEN }} >> auth.txt
|
||||||
|
gh auth login --with-token < auth.txt
|
||||||
|
rm auth.txt
|
||||||
|
gh pr merge https://github.com/FreeTubeApp/FreeTube/pull/${{ github.event.pull_request.number }} --auto --squash
|
|
@ -1,37 +0,0 @@
|
||||||
name: Auto Merge PR
|
|
||||||
|
|
||||||
# Controls when the workflow will run
|
|
||||||
on:
|
|
||||||
# Triggers the workflow on push or pull request events but only for the development branch
|
|
||||||
push: {} # update PR when base branch is updated
|
|
||||||
status: {} # try to merge when other checks are completed
|
|
||||||
pull_request_review: # try to merge after review
|
|
||||||
types:
|
|
||||||
- submitted
|
|
||||||
- edited
|
|
||||||
- dismissed
|
|
||||||
pull_request: # try to merge if labels have changed (white/black list)
|
|
||||||
types:
|
|
||||||
- labeled
|
|
||||||
- unlabeled
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
jobs:
|
|
||||||
# This workflow contains a single job called "build"
|
|
||||||
build:
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Merge Pal
|
|
||||||
# You may pin to the exact commit or the version.
|
|
||||||
# uses: maxkomarychev/merge-pal-action@7a3bca37e260865d9e9a259212c1d13ef4da7f41
|
|
||||||
uses: maxkomarychev/merge-pal-action@v0.5.1
|
|
||||||
with:
|
|
||||||
# Token to perform api calls
|
|
||||||
token: ${{ secrets.PUSH_TOKEN }}
|
|
||||||
method: squash
|
|
|
@ -5,7 +5,7 @@ name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master, development ]
|
branches: [ master, development, '**-RC' ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -31,10 +31,11 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm ci
|
cache: "yarn"
|
||||||
|
- run: npm run ci
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
- name: Get Version Number
|
- name: Get Version Number
|
||||||
uses: nyaayaya/package-version@v1
|
uses: nyaayaya/package-version@v1
|
||||||
|
@ -47,13 +48,17 @@ jobs:
|
||||||
uses: actions/github-script@v3
|
uses: actions/github-script@v3
|
||||||
env:
|
env:
|
||||||
IS_DEV: ${{ contains(github.ref, 'development') }}
|
IS_DEV: ${{ contains(github.ref, 'development') }}
|
||||||
|
IS_RC: ${{ contains(github.ref, 'RC') }}
|
||||||
VERSION_NUMBER_NIGHTLY: ${{ env.PACKAGE_VERSION }}-nightly-${{ github.run_number }}
|
VERSION_NUMBER_NIGHTLY: ${{ env.PACKAGE_VERSION }}-nightly-${{ github.run_number }}
|
||||||
|
VERSION_NUMBER_RC: ${{ env.PACKAGE_VERSION }}-RC-${{ github.run_number }}
|
||||||
VERSION_NUMBER: ${{ env.PACKAGE_VERSION }}
|
VERSION_NUMBER: ${{ env.PACKAGE_VERSION }}
|
||||||
with:
|
with:
|
||||||
result-encoding: string
|
result-encoding: string
|
||||||
script: |
|
script: |
|
||||||
if (${{ env.IS_DEV }}) {
|
if (${{ env.IS_DEV }}) {
|
||||||
return "${{ env.VERSION_NUMBER_NIGHTLY }}"
|
return "${{ env.VERSION_NUMBER_NIGHTLY }}"
|
||||||
|
} else if (${{ env.IS_RC }}) {
|
||||||
|
return "${{ env.VERSION_NUMBER_RC }}"
|
||||||
} else {
|
} else {
|
||||||
return "${{env.VERSION_NUMBER }}"
|
return "${{env.VERSION_NUMBER }}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
- run: npm ci
|
cache: "yarn"
|
||||||
|
- run: npm run ci
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
|
|
|
@ -32,10 +32,11 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm ci
|
cache: "yarn"
|
||||||
|
- run: npm run ci
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
|
|
||||||
- name: Get Version Number
|
- name: Get Version Number
|
||||||
|
|
|
@ -24,4 +24,4 @@ Please follow these guidelines before sending your pull request and making contr
|
||||||
|
|
||||||
# Setting up Your Environment
|
# Setting up Your Environment
|
||||||
|
|
||||||
Check out the [wiki](https://github.com/FreeTubeApp/FreeTube/wiki/Environment-Setup-and-Packaging) page to learn how to set up your environment and create packages.
|
Check out the [wiki](https://docs.freetubeapp.io/development/getting-started/) page to learn how to set up your environment and get started.
|
||||||
|
|
10
README.md
10
README.md
|
@ -25,6 +25,8 @@ FreeTube uses a built in extractor to grab and serve data / videos. The [Invidi
|
||||||
longer track you using cookies or JavaScript. Your subscriptions and history are stored locally on your computer and never sent out. Using a VPN or Tor is highly recommended
|
longer track you using cookies or JavaScript. Your subscriptions and history are stored locally on your computer and never sent out. Using a VPN or Tor is highly recommended
|
||||||
to hide your IP while using FreeTube.
|
to hide your IP while using FreeTube.
|
||||||
|
|
||||||
|
Go to [FreeTube's Documentation](https://docs.freetubeapp.io/) if you'd like to know more about how to operate FreeTube and its features.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
<img src="https://i.imgur.com/zFgZUUV.png" width=300> <img src="https://i.imgur.com/9evYHgN.png" width=300> <img src="https://i.imgur.com/yT2UzPa.png" width=300> <img src="https://i.imgur.com/47zIEt4.png" width=300> <img src="https://i.imgur.com/hFB2fKC.png" width=300>
|
<img src="https://i.imgur.com/zFgZUUV.png" width=300> <img src="https://i.imgur.com/9evYHgN.png" width=300> <img src="https://i.imgur.com/yT2UzPa.png" width=300> <img src="https://i.imgur.com/47zIEt4.png" width=300> <img src="https://i.imgur.com/hFB2fKC.png" width=300>
|
||||||
|
|
||||||
|
@ -77,18 +79,20 @@ follow the [Contribution
|
||||||
Guidelines](https://github.com/FreeTubeApp/FreeTube/blob/development/CONTRIBUTING.md)
|
Guidelines](https://github.com/FreeTubeApp/FreeTube/blob/development/CONTRIBUTING.md)
|
||||||
before sending your pull request.
|
before sending your pull request.
|
||||||
|
|
||||||
Thank you very much to the [People and Projects](https://github.com/FreeTubeApp/FreeTube/wiki/Credits) that make FreeTube possible!
|
Thank you very much to the [People and Projects](https://docs.freetubeapp.io/credits/) that make FreeTube possible!
|
||||||
|
|
||||||
## Localization
|
## Localization
|
||||||
<a href="https://hosted.weblate.org/engage/free-tube/">
|
<a href="https://hosted.weblate.org/engage/free-tube/">
|
||||||
<img src="https://hosted.weblate.org/widgets/free-tube/-/translations/287x66-grey.png" alt="Translation status" />
|
<img src="https://hosted.weblate.org/widgets/free-tube/-/translations/287x66-grey.png" alt="Translation status" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
We are actively looking for translations! We use Weblate to make it easy for translators to get involved. Click on the badge above to learn how to get involved.
|
We are actively looking for translations! We use [Weblate](https://hosted.weblate.org/engage/free-tube/) to make it easy for translators to get involved. Click on the badge above to learn how to get involved.
|
||||||
|
|
||||||
|
For the Linux Flatpak, the desktop entry comment string can be translated at our [Flatpak repository](https://github.com/flathub/io.freetubeapp.FreeTube/blob/master/io.freetubeapp.FreeTube.desktop).
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
If you ever have any questions, feel free to make an issue here on GitHub. Alternatively, you can email me at FreeTubeApp@protonmail.com or you can join our [Matrix Community](https://matrix.to/#/+freetube:matrix.org). Don't forget to check out the [rules](https://github.com/FreeTubeApp/FreeTube/wiki/Matrix-Server-Info-&-Rules) before joining.
|
If you ever have any questions, feel free to make an issue here on GitHub. Alternatively, you can email me at FreeTubeApp@protonmail.com or you can join our [Matrix Community](https://matrix.to/#/+freetube:matrix.org). Don't forget to check out the [rules](https://docs.freetubeapp.io/community/matrix/) before joining.
|
||||||
|
|
||||||
You can also stay up to date by reading the [FreeTube Blog](https://write.as/freetube/). [View the welcome blog](https://write.as/freetube/welcome-to-freetube-blogs).
|
You can also stay up to date by reading the [FreeTube Blog](https://write.as/freetube/). [View the welcome blog](https://write.as/freetube/welcome-to-freetube-blogs).
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
|
@ -116,7 +116,12 @@ function startRenderer(callback) {
|
||||||
})
|
})
|
||||||
|
|
||||||
const server = new WebpackDevServer(compiler, {
|
const server = new WebpackDevServer(compiler, {
|
||||||
static: path.join(process.cwd(), 'static'),
|
static: {
|
||||||
|
directory: path.join(process.cwd(), 'static'),
|
||||||
|
watch: {
|
||||||
|
ignored: /(dashFiles|storyboards)\/*/
|
||||||
|
}
|
||||||
|
},
|
||||||
port
|
port
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,6 @@ const config = {
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
// any dev only config
|
// any dev only config
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
|
__static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -147,7 +147,6 @@ const config = {
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
// any dev only config
|
// any dev only config
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
|
__static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -62,7 +62,6 @@ const config = {
|
||||||
*/
|
*/
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
// any dev only config
|
// any dev only config
|
||||||
config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
|
||||||
} else {
|
} else {
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
|
@ -2,7 +2,7 @@
|
||||||
"name": "freetube",
|
"name": "freetube",
|
||||||
"productName": "FreeTube",
|
"productName": "FreeTube",
|
||||||
"description": "A private YouTube client",
|
"description": "A private YouTube client",
|
||||||
"version": "0.13.2",
|
"version": "0.15.0",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -46,14 +46,16 @@
|
||||||
"rebuild:node": "npm rebuild",
|
"rebuild:node": "npm rebuild",
|
||||||
"release": "run-s test build",
|
"release": "run-s test build",
|
||||||
"test": "run-s rebuild:node pack:workers jest",
|
"test": "run-s rebuild:node pack:workers jest",
|
||||||
"test:watch": "run-s rebuild:node pack:workers jest:watch"
|
"test:watch": "run-s rebuild:node pack:workers jest:watch",
|
||||||
|
|
||||||
|
"ci": "yarn install --frozen-lockfile"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "^2.0.2",
|
"@fortawesome/vue-fontawesome": "^2.0.2",
|
||||||
"@freetube/youtube-chat": "^1.1.1",
|
"@freetube/youtube-chat": "^1.1.2",
|
||||||
"@freetube/yt-comment-scraper": "^6.0.0",
|
"@freetube/yt-comment-scraper": "^6.0.0",
|
||||||
"@silvermine/videojs-quality-selector": "^1.2.5",
|
"@silvermine/videojs-quality-selector": "^1.2.5",
|
||||||
"autolinker": "^3.14.3",
|
"autolinker": "^3.14.3",
|
||||||
|
@ -67,9 +69,9 @@
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.uniqwith": "^4.5.0",
|
"lodash.uniqwith": "^4.5.0",
|
||||||
"marked": "^2.1.3",
|
"marked": "^3.0.2",
|
||||||
"material-design-icons": "^3.0.1",
|
"material-design-icons": "^3.0.1",
|
||||||
"nedb-promises": "^5.0.0",
|
"nedb-promises": "^5.0.1",
|
||||||
"node-forge": "^0.10.0",
|
"node-forge": "^0.10.0",
|
||||||
"opml-to-json": "^1.0.1",
|
"opml-to-json": "^1.0.1",
|
||||||
"rss-parser": "^3.12.0",
|
"rss-parser": "^3.12.0",
|
||||||
|
@ -91,58 +93,58 @@
|
||||||
"yt-channel-info": "^2.2.0",
|
"yt-channel-info": "^2.2.0",
|
||||||
"yt-dash-manifest-generator": "1.1.0",
|
"yt-dash-manifest-generator": "1.1.0",
|
||||||
"yt-trending-scraper": "^2.0.1",
|
"yt-trending-scraper": "^2.0.1",
|
||||||
"ytdl-core": "^4.9.0",
|
"ytdl-core": "^4.9.1",
|
||||||
"ytpl": "^2.2.1",
|
"ytpl": "^2.2.3",
|
||||||
"ytsr": "^3.5.0"
|
"ytsr": "^3.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.8",
|
"@babel/core": "^7.15.0",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
|
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
|
||||||
"@babel/preset-env": "^7.14.9",
|
"@babel/preset-env": "^7.15.0",
|
||||||
"@babel/preset-typescript": "^7.14.5",
|
"@babel/preset-typescript": "^7.15.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
||||||
"@typescript-eslint/parser": "^4.29.0",
|
"@typescript-eslint/parser": "^4.30.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"copy-webpack-plugin": "^9.0.1",
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
"css-loader": "5.2.6",
|
"css-loader": "5.2.6",
|
||||||
"electron": "^13.1.7",
|
"electron": "^13.5.1",
|
||||||
"electron-builder": "^22.11.7",
|
"electron-builder": "^22.11.7",
|
||||||
"electron-builder-squirrel-windows": "^22.11.11",
|
"electron-builder-squirrel-windows": "^22.13.1",
|
||||||
"electron-debug": "^3.2.0",
|
"electron-debug": "^3.2.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-config-standard": "^16.0.3",
|
"eslint-config-standard": "^16.0.3",
|
||||||
"eslint-plugin-import": "^2.23.4",
|
"eslint-plugin-import": "^2.24.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-promise": "^5.1.0",
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
"eslint-plugin-standard": "^5.0.0",
|
"eslint-plugin-standard": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^7.15.1",
|
"eslint-plugin-vue": "^7.17.0",
|
||||||
"fast-glob": "^3.2.7",
|
"fast-glob": "^3.2.7",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-webpack-plugin": "^5.3.2",
|
"html-webpack-plugin": "^5.3.2",
|
||||||
"jest": "^27.0.6",
|
"jest": "^27.1.0",
|
||||||
"mini-css-extract-plugin": "^2.1.0",
|
"mini-css-extract-plugin": "^2.2.2",
|
||||||
"node-abi": "^2.30.0",
|
"node-abi": "^2.30.1",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.37.2",
|
"sass": "^1.38.2",
|
||||||
"sass-loader": "^12.1.0",
|
"sass-loader": "^12.1.0",
|
||||||
"style-loader": "^3.2.1",
|
"style-loader": "^3.2.1",
|
||||||
"tree-kill": "1.2.2",
|
"tree-kill": "1.2.2",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.4.2",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"vue-devtools": "^5.1.4",
|
"vue-devtools": "^5.1.4",
|
||||||
"vue-eslint-parser": "^7.10.0",
|
"vue-eslint-parser": "^7.10.0",
|
||||||
"vue-loader": "^15.9.8",
|
"vue-loader": "^15.9.8",
|
||||||
"vue-style-loader": "^4.1.3",
|
"vue-style-loader": "^4.1.3",
|
||||||
"vue-template-compiler": "^2.6.14",
|
"vue-template-compiler": "^2.6.14",
|
||||||
"webpack": "^5.48.0",
|
"webpack": "^5.51.1",
|
||||||
"webpack-cli": "^4.7.2",
|
"webpack-cli": "^4.8.0",
|
||||||
"webpack-dev-server": "^4.0.0-beta.3"
|
"webpack-dev-server": "^4.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="light mainRed secBlue">
|
<body class="dark mainRed secBlue">
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- Set `__static` path to static files in production -->
|
<!-- Set `__static` path to static files in production -->
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -181,7 +181,7 @@ function runApp() {
|
||||||
* Initial window options
|
* Initial window options
|
||||||
*/
|
*/
|
||||||
const newWindow = new BrowserWindow({
|
const newWindow = new BrowserWindow({
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#212121',
|
||||||
icon: isDev
|
icon: isDev
|
||||||
? path.join(__dirname, '../../_icons/iconColor.png')
|
? path.join(__dirname, '../../_icons/iconColor.png')
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
|
|
|
@ -32,6 +32,12 @@ body {
|
||||||
margin-bottom: -75px;
|
margin-bottom: -75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#changeLogText {
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 40vh;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
|
||||||
.fade-enter-active, .fade-leave-active {
|
.fade-enter-active, .fade-leave-active {
|
||||||
transition: opacity .15s;
|
transition: opacity .15s;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,14 @@ export default Vue.extend({
|
||||||
blogBannerMessage: '',
|
blogBannerMessage: '',
|
||||||
latestBlogUrl: '',
|
latestBlogUrl: '',
|
||||||
updateChangelog: '',
|
updateChangelog: '',
|
||||||
changeLogTitle: ''
|
changeLogTitle: '',
|
||||||
|
|
||||||
|
lastExternalLinkToBeOpened: '',
|
||||||
|
showExternalLinkOpeningPrompt: false,
|
||||||
|
externalLinkOpeningPromptValues: [
|
||||||
|
'yes',
|
||||||
|
'no'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -96,12 +103,29 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
defaultInvidiousInstance: function () {
|
defaultInvidiousInstance: function () {
|
||||||
return this.$store.getters.getDefaultInvidiousInstance
|
return this.$store.getters.getDefaultInvidiousInstance
|
||||||
|
},
|
||||||
|
|
||||||
|
externalLinkOpeningPromptNames: function () {
|
||||||
|
return [
|
||||||
|
this.$t('Yes'),
|
||||||
|
this.$t('No')
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
externalLinkHandling: function () {
|
||||||
|
return this.$store.getters.getExternalLinkHandling
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
windowTitle: 'setWindowTitle'
|
windowTitle: 'setWindowTitle',
|
||||||
|
$route () {
|
||||||
|
// react to route changes...
|
||||||
|
// Hide top nav filter panel on page change
|
||||||
|
this.$refs.topNav.hideFilters()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
this.checkThemeSettings()
|
||||||
this.setWindowTitle()
|
this.setWindowTitle()
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -114,7 +138,6 @@ export default Vue.extend({
|
||||||
this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => {
|
this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => {
|
||||||
this.grabHistory()
|
this.grabHistory()
|
||||||
this.grabAllPlaylists()
|
this.grabAllPlaylists()
|
||||||
this.checkThemeSettings()
|
|
||||||
|
|
||||||
if (this.usingElectron) {
|
if (this.usingElectron) {
|
||||||
console.log('User is using Electron')
|
console.log('User is using Electron')
|
||||||
|
@ -133,6 +156,10 @@ export default Vue.extend({
|
||||||
this.checkForNewBlogPosts()
|
this.checkForNewBlogPosts()
|
||||||
}, 500)
|
}, 500)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.$router.afterEach((to, from) => {
|
||||||
|
this.$refs.topNav.navigateHistory()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -142,7 +169,7 @@ export default Vue.extend({
|
||||||
let secColor = localStorage.getItem('secColor')
|
let secColor = localStorage.getItem('secColor')
|
||||||
|
|
||||||
if (baseTheme === null) {
|
if (baseTheme === null) {
|
||||||
baseTheme = 'light'
|
baseTheme = 'dark'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainColor === null) {
|
if (mainColor === null) {
|
||||||
|
@ -193,7 +220,7 @@ export default Vue.extend({
|
||||||
this.showUpdatesBanner = true
|
this.showUpdatesBanner = true
|
||||||
} else if (parseInt(appVersion[1]) < parseInt(latestVersion[1])) {
|
} else if (parseInt(appVersion[1]) < parseInt(latestVersion[1])) {
|
||||||
this.showUpdatesBanner = true
|
this.showUpdatesBanner = true
|
||||||
} else if (parseInt(appVersion[2]) < parseInt(latestVersion[2])) {
|
} else if (parseInt(appVersion[2]) < parseInt(latestVersion[2]) && parseInt(appVersion[1]) <= parseInt(latestVersion[1])) {
|
||||||
this.showUpdatesBanner = true
|
this.showUpdatesBanner = true
|
||||||
}
|
}
|
||||||
}).fail((xhr, textStatus, error) => {
|
}).fail((xhr, textStatus, error) => {
|
||||||
|
@ -273,10 +300,10 @@ export default Vue.extend({
|
||||||
if (event.altKey) {
|
if (event.altKey) {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case 'ArrowRight':
|
case 'ArrowRight':
|
||||||
window.history.forward()
|
this.$refs.topNav.historyForward()
|
||||||
break
|
break
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
window.history.back()
|
this.$refs.topNav.historyBack()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,8 +327,18 @@ export default Vue.extend({
|
||||||
|
|
||||||
if (isYoutubeLink) {
|
if (isYoutubeLink) {
|
||||||
this.handleYoutubeLink(el.href)
|
this.handleYoutubeLink(el.href)
|
||||||
|
} else if (this.externalLinkHandling === 'doNothing') {
|
||||||
|
// Let user know opening external link is disabled via setting
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('External link opening has been disabled in the general settings')
|
||||||
|
})
|
||||||
|
} else if (this.externalLinkHandling === 'openLinkAfterPrompt') {
|
||||||
|
// Storing the URL is necessary as
|
||||||
|
// there is no other way to pass the URL to click callback
|
||||||
|
this.lastExternalLinkToBeOpened = el.href
|
||||||
|
this.showExternalLinkOpeningPrompt = true
|
||||||
} else {
|
} else {
|
||||||
// Open links externally by default
|
// Open links externally
|
||||||
this.openExternalLink(el.href)
|
this.openExternalLink(el.href)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -399,6 +436,18 @@ export default Vue.extend({
|
||||||
ipcRenderer.send('appReady')
|
ipcRenderer.send('appReady')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleExternalLinkOpeningPromptAnswer: function (option) {
|
||||||
|
this.showExternalLinkOpeningPrompt = false
|
||||||
|
|
||||||
|
if (option === 'yes' && this.lastExternalLinkToBeOpened.length > 0) {
|
||||||
|
// Maybe user should be notified
|
||||||
|
// if `lastExternalLinkToBeOpened` is empty
|
||||||
|
|
||||||
|
// Open links externally
|
||||||
|
this.openExternalLink(this.lastExternalLinkToBeOpened)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
...mapMutations([
|
...mapMutations([
|
||||||
'setInvidiousInstancesList'
|
'setInvidiousInstancesList'
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -46,7 +46,10 @@
|
||||||
<h2>
|
<h2>
|
||||||
{{ changeLogTitle }}
|
{{ changeLogTitle }}
|
||||||
</h2>
|
</h2>
|
||||||
<span v-html="updateChangelog" />
|
<span
|
||||||
|
id="changeLogText"
|
||||||
|
v-html="updateChangelog"
|
||||||
|
/>
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<ft-button
|
<ft-button
|
||||||
:label="$t('Download From Site')"
|
:label="$t('Download From Site')"
|
||||||
|
@ -54,6 +57,14 @@
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-prompt>
|
</ft-prompt>
|
||||||
|
<ft-prompt
|
||||||
|
v-if="showExternalLinkOpeningPrompt"
|
||||||
|
:label="$t('Are you sure you want to open this link?')"
|
||||||
|
:extra-labels="[lastExternalLinkToBeOpened]"
|
||||||
|
:option-names="externalLinkOpeningPromptNames"
|
||||||
|
:option-values="externalLinkOpeningPromptValues"
|
||||||
|
@click="handleExternalLinkOpeningPromptAnswer"
|
||||||
|
/>
|
||||||
<ft-toast />
|
<ft-toast />
|
||||||
<ft-progress-bar
|
<ft-progress-bar
|
||||||
v-if="showProgressBar"
|
v-if="showProgressBar"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" fill="white" width="20px" height="19px" data-prefix="fas" data-icon="desktop" class="svg-inline--fa fa-desktop fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M528 0H48C21.5 0 0 21.5 0 48v320c0 26.5 21.5 48 48 48h192l-16 48h-72c-13.3 0-24 10.7-24 24s10.7 24 24 24h272c13.3 0 24-10.7 24-24s-10.7-24-24-24h-72l-16-48h192c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zm-16 352H64V64h448v288z"></path></svg>
|
After Width: | Height: | Size: 483 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" fill="white" width="20px" height="19px" data-icon="tv" class="svg-inline--fa fa-tv fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M592 0H48A48 48 0 0 0 0 48v320a48 48 0 0 0 48 48h240v32H112a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16H352v-32h240a48 48 0 0 0 48-48V48a48 48 0 0 0-48-48zm-16 352H64V64h512z"></path></svg>
|
After Width: | Height: | Size: 458 B |
|
@ -27,6 +27,7 @@ export default Vue.extend({
|
||||||
showExportSubscriptionsPrompt: false,
|
showExportSubscriptionsPrompt: false,
|
||||||
subscriptionsPromptValues: [
|
subscriptionsPromptValues: [
|
||||||
'freetube',
|
'freetube',
|
||||||
|
'youtubenew',
|
||||||
'youtube',
|
'youtube',
|
||||||
'youtubeold',
|
'youtubeold',
|
||||||
'newpipe'
|
'newpipe'
|
||||||
|
@ -58,6 +59,7 @@ export default Vue.extend({
|
||||||
const importNewPipe = this.$t('Settings.Data Settings.Import NewPipe')
|
const importNewPipe = this.$t('Settings.Data Settings.Import NewPipe')
|
||||||
return [
|
return [
|
||||||
`${importFreeTube} (.db)`,
|
`${importFreeTube} (.db)`,
|
||||||
|
`${importYouTube} (.csv)`,
|
||||||
`${importYouTube} (.json)`,
|
`${importYouTube} (.json)`,
|
||||||
`${importYouTube} (.opml)`,
|
`${importYouTube} (.opml)`,
|
||||||
`${importNewPipe} (.json)`
|
`${importNewPipe} (.json)`
|
||||||
|
@ -69,6 +71,7 @@ export default Vue.extend({
|
||||||
const exportNewPipe = this.$t('Settings.Data Settings.Export NewPipe')
|
const exportNewPipe = this.$t('Settings.Data Settings.Export NewPipe')
|
||||||
return [
|
return [
|
||||||
`${exportFreeTube} (.db)`,
|
`${exportFreeTube} (.db)`,
|
||||||
|
`${exportYouTube} (.csv)`,
|
||||||
`${exportYouTube} (.json)`,
|
`${exportYouTube} (.json)`,
|
||||||
`${exportYouTube} (.opml)`,
|
`${exportYouTube} (.opml)`,
|
||||||
`${exportNewPipe} (.json)`
|
`${exportNewPipe} (.json)`
|
||||||
|
@ -93,6 +96,9 @@ export default Vue.extend({
|
||||||
case 'freetube':
|
case 'freetube':
|
||||||
this.importFreeTubeSubscriptions()
|
this.importFreeTubeSubscriptions()
|
||||||
break
|
break
|
||||||
|
case 'youtubenew':
|
||||||
|
this.importCsvYouTubeSubscriptions()
|
||||||
|
break
|
||||||
case 'youtube':
|
case 'youtube':
|
||||||
this.importYouTubeSubscriptions()
|
this.importYouTubeSubscriptions()
|
||||||
break
|
break
|
||||||
|
@ -228,6 +234,75 @@ export default Vue.extend({
|
||||||
this.handleFreetubeImportFile(filePath)
|
this.handleFreetubeImportFile(filePath)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleYoutubeCsvImportFile: function(filePath) { // first row = header, last row = empty
|
||||||
|
fs.readFile(filePath, async (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
||||||
|
this.showToast({
|
||||||
|
message: `${message}: ${err}`
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const textDecode = new TextDecoder('utf-8').decode(data)
|
||||||
|
console.log(textDecode)
|
||||||
|
const youtubeSubscriptions = textDecode.split('\n')
|
||||||
|
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
|
||||||
|
const subscriptions = []
|
||||||
|
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('Settings.Data Settings.This might take a while, please wait')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.updateShowProgressBar(true)
|
||||||
|
this.setProgressBarPercentage(0)
|
||||||
|
let count = 0
|
||||||
|
for (let i = 1; i < (youtubeSubscriptions.length - 1); i++) {
|
||||||
|
const channelId = youtubeSubscriptions[i].split(',')[0]
|
||||||
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
||||||
|
return sub.id === channelId
|
||||||
|
})
|
||||||
|
if (subExists === -1) {
|
||||||
|
let channelInfo
|
||||||
|
if (this.backendPreference === 'invidious') { // only needed for thumbnail
|
||||||
|
channelInfo = await this.getChannelInfoInvidious(channelId)
|
||||||
|
} else {
|
||||||
|
channelInfo = await this.getChannelInfoLocal(channelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof channelInfo.author !== 'undefined') {
|
||||||
|
const subscription = {
|
||||||
|
id: channelId,
|
||||||
|
name: channelInfo.author,
|
||||||
|
thumbnail: channelInfo.authorThumbnails[1].url
|
||||||
|
}
|
||||||
|
subscriptions.push(subscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
|
||||||
|
const progressPercentage = (count / (youtubeSubscriptions.length - 1)) * 100
|
||||||
|
this.setProgressBarPercentage(progressPercentage)
|
||||||
|
if (count + 1 === (youtubeSubscriptions.length - 1)) {
|
||||||
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
|
||||||
|
this.updateProfile(primaryProfile)
|
||||||
|
|
||||||
|
if (subscriptions.length < count + 2) {
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateShowProgressBar(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
handleYoutubeImportFile: function (filePath) {
|
handleYoutubeImportFile: function (filePath) {
|
||||||
fs.readFile(filePath, async (err, data) => {
|
fs.readFile(filePath, async (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -310,6 +385,25 @@ export default Vue.extend({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
importCsvYouTubeSubscriptions: async function () {
|
||||||
|
const options = {
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'Database File',
|
||||||
|
extensions: ['csv']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const response = await this.showOpenDialog(options)
|
||||||
|
if (response.canceled || response.filePaths.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = response.filePaths[0]
|
||||||
|
this.handleYoutubeCsvImportFile(filePath)
|
||||||
|
},
|
||||||
|
|
||||||
importYouTubeSubscriptions: async function () {
|
importYouTubeSubscriptions: async function () {
|
||||||
const options = {
|
const options = {
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
|
@ -387,6 +481,10 @@ export default Vue.extend({
|
||||||
|
|
||||||
feedData.forEach(async (channel, index) => {
|
feedData.forEach(async (channel, index) => {
|
||||||
const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '')
|
const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '')
|
||||||
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
||||||
|
return sub.id === channelId
|
||||||
|
})
|
||||||
|
if (subExists === -1) {
|
||||||
let channelInfo
|
let channelInfo
|
||||||
if (this.backendPreference === 'invidious') {
|
if (this.backendPreference === 'invidious') {
|
||||||
channelInfo = await this.getChannelInfoInvidious(channelId)
|
channelInfo = await this.getChannelInfoInvidious(channelId)
|
||||||
|
@ -400,12 +498,6 @@ export default Vue.extend({
|
||||||
name: channelInfo.author,
|
name: channelInfo.author,
|
||||||
thumbnail: channelInfo.authorThumbnails[1].url
|
thumbnail: channelInfo.authorThumbnails[1].url
|
||||||
}
|
}
|
||||||
|
|
||||||
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
|
||||||
return sub.id === subscription.id || sub.name === subscription.name
|
|
||||||
})
|
|
||||||
|
|
||||||
if (subExists === -1) {
|
|
||||||
subscriptions.push(subscription)
|
subscriptions.push(subscription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -498,6 +590,11 @@ export default Vue.extend({
|
||||||
|
|
||||||
newPipeSubscriptions.forEach(async (channel, index) => {
|
newPipeSubscriptions.forEach(async (channel, index) => {
|
||||||
const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '')
|
const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '')
|
||||||
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
||||||
|
return sub.id === channelId
|
||||||
|
})
|
||||||
|
|
||||||
|
if (subExists === -1) {
|
||||||
let channelInfo
|
let channelInfo
|
||||||
if (this.backendPreference === 'invidious') {
|
if (this.backendPreference === 'invidious') {
|
||||||
channelInfo = await this.getChannelInfoInvidious(channelId)
|
channelInfo = await this.getChannelInfoInvidious(channelId)
|
||||||
|
@ -511,12 +608,6 @@ export default Vue.extend({
|
||||||
name: channelInfo.author,
|
name: channelInfo.author,
|
||||||
thumbnail: channelInfo.authorThumbnails[1].url
|
thumbnail: channelInfo.authorThumbnails[1].url
|
||||||
}
|
}
|
||||||
|
|
||||||
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
|
||||||
return sub.id === subscription.id || sub.name === subscription.name
|
|
||||||
})
|
|
||||||
|
|
||||||
if (subExists === -1) {
|
|
||||||
subscriptions.push(subscription)
|
subscriptions.push(subscription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -557,6 +648,9 @@ export default Vue.extend({
|
||||||
case 'freetube':
|
case 'freetube':
|
||||||
this.exportFreeTubeSubscriptions()
|
this.exportFreeTubeSubscriptions()
|
||||||
break
|
break
|
||||||
|
case 'youtubenew':
|
||||||
|
this.exportCsvYouTubeSubscriptions()
|
||||||
|
break
|
||||||
case 'youtube':
|
case 'youtube':
|
||||||
this.exportYouTubeSubscriptions()
|
this.exportYouTubeSubscriptions()
|
||||||
break
|
break
|
||||||
|
@ -573,21 +667,8 @@ export default Vue.extend({
|
||||||
await this.compactProfiles()
|
await this.compactProfiles()
|
||||||
const userData = await this.getUserDataPath()
|
const userData = await this.getUserDataPath()
|
||||||
const subscriptionsDb = `${userData}/profiles.db`
|
const subscriptionsDb = `${userData}/profiles.db`
|
||||||
const date = new Date()
|
const date = new Date().toISOString().split('T')[0]
|
||||||
let dateMonth = date.getMonth() + 1
|
const exportFileName = 'freetube-subscriptions-' + date + '.db'
|
||||||
|
|
||||||
if (dateMonth < 10) {
|
|
||||||
dateMonth = '0' + dateMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateDay = date.getDate()
|
|
||||||
|
|
||||||
if (dateDay < 10) {
|
|
||||||
dateDay = '0' + dateDay
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateYear = date.getFullYear()
|
|
||||||
const exportFileName = 'freetube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.db'
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
defaultPath: exportFileName,
|
defaultPath: exportFileName,
|
||||||
|
@ -633,21 +714,8 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
exportYouTubeSubscriptions: async function () {
|
exportYouTubeSubscriptions: async function () {
|
||||||
const date = new Date()
|
const date = new Date().toISOString().split('T')[0]
|
||||||
let dateMonth = date.getMonth() + 1
|
const exportFileName = 'youtube-subscriptions-' + date + '.json'
|
||||||
|
|
||||||
if (dateMonth < 10) {
|
|
||||||
dateMonth = '0' + dateMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateDay = date.getDate()
|
|
||||||
|
|
||||||
if (dateDay < 10) {
|
|
||||||
dateDay = '0' + dateDay
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateYear = date.getFullYear()
|
|
||||||
const exportFileName = 'youtube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.json'
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
defaultPath: exportFileName,
|
defaultPath: exportFileName,
|
||||||
|
@ -719,21 +787,8 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
exportOpmlYouTubeSubscriptions: async function () {
|
exportOpmlYouTubeSubscriptions: async function () {
|
||||||
const date = new Date()
|
const date = new Date().toISOString().split('T')[0]
|
||||||
let dateMonth = date.getMonth() + 1
|
const exportFileName = 'youtube-subscriptions-' + date + '.opml'
|
||||||
|
|
||||||
if (dateMonth < 10) {
|
|
||||||
dateMonth = '0' + dateMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateDay = date.getDate()
|
|
||||||
|
|
||||||
if (dateDay < 10) {
|
|
||||||
dateDay = '0' + dateDay
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateYear = date.getFullYear()
|
|
||||||
const exportFileName = 'youtube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.opml'
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
defaultPath: exportFileName,
|
defaultPath: exportFileName,
|
||||||
|
@ -783,22 +838,50 @@ export default Vue.extend({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exportCsvYouTubeSubscriptions: async function () {
|
||||||
|
const date = new Date().toISOString().split('T')[0]
|
||||||
|
const exportFileName = 'youtube-subscriptions-' + date + '.csv'
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
defaultPath: exportFileName,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: 'Database File',
|
||||||
|
extensions: ['csv']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
let exportText = 'Channel ID,Channel URL,Channel title\n'
|
||||||
|
this.profileList[0].subscriptions.forEach((channel) => {
|
||||||
|
const channelUrl = `https://www.youtube.com/channel/${channel.id}`
|
||||||
|
exportText += `${channel.id},${channelUrl},${channel.name}\n`
|
||||||
|
})
|
||||||
|
exportText += '\n'
|
||||||
|
const response = await this.showSaveDialog(options)
|
||||||
|
if (response.canceled || response.filePath === '') {
|
||||||
|
// User canceled the save dialog
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = response.filePath
|
||||||
|
fs.writeFile(filePath, exportText, (writeErr) => {
|
||||||
|
if (writeErr) {
|
||||||
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
||||||
|
this.showToast({
|
||||||
|
message: `${message}: ${writeErr}`
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
exportNewPipeSubscriptions: async function () {
|
exportNewPipeSubscriptions: async function () {
|
||||||
const date = new Date()
|
const date = new Date().toISOString().split('T')[0]
|
||||||
let dateMonth = date.getMonth() + 1
|
const exportFileName = 'newpipe-subscriptions-' + date + '.json'
|
||||||
|
|
||||||
if (dateMonth < 10) {
|
|
||||||
dateMonth = '0' + dateMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateDay = date.getDate()
|
|
||||||
|
|
||||||
if (dateDay < 10) {
|
|
||||||
dateDay = '0' + dateDay
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateYear = date.getFullYear()
|
|
||||||
const exportFileName = 'newpipe-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.json'
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
defaultPath: exportFileName,
|
defaultPath: exportFileName,
|
||||||
|
@ -945,21 +1028,8 @@ export default Vue.extend({
|
||||||
await this.compactHistory()
|
await this.compactHistory()
|
||||||
const userData = await this.getUserDataPath()
|
const userData = await this.getUserDataPath()
|
||||||
const historyDb = `${userData}/history.db`
|
const historyDb = `${userData}/history.db`
|
||||||
const date = new Date()
|
const date = new Date().toISOString().split('T')[0]
|
||||||
let dateMonth = date.getMonth() + 1
|
const exportFileName = 'freetube-history-' + date + '.db'
|
||||||
|
|
||||||
if (dateMonth < 10) {
|
|
||||||
dateMonth = '0' + dateMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateDay = date.getDate()
|
|
||||||
|
|
||||||
if (dateDay < 10) {
|
|
||||||
dateDay = '0' + dateDay
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateYear = date.getFullYear()
|
|
||||||
const exportFileName = 'freetube-history-' + dateYear + '-' + dateMonth + '-' + dateDay + '.db'
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
defaultPath: exportFileName,
|
defaultPath: exportFileName,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
|
||||||
<h3>
|
<h3>
|
||||||
{{ $t("Settings.Data Settings.Data Settings") }}
|
{{ $t("Settings.Data Settings.Data Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<ft-button
|
<ft-button
|
||||||
:label="$t('Settings.Data Settings.Import Subscriptions')"
|
:label="$t('Settings.Data Settings.Import Subscriptions')"
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<a
|
<a
|
||||||
class="center"
|
class="center"
|
||||||
href="https://github.com/FreeTubeApp/FreeTube/wiki/Importing-Your-YouTube-Subscriptions"
|
href="https://docs.freetubeapp.io/usage/importing-subscriptions/"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{{ $t("Settings.Data Settings.How do I import my subscriptions?") }}
|
{{ $t("Settings.Data Settings.How do I import my subscriptions?") }}
|
||||||
|
@ -57,7 +58,7 @@
|
||||||
:option-values="subscriptionsPromptValues"
|
:option-values="subscriptionsPromptValues"
|
||||||
@click="exportSubscriptions"
|
@click="exportSubscriptions"
|
||||||
/>
|
/>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./data-settings.js" />
|
<script src="./data-settings.js" />
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.Distraction Free Settings.Distraction Free Settings") }}
|
{{ $t("Settings.Distraction Free Settings.Distraction Free Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<div class="switchColumnGrid">
|
<div class="switchColumnGrid">
|
||||||
<div class="switchColumn">
|
<div class="switchColumn">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
|
@ -90,7 +89,7 @@
|
||||||
label="Manage My Distractions"
|
label="Manage My Distractions"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./distraction-settings.js" />
|
<script src="./distraction-settings.js" />
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.External Player Settings.External Player Settings") }}
|
{{ $t("Settings.External Player Settings.External Player Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<div class="switchColumnGrid">
|
<div class="switchColumnGrid">
|
||||||
<div class="switchColumn">
|
<div class="switchColumn">
|
||||||
<ft-select
|
<ft-select
|
||||||
|
@ -34,7 +33,7 @@
|
||||||
>
|
>
|
||||||
<ft-input
|
<ft-input
|
||||||
:placeholder="$t('Settings.External Player Settings.Custom External Player Executable')"
|
:placeholder="$t('Settings.External Player Settings.Custom External Player Executable')"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
:value="externalPlayerExecutable"
|
:value="externalPlayerExecutable"
|
||||||
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Executable')"
|
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Executable')"
|
||||||
|
@ -42,14 +41,14 @@
|
||||||
/>
|
/>
|
||||||
<ft-input
|
<ft-input
|
||||||
:placeholder="$t('Settings.External Player Settings.Custom External Player Arguments')"
|
:placeholder="$t('Settings.External Player Settings.Custom External Player Arguments')"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
:value="externalPlayerCustomArgs"
|
:value="externalPlayerCustomArgs"
|
||||||
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Arguments')"
|
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Arguments')"
|
||||||
@input="updateExternalPlayerCustomArgs"
|
@input="updateExternalPlayerCustomArgs"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./external-player-settings.js" />
|
<script src="./external-player-settings.js" />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.ft-auto-grid
|
.ft-auto-grid
|
||||||
&.grid
|
&.grid
|
||||||
display: grid
|
display: grid
|
||||||
grid-template-columns: repeat(auto-fill, 262px)
|
grid-template-columns: repeat(auto-fill, minmax(262px, 1fr) )
|
||||||
justify-content: space-evenly
|
justify-content: space-evenly
|
||||||
grid-gap: 8px
|
grid-gap: 8px
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
showDropdown: false,
|
dropdownShown: false,
|
||||||
id: ''
|
id: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -60,25 +60,46 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleDropdown: function () {
|
toggleDropdown: function () {
|
||||||
$(`#${this.id}`)[0].style.display = 'inline'
|
const dropdownBox = $(`#${this.id}`)
|
||||||
$(`#${this.id}`).focus()
|
|
||||||
|
|
||||||
$(`#${this.id}`).focusout(() => {
|
if (this.dropdownShown) {
|
||||||
const shareLinks = $(`#${this.id}`).find('.shareLinks')
|
dropdownBox.get(0).style.display = 'none'
|
||||||
|
this.dropdownShown = false
|
||||||
|
} else {
|
||||||
|
dropdownBox.get(0).style.display = 'inline'
|
||||||
|
dropdownBox.get(0).focus()
|
||||||
|
this.dropdownShown = true
|
||||||
|
|
||||||
|
dropdownBox.focusout(() => {
|
||||||
|
const shareLinks = dropdownBox.find('.shareLinks')
|
||||||
|
|
||||||
if (shareLinks.length > 0) {
|
if (shareLinks.length > 0) {
|
||||||
if (!shareLinks[0].parentNode.matches(':hover')) {
|
if (!shareLinks[0].parentNode.matches(':hover')) {
|
||||||
$(`#${this.id}`)[0].style.display = 'none'
|
dropdownBox.get(0).style.display = 'none'
|
||||||
|
// When pressing the profile button
|
||||||
|
// It will make the menu reappear if we set `dropdownShown` immediately
|
||||||
|
setTimeout(() => {
|
||||||
|
this.dropdownShown = false
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$(`#${this.id}`)[0].style.display = 'none'
|
dropdownBox.get(0).style.display = 'none'
|
||||||
|
// When pressing the profile button
|
||||||
|
// It will make the menu reappear if we set `dropdownShown` immediately
|
||||||
|
setTimeout(() => {
|
||||||
|
this.dropdownShown = false
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
focusOut: function () {
|
focusOut: function () {
|
||||||
$(`#${this.id}`).focusout()
|
const dropdownBox = $(`#${this.id}`)
|
||||||
$(`#${this.id}`)[0].style.display = 'none'
|
|
||||||
|
dropdownBox.focusout()
|
||||||
|
dropdownBox.get(0).style.display = 'none'
|
||||||
|
this.dropdownShown = false
|
||||||
},
|
},
|
||||||
|
|
||||||
handleIconClick: function () {
|
handleIconClick: function () {
|
||||||
|
|
|
@ -2,6 +2,53 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clearInputTextButton {
|
||||||
|
position: absolute;
|
||||||
|
/* horizontal intentionally reduced to keep "I-beam pointer" visible */
|
||||||
|
padding: 10px 8px;
|
||||||
|
top: 5px;
|
||||||
|
left: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 200px 200px 200px 200px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
-moz-transition: background 0.2s ease-in, opacity 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in, opacity 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in, opacity 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearInputTextButton:hover {
|
||||||
|
background-color: var(--side-nav-hover-color);
|
||||||
|
}
|
||||||
|
.clearInputTextButton.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forceTextColor .clearInputTextButton:hover {
|
||||||
|
background-color: var(--primary-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearInputTextButton:active {
|
||||||
|
background-color: var(--tertiary-text-color);
|
||||||
|
-moz-transition: background 0.2s ease-in;
|
||||||
|
-o-transition: background 0.2s ease-in;
|
||||||
|
transition: background 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search .clearInputTextButton {
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forceTextColor .clearInputTextButton {
|
||||||
|
color: #EEEEEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forceTextColor .clearInputTextButton:active {
|
||||||
|
background-color: var(--primary-color-active);
|
||||||
|
}
|
||||||
|
|
||||||
.ft-input {
|
.ft-input {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
|
@ -41,12 +88,19 @@
|
||||||
|
|
||||||
.inputAction {
|
.inputAction {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 10px;
|
padding: 10px 8px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
right: 0px;
|
right: 0;
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 200px 200px 200px 200px;
|
border-radius: 200px 200px 200px 200px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
|
/* this should look disabled by default */
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputAction.enabled {
|
||||||
|
opacity: 100%;
|
||||||
|
/* Only look respond to cursor when enabled */
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search ::-webkit-calendar-picker-indicator {
|
.search ::-webkit-calendar-picker-indicator {
|
||||||
|
@ -61,25 +115,33 @@
|
||||||
color: #EEEEEE;
|
color: #EEEEEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputAction:hover {
|
.ft-input-component.showActionButton .ft-input {
|
||||||
|
/*
|
||||||
|
With arrow present means
|
||||||
|
the text might get under the arrow with normal padding
|
||||||
|
*/
|
||||||
|
padding-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputAction.enabled:hover {
|
||||||
background-color: var(--side-nav-hover-color);
|
background-color: var(--side-nav-hover-color);
|
||||||
-moz-transition: background 0.2s ease-in;
|
-moz-transition: background 0.2s ease-in;
|
||||||
-o-transition: background 0.2s ease-in;
|
-o-transition: background 0.2s ease-in;
|
||||||
transition: background 0.2s ease-in;
|
transition: background 0.2s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forceTextColor .inputAction:hover {
|
.forceTextColor .inputAction.enabled:hover {
|
||||||
background-color: var(--primary-color-hover);
|
background-color: var(--primary-color-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputAction:active {
|
.inputAction.enabled:active {
|
||||||
background-color: var(--tertiary-text-color);
|
background-color: var(--tertiary-text-color);
|
||||||
-moz-transition: background 0.2s ease-in;
|
-moz-transition: background 0.2s ease-in;
|
||||||
-o-transition: background 0.2s ease-in;
|
-o-transition: background 0.2s ease-in;
|
||||||
transition: background 0.2s ease-in;
|
transition: background 0.2s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forceTextColor .inputAction:active {
|
.forceTextColor .inputAction.enabled:active {
|
||||||
background-color: var(--primary-color-active);
|
background-color: var(--primary-color-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,9 +153,8 @@
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
border-radius: 0 0 5px 5px;
|
border-radius: 0 0 5px 5px;
|
||||||
border: 1px #ccc solid;
|
box-shadow: 0 0 10px var(--scrollbar-color-hover);
|
||||||
background-color: white;
|
background-color: var(--search-bar-color);
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list li {
|
.list li {
|
||||||
|
@ -103,6 +164,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover {
|
.hover {
|
||||||
background-color: #ccc;
|
background-color: var(--scrollbar-color-hover);
|
||||||
/* color: white; */
|
/* color: white; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.showClearTextButton .ft-input {
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
|
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
|
||||||
|
import { mapActions } from 'vuex'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'FtInput',
|
name: 'FtInput',
|
||||||
|
@ -15,10 +16,14 @@ export default Vue.extend({
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
showArrow: {
|
showActionButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
showClearTextButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
showLabel: {
|
showLabel: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
@ -56,7 +61,12 @@ export default Vue.extend({
|
||||||
showOptions: false,
|
showOptions: false,
|
||||||
selectedOption: -1,
|
selectedOption: -1,
|
||||||
isPointerInList: false
|
isPointerInList: false
|
||||||
}
|
},
|
||||||
|
// This button should be invisible on app start
|
||||||
|
// As the text input box should be empty
|
||||||
|
clearTextButtonExisting: false,
|
||||||
|
clearTextButtonVisible: false,
|
||||||
|
actionButtonIconName: 'search'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -70,11 +80,35 @@ export default Vue.extend({
|
||||||
|
|
||||||
idDataList: function () {
|
idDataList: function () {
|
||||||
return `${this.id}_datalist`
|
return `${this.id}_datalist`
|
||||||
|
},
|
||||||
|
|
||||||
|
inputDataPresent: function () {
|
||||||
|
return this.inputData.length > 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value: function (val) {
|
value: function (val) {
|
||||||
this.inputData = val
|
this.inputData = val
|
||||||
|
},
|
||||||
|
inputDataPresent: function (newVal, oldVal) {
|
||||||
|
if (newVal) {
|
||||||
|
// The button needs to be visible **immediately**
|
||||||
|
// To allow user to see the transition
|
||||||
|
this.clearTextButtonExisting = true
|
||||||
|
// The transition is not rendered if this property is set right after
|
||||||
|
// It's visible
|
||||||
|
setTimeout(() => {
|
||||||
|
this.clearTextButtonVisible = true
|
||||||
|
}, 0)
|
||||||
|
} else {
|
||||||
|
// Hide the button with transition
|
||||||
|
this.clearTextButtonVisible = false
|
||||||
|
// Remove the button after the transition
|
||||||
|
// 0.2s in CSS = 200ms in JS
|
||||||
|
setTimeout(() => {
|
||||||
|
this.clearTextButtonExisting = false
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -85,6 +119,9 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick: function () {
|
handleClick: function () {
|
||||||
|
// No action if no input text
|
||||||
|
if (!this.inputDataPresent) { return }
|
||||||
|
|
||||||
this.searchState.showOptions = false
|
this.searchState.showOptions = false
|
||||||
this.$emit('input', this.inputData)
|
this.$emit('input', this.inputData)
|
||||||
this.$emit('click', this.inputData)
|
this.$emit('click', this.inputData)
|
||||||
|
@ -94,9 +131,69 @@ export default Vue.extend({
|
||||||
if (this.isSearch &&
|
if (this.isSearch &&
|
||||||
this.searchState.selectedOption !== -1 &&
|
this.searchState.selectedOption !== -1 &&
|
||||||
this.inputData === this.dataList[this.searchState.selectedOption]) { return }
|
this.inputData === this.dataList[this.searchState.selectedOption]) { return }
|
||||||
|
this.handleActionIconChange()
|
||||||
this.$emit('input', this.inputData)
|
this.$emit('input', this.inputData)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleClearTextClick: function () {
|
||||||
|
this.inputData = ''
|
||||||
|
this.handleActionIconChange()
|
||||||
|
this.$emit('input', this.inputData)
|
||||||
|
|
||||||
|
// Focus on input element after text is clear for better UX
|
||||||
|
const inputElement = document.getElementById(this.id)
|
||||||
|
inputElement.focus()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleActionIconChange: function() {
|
||||||
|
// Only need to update icon if visible
|
||||||
|
if (!this.showActionButton) { return }
|
||||||
|
|
||||||
|
if (!this.inputDataPresent) {
|
||||||
|
// Change back to default icon if text is blank
|
||||||
|
this.actionButtonIconName = 'search'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update action button icon according to input
|
||||||
|
try {
|
||||||
|
this.getYoutubeUrlInfo(this.inputData).then((result) => {
|
||||||
|
let isYoutubeLink = false
|
||||||
|
|
||||||
|
switch (result.urlType) {
|
||||||
|
case 'video':
|
||||||
|
case 'playlist':
|
||||||
|
case 'search':
|
||||||
|
case 'channel':
|
||||||
|
isYoutubeLink = true
|
||||||
|
break
|
||||||
|
case 'hashtag':
|
||||||
|
// TODO: Implement a hashtag related view
|
||||||
|
// isYoutubeLink is already `false`
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'invalid_url':
|
||||||
|
default: {
|
||||||
|
// isYoutubeLink is already `false`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isYoutubeLink) {
|
||||||
|
// Go to URL (i.e. Video/Playlist/Channel
|
||||||
|
this.actionButtonIconName = 'arrow-right'
|
||||||
|
} else {
|
||||||
|
// Search with text
|
||||||
|
this.actionButtonIconName = 'search'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (ex) {
|
||||||
|
// On exception, consider text as invalid URL
|
||||||
|
this.actionButtonIconName = 'search'
|
||||||
|
// Rethrow exception
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
addListener: function () {
|
addListener: function () {
|
||||||
const inputElement = document.getElementById(this.id)
|
const inputElement = document.getElementById(this.id)
|
||||||
|
|
||||||
|
@ -145,6 +242,10 @@ export default Vue.extend({
|
||||||
if (this.selectOnFocus) {
|
if (this.selectOnFocus) {
|
||||||
e.target.select()
|
e.target.select()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
...mapActions([
|
||||||
|
'getYoutubeUrlInfo'
|
||||||
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
class="ft-input-component"
|
class="ft-input-component"
|
||||||
:class="{
|
:class="{
|
||||||
search: isSearch,
|
search: isSearch,
|
||||||
forceTextColor: forceTextColor
|
forceTextColor: forceTextColor,
|
||||||
|
showActionButton: showActionButton,
|
||||||
|
showClearTextButton: showClearTextButton
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
@ -18,6 +20,20 @@
|
||||||
:tooltip="tooltip"
|
:tooltip="tooltip"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<font-awesome-icon
|
||||||
|
v-if="showClearTextButton && clearTextButtonExisting"
|
||||||
|
icon="times-circle"
|
||||||
|
class="clearInputTextButton"
|
||||||
|
:class="{
|
||||||
|
visible: clearTextButtonVisible
|
||||||
|
}"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
:title="$t('Search Bar.Clear Input')"
|
||||||
|
@click="handleClearTextClick"
|
||||||
|
@keydown.space.prevent="handleClearTextClick"
|
||||||
|
@keydown.enter.prevent="handleClearTextClick"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
:id="id"
|
:id="id"
|
||||||
v-model="inputData"
|
v-model="inputData"
|
||||||
|
@ -33,9 +49,12 @@
|
||||||
@keydown="e => handleKeyDown(e.keyCode)"
|
@keydown="e => handleKeyDown(e.keyCode)"
|
||||||
>
|
>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="showArrow"
|
v-if="showActionButton"
|
||||||
icon="arrow-right"
|
:icon="actionButtonIconName"
|
||||||
class="inputAction"
|
class="inputAction"
|
||||||
|
:class="{
|
||||||
|
enabled: inputDataPresent
|
||||||
|
}"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonOption:hover {
|
.buttonOption:hover {
|
||||||
background-color: var(--card-bg-color);
|
background-color: var(--search-bar-color);
|
||||||
-moz-transition: background 0.2s ease-in;
|
-moz-transition: background 0.2s ease-in;
|
||||||
-o-transition: background 0.2s ease-in;
|
-o-transition: background 0.2s ease-in;
|
||||||
transition: background 0.2s ease-in;
|
transition: background 0.2s ease-in;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.grid {
|
.grid {
|
||||||
min-height: 264px;
|
min-height: 264px;
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
class="profileName"
|
class="profileName"
|
||||||
placeholder="Profile Name"
|
placeholder="Profile Name"
|
||||||
:value="profileName"
|
:value="profileName"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
@input="e => profileName = e"
|
@input="e => profileName = e"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
class="profileName"
|
class="profileName"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
:value="profileBgColor"
|
:value="profileBgColor"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
height: 400px;
|
height: 400px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background-color: var(--card-bg-color);
|
background-color: var(--card-bg-color);
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,.1);
|
box-shadow: 0 0 4px var(--scrollbar-color-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
#profileList:focus {
|
#profileList:focus {
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
showProfileList: false
|
profileListShown: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -38,12 +38,25 @@ export default Vue.extend({
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
$('#profileList').focusout(() => {
|
$('#profileList').focusout(() => {
|
||||||
$('#profileList')[0].style.display = 'none'
|
$('#profileList')[0].style.display = 'none'
|
||||||
|
// When pressing the profile button
|
||||||
|
// It will make the menu reappear if we set `profileListShown` immediately
|
||||||
|
setTimeout(() => {
|
||||||
|
this.profileListShown = false
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleProfileList: function () {
|
toggleProfileList: function () {
|
||||||
$('#profileList')[0].style.display = 'inline'
|
const profileList = $('#profileList')
|
||||||
$('#profileList').focus()
|
|
||||||
|
if (this.profileListShown) {
|
||||||
|
profileList.get(0).style.display = 'none'
|
||||||
|
this.profileListShown = false
|
||||||
|
} else {
|
||||||
|
profileList.get(0).style.display = 'inline'
|
||||||
|
profileList.get(0).focus()
|
||||||
|
this.profileListShown = true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
openProfileSettings: function () {
|
openProfileSettings: function () {
|
||||||
|
|
|
@ -15,6 +15,10 @@ export default Vue.extend({
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
extraLabels: {
|
||||||
|
type: Array,
|
||||||
|
default: () => { return [] }
|
||||||
|
},
|
||||||
optionNames: {
|
optionNames: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => { return [] }
|
default: () => { return [] }
|
||||||
|
|
|
@ -8,6 +8,15 @@
|
||||||
<h2 class="center">
|
<h2 class="center">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p
|
||||||
|
v-for="extraLabel in extraLabels"
|
||||||
|
:key="extraLabel"
|
||||||
|
class="center"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{{ extraLabel }}
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<ft-button
|
<ft-button
|
||||||
v-for="(option, index) in optionNames"
|
v-for="(option, index) in optionNames"
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.select option {
|
.select option {
|
||||||
color: #000000;
|
color: var(--secondary-text-color);
|
||||||
|
background-color: var(--card-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove focus */
|
/* Remove focus */
|
||||||
|
|
|
@ -112,6 +112,7 @@ export default Vue.extend({
|
||||||
'subsCapsButton',
|
'subsCapsButton',
|
||||||
'audioTrackButton',
|
'audioTrackButton',
|
||||||
'pictureInPictureToggle',
|
'pictureInPictureToggle',
|
||||||
|
'toggleTheatreModeButton',
|
||||||
'fullWindowButton',
|
'fullWindowButton',
|
||||||
'qualitySelector',
|
'qualitySelector',
|
||||||
'fullscreenToggle'
|
'fullscreenToggle'
|
||||||
|
@ -139,6 +140,10 @@ export default Vue.extend({
|
||||||
return this.$store.getters.getUsingElectron
|
return this.$store.getters.getUsingElectron
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentLocale: function () {
|
||||||
|
return this.$store.getters.getCurrentLocale
|
||||||
|
},
|
||||||
|
|
||||||
defaultPlayback: function () {
|
defaultPlayback: function () {
|
||||||
return this.$store.getters.getDefaultPlayback
|
return this.$store.getters.getDefaultPlayback
|
||||||
},
|
},
|
||||||
|
@ -195,6 +200,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
this.createFullWindowButton()
|
this.createFullWindowButton()
|
||||||
this.createLoopButton()
|
this.createLoopButton()
|
||||||
|
this.createToggleTheatreModeButton()
|
||||||
this.determineFormatType()
|
this.determineFormatType()
|
||||||
this.determineMaxFramerate()
|
this.determineMaxFramerate()
|
||||||
},
|
},
|
||||||
|
@ -268,6 +274,11 @@ export default Vue.extend({
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove built-in progress bar mouse over current time display
|
||||||
|
// `MouseTimeDisplay` in
|
||||||
|
// https://github.com/videojs/video.js/blob/v7.13.3/docs/guides/components.md#default-component-tree
|
||||||
|
this.player.controlBar.progressControl.seekBar.playProgressBar.removeChild('timeTooltip')
|
||||||
|
|
||||||
if (this.useSponsorBlock) {
|
if (this.useSponsorBlock) {
|
||||||
this.initializeSponsorBlock()
|
this.initializeSponsorBlock()
|
||||||
}
|
}
|
||||||
|
@ -459,7 +470,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
mouseScrollVolume: function (event) {
|
mouseScrollVolume: function (event) {
|
||||||
if (event.target) {
|
if (event.target && !event.currentTarget.querySelector('.vjs-menu:hover')) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
if (this.player.muted() && event.wheelDelta > 0) {
|
if (this.player.muted() && event.wheelDelta > 0) {
|
||||||
|
@ -966,6 +977,45 @@ export default Vue.extend({
|
||||||
videojs.registerComponent('fullWindowButton', fullWindowButton)
|
videojs.registerComponent('fullWindowButton', fullWindowButton)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createToggleTheatreModeButton: function() {
|
||||||
|
if (!this.$parent.theatrePossible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const theatreModeActive = this.$parent.useTheatreMode ? ' vjs-icon-theatre-active' : ''
|
||||||
|
|
||||||
|
const VjsButton = videojs.getComponent('Button')
|
||||||
|
const toggleTheatreModeButton = videojs.extend(VjsButton, {
|
||||||
|
constructor: function(player, options) {
|
||||||
|
VjsButton.call(this, player, options)
|
||||||
|
},
|
||||||
|
handleClick: () => {
|
||||||
|
this.toggleTheatreMode()
|
||||||
|
},
|
||||||
|
createControlTextEl: function (button) {
|
||||||
|
return $(button)
|
||||||
|
.addClass('vjs-button-theatre')
|
||||||
|
.html($(`<div id="toggleTheatreModeButton" class="vjs-icon-theatre-inactive${theatreModeActive} vjs-button"></div>`))
|
||||||
|
.attr('title', 'Toggle Theatre Mode')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
videojs.registerComponent('toggleTheatreModeButton', toggleTheatreModeButton)
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleTheatreMode: function() {
|
||||||
|
if (!this.player.isFullscreen_) {
|
||||||
|
const toggleTheatreModeButton = $('#toggleTheatreModeButton')
|
||||||
|
if (!this.$parent.useTheatreMode) {
|
||||||
|
toggleTheatreModeButton.addClass('vjs-icon-theatre-active')
|
||||||
|
} else {
|
||||||
|
toggleTheatreModeButton.removeClass('vjs-icon-theatre-active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$parent.toggleTheatreMode()
|
||||||
|
},
|
||||||
|
|
||||||
createDashQualitySelector: function (levels) {
|
createDashQualitySelector: function (levels) {
|
||||||
if (levels.levels_.length === 0) {
|
if (levels.levels_.length === 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -1054,6 +1104,47 @@ export default Vue.extend({
|
||||||
this.determineDefaultQualityDash()
|
this.determineDefaultQualityDash()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sortCaptions: function (captionList) {
|
||||||
|
return captionList.sort((captionA, captionB) => {
|
||||||
|
const aCode = captionA.languageCode.split('-') // ex. [en,US]
|
||||||
|
const bCode = captionB.languageCode.split('-')
|
||||||
|
const aName = (captionA.label || captionA.name.simpleText) // ex: english (auto-generated)
|
||||||
|
const bName = (captionB.label || captionB.name.simpleText)
|
||||||
|
const userLocale = this.currentLocale.split(/-|_/) // ex. [en,US]
|
||||||
|
if (aCode[0] === userLocale[0]) { // caption a has same language as user's locale
|
||||||
|
if (bCode[0] === userLocale[0]) { // caption b has same language as user's locale
|
||||||
|
if (bName.search('auto') !== -1) {
|
||||||
|
// prefer caption a: b is auto-generated captions
|
||||||
|
return -1
|
||||||
|
} else if (aName.search('auto') !== -1) {
|
||||||
|
// prefer caption b: a is auto-generated captions
|
||||||
|
return 1
|
||||||
|
} else if (aCode[1] === userLocale[1]) {
|
||||||
|
// prefer caption a: caption a has same county code as user's locale
|
||||||
|
return -1
|
||||||
|
} else if (bCode[1] === userLocale[1]) {
|
||||||
|
// prefer caption b: caption b has same county code as user's locale
|
||||||
|
return 1
|
||||||
|
} else if (aCode[1] === undefined) {
|
||||||
|
// prefer caption a: no country code is better than wrong country code
|
||||||
|
return -1
|
||||||
|
} else if (bCode[1] === undefined) {
|
||||||
|
// prefer caption b: no country code is better than wrong country code
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// prefer caption a: b does not match user's language
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
} else if (bCode[0] === userLocale[0]) {
|
||||||
|
// prefer caption b: a does not match user's language
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// sort alphabetically
|
||||||
|
return aName.localeCompare(bName)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
transformAndInsertCaptions: async function() {
|
transformAndInsertCaptions: async function() {
|
||||||
let captionList
|
let captionList
|
||||||
if (this.captionHybridList[0] instanceof Promise) {
|
if (this.captionHybridList[0] instanceof Promise) {
|
||||||
|
@ -1063,7 +1154,7 @@ export default Vue.extend({
|
||||||
captionList = this.captionHybridList
|
captionList = this.captionHybridList
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const caption of captionList) {
|
for (const caption of this.sortCaptions(captionList)) {
|
||||||
this.player.addRemoteTextTrack({
|
this.player.addRemoteTextTrack({
|
||||||
kind: 'subtitles',
|
kind: 'subtitles',
|
||||||
src: caption.baseUrl || caption.url,
|
src: caption.baseUrl || caption.url,
|
||||||
|
@ -1373,6 +1464,11 @@ export default Vue.extend({
|
||||||
// Toggle Full Window Mode
|
// Toggle Full Window Mode
|
||||||
this.toggleFullWindow()
|
this.toggleFullWindow()
|
||||||
break
|
break
|
||||||
|
case 84:
|
||||||
|
// T Key
|
||||||
|
// Toggle Theatre Mode
|
||||||
|
this.toggleTheatreMode()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,6 +50,11 @@ export default Vue.extend({
|
||||||
'start',
|
'start',
|
||||||
'middle',
|
'middle',
|
||||||
'end'
|
'end'
|
||||||
|
],
|
||||||
|
externalLinkHandlingValues: [
|
||||||
|
'',
|
||||||
|
'openLinkAfterPrompt',
|
||||||
|
'doNothing'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -150,6 +155,18 @@ export default Vue.extend({
|
||||||
this.$t('Settings.General Settings.Thumbnail Preference.Middle'),
|
this.$t('Settings.General Settings.Thumbnail Preference.Middle'),
|
||||||
this.$t('Settings.General Settings.Thumbnail Preference.End')
|
this.$t('Settings.General Settings.Thumbnail Preference.End')
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
externalLinkHandling: function () {
|
||||||
|
return this.$store.getters.getExternalLinkHandling
|
||||||
|
},
|
||||||
|
|
||||||
|
externalLinkHandlingNames: function () {
|
||||||
|
return [
|
||||||
|
this.$t('Settings.General Settings.External Link Handling.Open Link'),
|
||||||
|
this.$t('Settings.General Settings.External Link Handling.Ask Before Opening Link'),
|
||||||
|
this.$t('Settings.General Settings.External Link Handling.No Action')
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -218,7 +235,8 @@ export default Vue.extend({
|
||||||
'updateListType',
|
'updateListType',
|
||||||
'updateThumbnailPreference',
|
'updateThumbnailPreference',
|
||||||
'updateForceLocalBackendForLegacy',
|
'updateForceLocalBackendForLegacy',
|
||||||
'updateCurrentLocale'
|
'updateCurrentLocale',
|
||||||
|
'updateExternalLinkHandling'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1 +1,10 @@
|
||||||
@use "../../sass-partials/settings"
|
@use "../../sass-partials/settings"
|
||||||
|
|
||||||
|
.select
|
||||||
|
min-width: 240px
|
||||||
|
width: auto
|
||||||
|
|
||||||
|
// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors
|
||||||
|
.select::v-deep .select-text
|
||||||
|
min-width: 240px
|
||||||
|
width: auto
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.General Settings.General Settings") }}
|
{{ $t("Settings.General Settings.General Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<div class="switchColumnGrid">
|
<div class="switchColumnGrid">
|
||||||
<div class="switchColumn">
|
<div class="switchColumn">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
|
@ -85,11 +84,19 @@
|
||||||
:tooltip="$t('Tooltips.General Settings.Region for Trending')"
|
:tooltip="$t('Tooltips.General Settings.Region for Trending')"
|
||||||
@change="updateRegion"
|
@change="updateRegion"
|
||||||
/>
|
/>
|
||||||
|
<ft-select
|
||||||
|
:placeholder="$t('Settings.General Settings.External Link Handling.External Link Handling')"
|
||||||
|
:value="externalLinkHandling"
|
||||||
|
:select-names="externalLinkHandlingNames"
|
||||||
|
:select-values="externalLinkHandlingValues"
|
||||||
|
:tooltip="$t('Tooltips.General Settings.External Link Handling')"
|
||||||
|
@change="updateExternalLinkHandling"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ft-flex-box class="generalSettingsFlexBox">
|
<ft-flex-box class="generalSettingsFlexBox">
|
||||||
<ft-input
|
<ft-input
|
||||||
:placeholder="$t('Settings.General Settings.Current Invidious Instance')"
|
:placeholder="$t('Settings.General Settings.Current Invidious Instance')"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
:value="currentInvidiousInstance"
|
:value="currentInvidiousInstance"
|
||||||
:data-list="invidiousInstancesList"
|
:data-list="invidiousInstancesList"
|
||||||
|
@ -130,7 +137,7 @@
|
||||||
@click="handleClearDefaultInstanceClick"
|
@click="handleClearDefaultInstanceClick"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./general-settings.js" />
|
<script src="./general-settings.js" />
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.Player Settings.Player Settings") }}
|
{{ $t("Settings.Player Settings.Player Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<div class="switchColumnGrid">
|
<div class="switchColumnGrid">
|
||||||
<div class="switchColumn">
|
<div class="switchColumn">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
|
@ -127,7 +126,7 @@
|
||||||
@change="updateDefaultQuality"
|
@change="updateDefaultQuality"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./player-settings.js" />
|
<script src="./player-settings.js" />
|
||||||
|
|
|
@ -100,12 +100,18 @@ export default Vue.extend({
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'copyYoutube':
|
case 'copyYoutube':
|
||||||
navigator.clipboard.writeText(youtubeUrl)
|
navigator.clipboard.writeText(youtubeUrl)
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('Share.YouTube URL copied to clipboard')
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case 'openYoutube':
|
case 'openYoutube':
|
||||||
this.openExternalLink(youtubeUrl)
|
this.openExternalLink(youtubeUrl)
|
||||||
break
|
break
|
||||||
case 'copyInvidious':
|
case 'copyInvidious':
|
||||||
navigator.clipboard.writeText(invidiousUrl)
|
navigator.clipboard.writeText(invidiousUrl)
|
||||||
|
this.showToast({
|
||||||
|
message: this.$t('Share.Invidious URL copied to clipboard')
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case 'openInvidious':
|
case 'openInvidious':
|
||||||
this.openExternalLink(invidiousUrl)
|
this.openExternalLink(invidiousUrl)
|
||||||
|
@ -131,6 +137,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
...mapActions([
|
...mapActions([
|
||||||
|
'showToast',
|
||||||
'openExternalLink'
|
'openExternalLink'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
|
||||||
<h3>
|
<h3>
|
||||||
{{ $t("Settings.Privacy Settings.Privacy Settings") }}
|
{{ $t("Settings.Privacy Settings.Privacy Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<div class="switchColumnGrid">
|
<div class="switchColumnGrid">
|
||||||
<div class="switchColumn">
|
<div class="switchColumn">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
|
@ -75,7 +76,7 @@
|
||||||
:option-values="promptValues"
|
:option-values="promptValues"
|
||||||
@click="handleRemoveSubscriptions"
|
@click="handleRemoveSubscriptions"
|
||||||
/>
|
/>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./privacy-settings.js" />
|
<script src="./privacy-settings.js" />
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
width: 85%;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
|
||||||
.card {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
|
||||||
.subscriptionSettingsFlexBox {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
@use "../../sass-partials/settings"
|
||||||
|
|
||||||
|
@media only screen and (max-width: 500px)
|
||||||
|
.subscriptionSettingsFlexBox
|
||||||
|
justify-content: flex-start
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.Proxy Settings.Proxy Settings") }}
|
{{ $t("Settings.Proxy Settings.Proxy Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<ft-flex-box class="subscriptionSettingsFlexBox">
|
<ft-flex-box class="subscriptionSettingsFlexBox">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
:label="$t('Settings.Proxy Settings.Enable Tor / Proxy')"
|
:label="$t('Settings.Proxy Settings.Enable Tor / Proxy')"
|
||||||
|
@ -26,14 +25,14 @@
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<ft-input
|
<ft-input
|
||||||
:placeholder="$t('Settings.Proxy Settings.Proxy Host')"
|
:placeholder="$t('Settings.Proxy Settings.Proxy Host')"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
:value="proxyHostname"
|
:value="proxyHostname"
|
||||||
@input="handleUpdateProxyHostname"
|
@input="handleUpdateProxyHostname"
|
||||||
/>
|
/>
|
||||||
<ft-input
|
<ft-input
|
||||||
:placeholder="$t('Settings.Proxy Settings.Proxy Port Number')"
|
:placeholder="$t('Settings.Proxy Settings.Proxy Port Number')"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
:value="proxyPort"
|
:value="proxyPort"
|
||||||
@input="handleUpdateProxyPort"
|
@input="handleUpdateProxyPort"
|
||||||
|
@ -71,8 +70,8 @@
|
||||||
{{ $t('Settings.Proxy Settings.City') }}: {{ proxyCity }}
|
{{ $t('Settings.Proxy Settings.City') }}: {{ proxyCity }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./proxy-settings.js" />
|
<script src="./proxy-settings.js" />
|
||||||
<style scoped src="./proxy-settings.css" />
|
<style scoped lang="sass" src="./proxy-settings.sass" />
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
class="navIcon"
|
class="navIcon"
|
||||||
/>
|
/>
|
||||||
<p class="navLabel">
|
<p class="navLabel">
|
||||||
{{ $t("Trending") }}
|
{{ $t("Trending.Trending") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
fixed-width
|
fixed-width
|
||||||
/>
|
/>
|
||||||
<p class="navLabel">
|
<p class="navLabel">
|
||||||
{{ $t("Trending") }}
|
{{ $t("Trending.Trending") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
width: 85%;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
|
||||||
.card {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
|
||||||
.sponsorBlockSettingsFlexBox {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
@use "../../sass-partials/settings"
|
||||||
|
|
||||||
|
@media only screen and (max-width: 500px)
|
||||||
|
.sponsorBlockSettingsFlexBox
|
||||||
|
justify-content: flex-start
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.SponsorBlock Settings.SponsorBlock Settings") }}
|
{{ $t("Settings.SponsorBlock Settings.SponsorBlock Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<ft-flex-box class="sponsorBlockSettingsFlexBox">
|
<ft-flex-box class="sponsorBlockSettingsFlexBox">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
:label="$t('Settings.SponsorBlock Settings.Enable SponsorBlock')"
|
:label="$t('Settings.SponsorBlock Settings.Enable SponsorBlock')"
|
||||||
|
@ -24,14 +23,14 @@
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<ft-input
|
<ft-input
|
||||||
:placeholder="$t('Settings.SponsorBlock Settings[\'SponsorBlock API Url (Default is https://sponsor.ajay.app)\']')"
|
:placeholder="$t('Settings.SponsorBlock Settings[\'SponsorBlock API Url (Default is https://sponsor.ajay.app)\']')"
|
||||||
:show-arrow="false"
|
:show-action-button="false"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
:value="sponsorBlockUrl"
|
:value="sponsorBlockUrl"
|
||||||
@input="handleUpdateSponsorBlockUrl"
|
@input="handleUpdateSponsorBlockUrl"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./sponsor-block-settings.js" />
|
<script src="./sponsor-block-settings.js" />
|
||||||
<style scoped src="./sponsor-block-settings.css" />
|
<style scoped lang="sass" src="./sponsor-block-settings.sass" />
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
width: 85%;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
|
||||||
.card {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
|
||||||
.subscriptionSettingsFlexBox {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
@use "../../sass-partials/settings"
|
||||||
|
|
||||||
|
@media only screen and (max-width: 500px)
|
||||||
|
.subscriptionSettingsFlexBox
|
||||||
|
justify-content: flex-start
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
<h3>
|
||||||
<h3
|
|
||||||
class="videoTitle"
|
|
||||||
>
|
|
||||||
{{ $t("Settings.Subscription Settings.Subscription Settings") }}
|
{{ $t("Settings.Subscription Settings.Subscription Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<ft-flex-box class="subscriptionSettingsFlexBox">
|
<ft-flex-box class="subscriptionSettingsFlexBox">
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
:label="$t('Settings.Subscription Settings.Hide Videos on Watch')"
|
:label="$t('Settings.Subscription Settings.Hide Videos on Watch')"
|
||||||
|
@ -37,8 +36,8 @@
|
||||||
label="Manage My Subscriptions"
|
label="Manage My Subscriptions"
|
||||||
/>
|
/>
|
||||||
</ft-flex-box>
|
</ft-flex-box>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./subscription-settings.js" />
|
<script src="./subscription-settings.js" />
|
||||||
<style scoped src="./subscription-settings.css" />
|
<style scoped lang="sass" src="./subscription-settings.sass" />
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
width: 85%;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
|
||||||
.card {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,8 @@ export default Vue.extend({
|
||||||
baseThemeValues: [
|
baseThemeValues: [
|
||||||
'light',
|
'light',
|
||||||
'dark',
|
'dark',
|
||||||
'black'
|
'black',
|
||||||
|
'dracula'
|
||||||
],
|
],
|
||||||
colorValues: [
|
colorValues: [
|
||||||
'Red',
|
'Red',
|
||||||
|
@ -53,7 +54,14 @@ export default Vue.extend({
|
||||||
'Yellow',
|
'Yellow',
|
||||||
'Amber',
|
'Amber',
|
||||||
'Orange',
|
'Orange',
|
||||||
'DeepOrange'
|
'DeepOrange',
|
||||||
|
'DraculaCyan',
|
||||||
|
'DraculaGreen',
|
||||||
|
'DraculaOrange',
|
||||||
|
'DraculaPink',
|
||||||
|
'DraculaPurple',
|
||||||
|
'DraculaRed',
|
||||||
|
'DraculaYellow'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -89,7 +97,8 @@ export default Vue.extend({
|
||||||
return [
|
return [
|
||||||
this.$t('Settings.Theme Settings.Base Theme.Light'),
|
this.$t('Settings.Theme Settings.Base Theme.Light'),
|
||||||
this.$t('Settings.Theme Settings.Base Theme.Dark'),
|
this.$t('Settings.Theme Settings.Base Theme.Dark'),
|
||||||
this.$t('Settings.Theme Settings.Base Theme.Black')
|
this.$t('Settings.Theme Settings.Base Theme.Black'),
|
||||||
|
this.$t('Settings.Theme Settings.Base Theme.Dracula')
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -110,7 +119,14 @@ export default Vue.extend({
|
||||||
this.$t('Settings.Theme Settings.Main Color Theme.Yellow'),
|
this.$t('Settings.Theme Settings.Main Color Theme.Yellow'),
|
||||||
this.$t('Settings.Theme Settings.Main Color Theme.Amber'),
|
this.$t('Settings.Theme Settings.Main Color Theme.Amber'),
|
||||||
this.$t('Settings.Theme Settings.Main Color Theme.Orange'),
|
this.$t('Settings.Theme Settings.Main Color Theme.Orange'),
|
||||||
this.$t('Settings.Theme Settings.Main Color Theme.Deep Orange')
|
this.$t('Settings.Theme Settings.Main Color Theme.Deep Orange'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Cyan'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Green'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Orange'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Pink'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Purple'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Red'),
|
||||||
|
this.$t('Settings.Theme Settings.Main Color Theme.Dracula Yellow')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
@use "../../sass-partials/settings"
|
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<ft-card
|
<details>
|
||||||
class="relative card"
|
<summary>
|
||||||
>
|
|
||||||
<h3>
|
<h3>
|
||||||
{{ $t("Settings.Theme Settings.Theme Settings") }}
|
{{ $t("Settings.Theme Settings.Theme Settings") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
</summary>
|
||||||
|
<hr>
|
||||||
<ft-flex-box>
|
<ft-flex-box>
|
||||||
<ft-toggle-switch
|
<ft-toggle-switch
|
||||||
:label="$t('Settings.Theme Settings.Match Top Bar with Main Color')"
|
:label="$t('Settings.Theme Settings.Match Top Bar with Main Color')"
|
||||||
|
@ -64,8 +65,8 @@
|
||||||
:option-values="restartPromptValues"
|
:option-values="restartPromptValues"
|
||||||
@click="handleSmoothScrolling"
|
@click="handleSmoothScrolling"
|
||||||
/>
|
/>
|
||||||
</ft-card>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./theme-settings.js" />
|
<script src="./theme-settings.js" />
|
||||||
<style scoped src="./theme-settings.css" />
|
<style scoped lang="sass" src="./theme-settings.sass" />
|
||||||
|
|
|
@ -20,6 +20,8 @@ export default Vue.extend({
|
||||||
windowWidth: 0,
|
windowWidth: 0,
|
||||||
showFilters: false,
|
showFilters: false,
|
||||||
searchFilterValueChanged: false,
|
searchFilterValueChanged: false,
|
||||||
|
historyIndex: 1,
|
||||||
|
isForwardOrBack: false,
|
||||||
searchSuggestionsDataList: []
|
searchSuggestionsDataList: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -257,12 +259,41 @@ export default Vue.extend({
|
||||||
this.searchFilterValueChanged = filterValueChanged
|
this.searchFilterValueChanged = filterValueChanged
|
||||||
},
|
},
|
||||||
|
|
||||||
|
navigateHistory: function() {
|
||||||
|
if (!this.isForwardOrBack) {
|
||||||
|
this.historyIndex = window.history.length
|
||||||
|
$('#historyArrowBack').removeClass('fa-arrow-left')
|
||||||
|
$('#historyArrowForward').addClass('fa-arrow-right')
|
||||||
|
} else {
|
||||||
|
this.isForwardOrBack = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
historyBack: function () {
|
historyBack: function () {
|
||||||
|
this.isForwardOrBack = true
|
||||||
window.history.back()
|
window.history.back()
|
||||||
|
|
||||||
|
if (this.historyIndex > 1) {
|
||||||
|
this.historyIndex--
|
||||||
|
$('#historyArrowForward').removeClass('fa-arrow-right')
|
||||||
|
if (this.historyIndex === 1) {
|
||||||
|
$('#historyArrowBack').addClass('fa-arrow-left')
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
historyForward: function () {
|
historyForward: function () {
|
||||||
|
this.isForwardOrBack = true
|
||||||
window.history.forward()
|
window.history.forward()
|
||||||
|
|
||||||
|
if (this.historyIndex < window.history.length) {
|
||||||
|
this.historyIndex++
|
||||||
|
$('#historyArrowBack').removeClass('fa-arrow-left')
|
||||||
|
|
||||||
|
if (this.historyIndex === window.history.length) {
|
||||||
|
$('#historyArrowForward').addClass('fa-arrow-right')
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSideNav: function () {
|
toggleSideNav: function () {
|
||||||
|
@ -277,7 +308,12 @@ export default Vue.extend({
|
||||||
// Web placeholder
|
// Web placeholder
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
navigate: function (route) {
|
||||||
|
this.$router.push('/' + route)
|
||||||
|
},
|
||||||
|
hideFilters: function () {
|
||||||
|
this.showFilters = false
|
||||||
|
},
|
||||||
...mapActions([
|
...mapActions([
|
||||||
'showToast',
|
'showToast',
|
||||||
'getYoutubeUrlInfo',
|
'getYoutubeUrlInfo',
|
||||||
|
|
|
@ -38,6 +38,12 @@
|
||||||
width: 1em
|
width: 1em
|
||||||
height: 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
|
@include top-nav-is-colored
|
||||||
color: var(--text-with-main-color)
|
color: var(--text-with-main-color)
|
||||||
|
|
||||||
|
@ -85,7 +91,13 @@
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
padding: 0px 25px 0px 10px
|
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
|
.logoIcon
|
||||||
background-image: var(--logo-icon)
|
background-image: var(--logo-icon)
|
||||||
background-repeat: no-repeat
|
background-repeat: no-repeat
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
@keypress="toggleSideNav"
|
@keypress="toggleSideNav"
|
||||||
/>
|
/>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
class="navBackIcon navIcon"
|
id="historyArrowBack"
|
||||||
|
class="navBackIcon navIcon fa-arrow-left"
|
||||||
icon="arrow-left"
|
icon="arrow-left"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -22,7 +23,8 @@
|
||||||
@keypress="historyBack"
|
@keypress="historyBack"
|
||||||
/>
|
/>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
class="navForwardIcon navIcon"
|
id="historyArrowForward"
|
||||||
|
class="navForwardIcon navIcon fa-arrow-right"
|
||||||
icon="arrow-right"
|
icon="arrow-right"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -44,7 +46,15 @@
|
||||||
:title="newWindowText"
|
:title="newWindowText"
|
||||||
@click="createNewWindow"
|
@click="createNewWindow"
|
||||||
/>
|
/>
|
||||||
<div class="logo">
|
<div
|
||||||
|
class="logo"
|
||||||
|
role="link"
|
||||||
|
tabindex="0"
|
||||||
|
:title="$t('Subscriptions.Subscriptions')"
|
||||||
|
@click="navigate('subscriptions')"
|
||||||
|
@keydown.space.prevent="navigate('subscriptions')"
|
||||||
|
@keydown.enter.prevent="navigate('subscriptions')"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="logoIcon"
|
class="logoIcon"
|
||||||
/>
|
/>
|
||||||
|
@ -62,6 +72,7 @@
|
||||||
:select-on-focus="true"
|
:select-on-focus="true"
|
||||||
:data-list="searchSuggestionsDataList"
|
:data-list="searchSuggestionsDataList"
|
||||||
:spellcheck="false"
|
:spellcheck="false"
|
||||||
|
:show-clear-text-button="true"
|
||||||
@input="getSearchSuggestionsDebounce"
|
@input="getSearchSuggestionsDebounce"
|
||||||
@click="goToSearch"
|
@click="goToSearch"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.commentText {
|
.commentText {
|
||||||
|
white-space: pre-wrap;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
margin-left: 70px;
|
margin-left: 70px;
|
||||||
|
|
|
@ -191,7 +191,14 @@ export default Vue.extend({
|
||||||
this.showToast({
|
this.showToast({
|
||||||
message: this.$t('Falling back to Invidious API')
|
message: this.$t('Falling back to Invidious API')
|
||||||
})
|
})
|
||||||
this.getCommentDataInvidious()
|
this.getCommentDataInvidious({
|
||||||
|
resource: 'comments',
|
||||||
|
id: this.id,
|
||||||
|
params: {
|
||||||
|
continuation: this.nextPageToken,
|
||||||
|
sort_by: this.sortNewest ? 'new' : 'top'
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
}
|
}
|
||||||
|
@ -219,7 +226,14 @@ export default Vue.extend({
|
||||||
this.showToast({
|
this.showToast({
|
||||||
message: this.$t('Falling back to Invidious API')
|
message: this.$t('Falling back to Invidious API')
|
||||||
})
|
})
|
||||||
this.getCommentDataInvidious()
|
this.getCommentDataInvidious({
|
||||||
|
resource: 'comments',
|
||||||
|
id: this.id,
|
||||||
|
params: {
|
||||||
|
continuation: this.nextPageToken,
|
||||||
|
sort_by: this.sortNewest ? 'new' : 'top'
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
}
|
}
|
||||||
|
@ -250,7 +264,7 @@ export default Vue.extend({
|
||||||
if (this.hideCommentLikes) {
|
if (this.hideCommentLikes) {
|
||||||
comment.likes = null
|
comment.likes = null
|
||||||
}
|
}
|
||||||
comment.text = autolinker.link(comment.text.replace(/(<([^>]+)>)/ig, ''))
|
comment.text = autolinker.link(comment.text.replace(/(<(?!br>)([^>]+)>)/ig, ''))
|
||||||
|
|
||||||
return comment
|
return comment
|
||||||
})
|
})
|
||||||
|
@ -282,7 +296,7 @@ export default Vue.extend({
|
||||||
} else {
|
} else {
|
||||||
comment.likes = comment.likeCount
|
comment.likes = comment.likeCount
|
||||||
}
|
}
|
||||||
comment.text = autolinker.link(comment.content.replace(/(<([^>]+)>)/ig, ''))
|
comment.text = autolinker.link(comment.content.replace(/(<(?!br>)([^>]+)>)/ig, ''))
|
||||||
comment.dataType = 'invidious'
|
comment.dataType = 'invidious'
|
||||||
|
|
||||||
if (typeof (comment.replies) !== 'undefined' && typeof (comment.replies.replyCount) !== 'undefined') {
|
if (typeof (comment.replies) !== 'undefined' && typeof (comment.replies.replyCount) !== 'undefined') {
|
||||||
|
@ -348,7 +362,7 @@ export default Vue.extend({
|
||||||
} else {
|
} else {
|
||||||
comment.likes = comment.likeCount
|
comment.likes = comment.likeCount
|
||||||
}
|
}
|
||||||
comment.text = autolinker.link(comment.content.replace(/(<([^>]+)>)/ig, ''))
|
comment.text = autolinker.link(comment.content.replace(/(<(?!br>)([^>]+)>)/ig, ''))
|
||||||
comment.time = comment.publishedText
|
comment.time = comment.publishedText
|
||||||
comment.dataType = 'invidious'
|
comment.dataType = 'invidious'
|
||||||
comment.numReplies = 0
|
comment.numReplies = 0
|
||||||
|
|
|
@ -146,7 +146,7 @@
|
||||||
class="showMoreReplies"
|
class="showMoreReplies"
|
||||||
@click="getCommentReplies(index)"
|
@click="getCommentReplies(index)"
|
||||||
>
|
>
|
||||||
<span>Show More Replies</span>
|
<span>{{ $t("Comments.Show More Replies") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Vue from 'vue'
|
||||||
import FtCard from '../ft-card/ft-card.vue'
|
import FtCard from '../ft-card/ft-card.vue'
|
||||||
import FtTimestampCatcher from '../ft-timestamp-catcher/ft-timestamp-catcher.vue'
|
import FtTimestampCatcher from '../ft-timestamp-catcher/ft-timestamp-catcher.vue'
|
||||||
import autolinker from 'autolinker'
|
import autolinker from 'autolinker'
|
||||||
|
import $ from 'jquery'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'WatchVideoDescription',
|
name: 'WatchVideoDescription',
|
||||||
|
@ -34,6 +35,10 @@ export default Vue.extend({
|
||||||
} else {
|
} else {
|
||||||
this.shownDescription = autolinker.link(this.description)
|
this.shownDescription = autolinker.link(this.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (/^\s*$/.test(this.shownDescription)) {
|
||||||
|
$('.videoDescription')[0].style.display = 'none'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onTimestamp: function(timestamp) {
|
onTimestamp: function(timestamp) {
|
||||||
|
|
|
@ -130,6 +130,10 @@ export default Vue.extend({
|
||||||
return this.$store.getters.getCurrentInvidiousInstance
|
return this.$store.getters.getCurrentInvidiousInstance
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentLocale: function () {
|
||||||
|
return this.$store.getters.getCurrentLocale
|
||||||
|
},
|
||||||
|
|
||||||
profileList: function () {
|
profileList: function () {
|
||||||
return this.$store.getters.getProfileList
|
return this.$store.getters.getProfileList
|
||||||
},
|
},
|
||||||
|
@ -227,9 +231,9 @@ export default Vue.extend({
|
||||||
|
|
||||||
dateString() {
|
dateString() {
|
||||||
const date = new Date(this.published)
|
const date = new Date(this.published)
|
||||||
const dateSplit = date.toDateString().split(' ')
|
const locale = this.currentLocale.replace('_', '-')
|
||||||
const localeDateString = `Video.Published.${dateSplit[1]}`
|
const localeDateString = new Intl.DateTimeFormat([locale, 'en'], { dateStyle: 'medium' }).format(date)
|
||||||
return `${this.$t(localeDateString)} ${dateSplit[2]}, ${dateSplit[3]}`
|
return `${localeDateString}`
|
||||||
},
|
},
|
||||||
|
|
||||||
publishedString() {
|
publishedString() {
|
||||||
|
|
|
@ -86,7 +86,3 @@
|
||||||
::v-deep .iconDropdown
|
::v-deep .iconDropdown
|
||||||
left: calc(50% - 20px)
|
left: calc(50% - 20px)
|
||||||
right: auto
|
right: auto
|
||||||
|
|
||||||
@media only screen and (max-width: 1350px)
|
|
||||||
.theatreModeButton
|
|
||||||
display: none
|
|
||||||
|
|
|
@ -78,14 +78,6 @@
|
||||||
theme="secondary"
|
theme="secondary"
|
||||||
@click="handleExternalPlayer"
|
@click="handleExternalPlayer"
|
||||||
/>
|
/>
|
||||||
<ft-icon-button
|
|
||||||
v-if="theatrePossible"
|
|
||||||
:title="$t('Toggle Theatre Mode')"
|
|
||||||
class="theatreModeButton option"
|
|
||||||
icon="tv"
|
|
||||||
theme="secondary"
|
|
||||||
@click="$emit('theatre-mode')"
|
|
||||||
/>
|
|
||||||
<ft-icon-button
|
<ft-icon-button
|
||||||
v-if="!isUpcoming && downloadLinks.length > 0"
|
v-if="!isUpcoming && downloadLinks.length > 0"
|
||||||
:title="$t('Video.Download Video')"
|
:title="$t('Video.Download Video')"
|
||||||
|
|
|
@ -190,14 +190,14 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const videoIndex = this.playlistItems.findIndex((item) => {
|
const videoIndex = this.playlistItems.findIndex((item) => {
|
||||||
return item.id === this.videoId
|
return (item.id ?? item.videoId) === this.videoId
|
||||||
})
|
})
|
||||||
|
|
||||||
if (videoIndex === this.playlistItems.length - 1) {
|
if (videoIndex === this.playlistItems.length - 1) {
|
||||||
if (this.loopEnabled) {
|
if (this.loopEnabled) {
|
||||||
this.$router.push(
|
this.$router.push(
|
||||||
{
|
{
|
||||||
path: `/watch/${this.playlistItems[0].id}`,
|
path: `/watch/${this.playlistItems[0].id ?? this.playlistItems[0].videoId}`,
|
||||||
query: playlistInfo
|
query: playlistInfo
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -211,7 +211,7 @@ export default Vue.extend({
|
||||||
} else {
|
} else {
|
||||||
this.$router.push(
|
this.$router.push(
|
||||||
{
|
{
|
||||||
path: `/watch/${this.playlistItems[videoIndex + 1].id}`,
|
path: `/watch/${this.playlistItems[videoIndex + 1].id ?? this.playlistItems[videoIndex + 1].videoId}`,
|
||||||
query: playlistInfo
|
query: playlistInfo
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -253,20 +253,20 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const videoIndex = this.playlistItems.findIndex((item) => {
|
const videoIndex = this.playlistItems.findIndex((item) => {
|
||||||
return item.id === this.videoId
|
return (item.id ?? item.videoId) === this.videoId
|
||||||
})
|
})
|
||||||
|
|
||||||
if (videoIndex === 0) {
|
if (videoIndex === 0) {
|
||||||
this.$router.push(
|
this.$router.push(
|
||||||
{
|
{
|
||||||
path: `/watch/${this.playlistItems[this.randomizedPlaylistItems.length - 1].id}`,
|
path: `/watch/${this.playlistItems[this.randomizedPlaylistItems.length - 1].id ?? this.playlistItems[this.randomizedPlaylistItems.length - 1].videoId}`,
|
||||||
query: playlistInfo
|
query: playlistInfo
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.$router.push(
|
this.$router.push(
|
||||||
{
|
{
|
||||||
path: `/watch/${this.playlistItems[videoIndex - 1].id}`,
|
path: `/watch/${this.playlistItems[videoIndex - 1].id ?? this.playlistItems[videoIndex - 1].videoId}`,
|
||||||
query: playlistInfo
|
query: playlistInfo
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -388,8 +388,8 @@ export default Vue.extend({
|
||||||
this.playlistItems.forEach((item) => {
|
this.playlistItems.forEach((item) => {
|
||||||
const randomInt = Math.floor(Math.random() * remainingItems.length)
|
const randomInt = Math.floor(Math.random() * remainingItems.length)
|
||||||
|
|
||||||
if (remainingItems[randomInt].id !== this.videoId) {
|
if ((remainingItems[randomInt].id ?? remainingItems[randomInt].videoId) !== this.videoId) {
|
||||||
items.push(remainingItems[randomInt].id)
|
items.push(remainingItems[randomInt].id ?? remainingItems[randomInt].videoId)
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingItems.splice(randomInt, 1)
|
remainingItems.splice(randomInt, 1)
|
||||||
|
|
|
@ -8,8 +8,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoPlayToggle {
|
.autoPlayToggle {
|
||||||
width: 120px;
|
display: flex;
|
||||||
position: absolute;
|
justify-content: flex-end;
|
||||||
top: 10px;
|
align-items: center;
|
||||||
right: 0px;
|
}
|
||||||
|
|
||||||
|
.VideoRecommendationsTopBar{
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
v-if="!hideRecommendedVideos"
|
v-if="!hideRecommendedVideos"
|
||||||
class="relative watchVideoRecommendations"
|
class="relative watchVideoRecommendations"
|
||||||
>
|
>
|
||||||
|
<div class="VideoRecommendationsTopBar">
|
||||||
<h3>
|
<h3>
|
||||||
{{ $t("Up Next") }}
|
{{ $t("Up Next") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
:default-value="playNextVideo"
|
:default-value="playNextVideo"
|
||||||
@change="updatePlayNextVideo"
|
@change="updatePlayNextVideo"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<ft-list-video
|
<ft-list-video
|
||||||
v-for="(video, index) in data"
|
v-for="(video, index) in data"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
|
|
@ -63,7 +63,7 @@ const router = new Router({
|
||||||
{
|
{
|
||||||
path: '/trending',
|
path: '/trending',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Trending',
|
title: 'Trending.Trending',
|
||||||
icon: 'fa-home'
|
icon: 'fa-home'
|
||||||
},
|
},
|
||||||
component: Trending
|
component: Trending
|
||||||
|
|
|
@ -44,14 +44,12 @@ $thumbnail-overlay-opacity: 0.85
|
||||||
opacity: 1
|
opacity: 1
|
||||||
|
|
||||||
.videoThumbnail
|
.videoThumbnail
|
||||||
display: flex
|
|
||||||
position: relative
|
position: relative
|
||||||
|
|
||||||
.thumbnailLink
|
.thumbnailLink
|
||||||
display: flex
|
display: flex
|
||||||
|
|
||||||
.thumbnailImage
|
.thumbnailImage
|
||||||
height: 130px
|
|
||||||
|
|
||||||
@include is-sidebar-item
|
@include is-sidebar-item
|
||||||
height: 75px
|
height: 75px
|
||||||
|
@ -62,6 +60,7 @@ $thumbnail-overlay-opacity: 0.85
|
||||||
|
|
||||||
.videoWatched
|
.videoWatched
|
||||||
position: absolute
|
position: absolute
|
||||||
|
top:0
|
||||||
padding: 2px
|
padding: 2px
|
||||||
opacity: $thumbnail-overlay-opacity
|
opacity: $thumbnail-overlay-opacity
|
||||||
color: var(--primary-text-color)
|
color: var(--primary-text-color)
|
||||||
|
|
|
@ -16,10 +16,25 @@
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
justify-items: start
|
justify-items: start
|
||||||
|
|
||||||
.card
|
details
|
||||||
|
background-color: var(--card-bg-color)
|
||||||
width: 85%
|
width: 85%
|
||||||
margin: 0 auto
|
margin: 0 auto
|
||||||
margin-bottom: 10px
|
|
||||||
|
hr
|
||||||
|
width: 100%
|
||||||
|
height: 2px
|
||||||
|
border: 0
|
||||||
|
margin-top: -1px
|
||||||
|
background-color: var(--primary-color)
|
||||||
|
|
||||||
|
summary
|
||||||
|
display: block
|
||||||
|
cursor: pointer
|
||||||
|
padding: 1px 1px 1px 1px
|
||||||
|
|
||||||
|
h3
|
||||||
|
margin-left: 2%
|
||||||
|
|
||||||
@media only screen and (max-width: 680px)
|
@media only screen and (max-width: 680px)
|
||||||
width: 90%
|
width: 90%
|
||||||
|
|
|
@ -179,6 +179,7 @@ const state = {
|
||||||
displayVideoPlayButton: true,
|
displayVideoPlayButton: true,
|
||||||
enableSearchSuggestions: true,
|
enableSearchSuggestions: true,
|
||||||
enableSubtitles: true,
|
enableSubtitles: true,
|
||||||
|
externalLinkHandling: '',
|
||||||
externalPlayer: '',
|
externalPlayer: '',
|
||||||
externalPlayerExecutable: '',
|
externalPlayerExecutable: '',
|
||||||
externalPlayerIgnoreWarnings: false,
|
externalPlayerIgnoreWarnings: false,
|
||||||
|
|
|
@ -5,7 +5,12 @@ const state = {
|
||||||
isSideNavOpen: false,
|
isSideNavOpen: false,
|
||||||
sessionSearchHistory: [],
|
sessionSearchHistory: [],
|
||||||
popularCache: null,
|
popularCache: null,
|
||||||
trendingCache: null,
|
trendingCache: {
|
||||||
|
default: null,
|
||||||
|
music: null,
|
||||||
|
gaming: null,
|
||||||
|
movies: null
|
||||||
|
},
|
||||||
showProgressBar: false,
|
showProgressBar: false,
|
||||||
progressBarPercentage: 0,
|
progressBarPercentage: 0,
|
||||||
regionNames: [],
|
regionNames: [],
|
||||||
|
@ -303,6 +308,13 @@ const actions = {
|
||||||
return paramsObject
|
return paramsObject
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// youtube.com/shorts
|
||||||
|
function() {
|
||||||
|
if (urlObject.pathname.match(/^\/shorts\/[A-Za-z0-9_-]+$/)) {
|
||||||
|
extractParams(urlObject.pathname.replace('/shorts/', ''))
|
||||||
|
return paramsObject
|
||||||
|
}
|
||||||
|
},
|
||||||
// cloudtube
|
// cloudtube
|
||||||
function() {
|
function() {
|
||||||
if (urlObject.host.match(/^cadence\.(gq|moe)$/) && urlObject.pathname.match(/^\/cloudtube\/video\/[A-Za-z0-9_-]+$/)) {
|
if (urlObject.host.match(/^cadence\.(gq|moe)$/) && urlObject.pathname.match(/^\/cloudtube\/video\/[A-Za-z0-9_-]+$/)) {
|
||||||
|
@ -838,8 +850,8 @@ const mutations = {
|
||||||
state.popularCache = value
|
state.popularCache = value
|
||||||
},
|
},
|
||||||
|
|
||||||
setTrendingCache (state, value) {
|
setTrendingCache (state, value, page) {
|
||||||
state.trendingCache = value
|
state.trendingCache[page] = value
|
||||||
},
|
},
|
||||||
|
|
||||||
setSearchSortBy (state, value) {
|
setSearchSortBy (state, value) {
|
||||||
|
|
|
@ -288,7 +288,9 @@ const actions = {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const locale = settings.currentLocale.replace('-', '_')
|
||||||
ytpl(playlistId, {
|
ytpl(playlistId, {
|
||||||
|
hl: locale,
|
||||||
limit: 'Infinity',
|
limit: 'Infinity',
|
||||||
requestOptions: { agent }
|
requestOptions: { agent }
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
--logo-text: url("~../../_icons/textColorSmall.png");
|
--logo-text: url("~../../_icons/textColorSmall.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--primary-text-color: #EEEEEE;
|
--primary-text-color: #EEEEEE;
|
||||||
--secondary-text-color: #ddd;
|
--secondary-text-color: #ddd;
|
||||||
|
@ -89,6 +88,30 @@
|
||||||
--logo-text: url("~../../_icons/textColorSmall.png");
|
--logo-text: url("~../../_icons/textColorSmall.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dracula {
|
||||||
|
--primary-text-color: #F8F8F2;
|
||||||
|
--secondary-text-color: #c6cee6;
|
||||||
|
--tertiary-text-color: #e5e8f3;
|
||||||
|
--primary-input-color: rgba(0, 0, 0, 0.50);
|
||||||
|
--primary-shadow-color: rgba(0, 0, 0, 0.75);
|
||||||
|
--title-color: #BD93F9;
|
||||||
|
--bg-color: #282A36;
|
||||||
|
--link-color: var(--accent-color);
|
||||||
|
--link-visited-color: var(--accent-color-visited);
|
||||||
|
--favorite-icon-color: #F1FA8C;
|
||||||
|
--card-bg-color: #33353F;
|
||||||
|
--secondary-card-bg-color: #282A36;
|
||||||
|
--scrollbar-color: #44475A;
|
||||||
|
--scrollbar-color-hover: #3D4051;
|
||||||
|
--side-nav-color: #44475A;
|
||||||
|
--side-nav-hover-color: #57596B;
|
||||||
|
--side-nav-active-color: #3D4051;
|
||||||
|
--search-bar-color: #3E3F4A;
|
||||||
|
--instance-menu-color: var(--search-bar-color);
|
||||||
|
--logo-icon: url("~../../_icons/iconDraculaLightSmall.png");
|
||||||
|
--logo-text: url("~../../_icons/textDraculaLightSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
.mainRed {
|
.mainRed {
|
||||||
--primary-color: #f44336;
|
--primary-color: #f44336;
|
||||||
--primary-color-hover: #e53935;
|
--primary-color-hover: #e53935;
|
||||||
|
@ -233,6 +256,69 @@
|
||||||
--logo-text-bar-color: url("~../../_icons/textBlackSmall.png");
|
--logo-text-bar-color: url("~../../_icons/textBlackSmall.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mainDraculaCyan {
|
||||||
|
--primary-color: #8BE9FD;
|
||||||
|
--primary-color-hover: #97EBFD;
|
||||||
|
--primary-color-active: #7DD2E4;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaDarkSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaDarkSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainDraculaGreen {
|
||||||
|
--primary-color: #50FA7B;
|
||||||
|
--primary-color-hover: #62FB88;
|
||||||
|
--primary-color-active: #48E16F;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaDarkSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaDarkSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainDraculaOrange {
|
||||||
|
--primary-color: #FFB86C;
|
||||||
|
--primary-color-hover: #FFBF7B;
|
||||||
|
--primary-color-active: #E6A661;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaDarkSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaDarkSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainDraculaPink {
|
||||||
|
--primary-color: #FF79C6;
|
||||||
|
--primary-color-hover: #FF86CC;
|
||||||
|
--primary-color-active: #E66DB2;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaLightSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaLightSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainDraculaPurple {
|
||||||
|
--primary-color: #BD93F9;
|
||||||
|
--primary-color-hover: #C49EFA;
|
||||||
|
--primary-color-active: #AA84E0;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaLightSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaLightSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainDraculaRed {
|
||||||
|
--primary-color: #FF5555;
|
||||||
|
--primary-color-hover: #FF6666;
|
||||||
|
--primary-color-active: #E64D4D;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaLightSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaLightSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainDraculaYellow {
|
||||||
|
--primary-color: #F1FA8C;
|
||||||
|
--primary-color-hover: #F2FB98;
|
||||||
|
--primary-color-active: #D9E17E;
|
||||||
|
--text-with-main-color: #282A36;
|
||||||
|
--logo-icon-bar-color: url("~../../_icons/iconDraculaDarkSmall.png");
|
||||||
|
--logo-text-bar-color: url("~../../_icons/textDraculaDarkSmall.png");
|
||||||
|
}
|
||||||
|
|
||||||
.secRed {
|
.secRed {
|
||||||
--accent-color: #f44336;
|
--accent-color: #f44336;
|
||||||
--accent-color-hover: #e53935;
|
--accent-color-hover: #e53935;
|
||||||
|
@ -441,6 +527,97 @@
|
||||||
--accent-color-opacity4: rgba(255,87,34,0.24);
|
--accent-color-opacity4: rgba(255,87,34,0.24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secDraculaCyan {
|
||||||
|
--accent-color: #8BE9FD;
|
||||||
|
--accent-color-hover: #97EBFD;
|
||||||
|
--accent-color-active: #7DD2E4;
|
||||||
|
--accent-color-light: #A2EDFD;
|
||||||
|
--accent-color-visited: #6FBACA;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secDraculaGreen {
|
||||||
|
--accent-color: #50FA7B;
|
||||||
|
--accent-color-hover: #62FB88;
|
||||||
|
--accent-color-active: #48E16F;
|
||||||
|
--accent-color-light: #73FB95;
|
||||||
|
--accent-color-visited: #40C862;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secDraculaOrange {
|
||||||
|
--accent-color: #FFB86C;
|
||||||
|
--accent-color-hover: #FFBF7B;
|
||||||
|
--accent-color-active: #E6A661;
|
||||||
|
--accent-color-light: #FFC689;
|
||||||
|
--accent-color-visited: #CC9356;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secDraculaPink {
|
||||||
|
--accent-color: #FF79C6;
|
||||||
|
--accent-color-hover: #FF86CC;
|
||||||
|
--accent-color-active: #E66DB2;
|
||||||
|
--accent-color-light: #FF94D1;
|
||||||
|
--accent-color-visited: #CC619E;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secDraculaPurple {
|
||||||
|
--accent-color: #BD93F9;
|
||||||
|
--accent-color-hover: #C49EFA;
|
||||||
|
--accent-color-active: #AA84E0;
|
||||||
|
--accent-color-light: #CAA9FA;
|
||||||
|
--accent-color-visited: #9776C7;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secDraculaRed {
|
||||||
|
--accent-color: #FF5555;
|
||||||
|
--accent-color-hover: #FF6666;
|
||||||
|
--accent-color-active: #E64D4D;
|
||||||
|
--accent-color-light: #FF7777;
|
||||||
|
--accent-color-visited: #CC4444;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secDraculaYellow {
|
||||||
|
--accent-color: #F1FA8C;
|
||||||
|
--accent-color-hover: #F2FB98;
|
||||||
|
--accent-color-active: #D9E17E;
|
||||||
|
--accent-color-light: #F4FBA3;
|
||||||
|
--accent-color-visited: #C1C870;
|
||||||
|
--text-with-accent-color: #212121;
|
||||||
|
--accent-color-opacity1: rgba(98,114,164,0.04);
|
||||||
|
--accent-color-opacity2: rgba(98,114,164,0.12);
|
||||||
|
--accent-color-opacity3: rgba(98,114,164,0.16);
|
||||||
|
--accent-color-opacity4: rgba(98,114,164,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
|
@ -456,7 +633,7 @@ body {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightAligned: {
|
.rightAligned {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -437,7 +437,7 @@ body.vjs-full-window {
|
||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-icon-fullwindow-enter, .video-js .vjs-fullwindow-control .vjs-icon-placeholder {
|
.vjs-icon-fullwindow-enter, .vjs-icon-theatre-inactive, .video-js .vjs-fullwindow-control .vjs-icon-placeholder {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
margin-top: 10px !important;
|
margin-top: 10px !important;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
@ -449,11 +449,11 @@ body.vjs-full-window {
|
||||||
content: url(assets/img/open_fullwindow.svg);
|
content: url(assets/img/open_fullwindow.svg);
|
||||||
}
|
}
|
||||||
/* Hide button in full screen mode */
|
/* Hide button in full screen mode */
|
||||||
.vjs--full-screen-enabled .vjs-button-fullwindow {
|
.vjs--full-screen-enabled .vjs-button-fullwindow, .vjs--full-screen-enabled .vjs-button-theatre, .vjs-full-window .vjs-button-theatre {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-icon-fullwindow-exit, .video-js.vjs-fullwindow .vjs-fullwindow-control .vjs-icon-placeholder {
|
.vjs-icon-fullwindow-exit, .vjs-icon-theatre-active, .video-js.vjs-fullwindow .vjs-fullwindow-control .vjs-icon-placeholder {
|
||||||
font-family: VideoJS;
|
font-family: VideoJS;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -469,6 +469,10 @@ body.vjs-full-window {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-icon-theatre-inactive, .vjs-icon-theatre-active {
|
||||||
|
margin-top: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.vjs-icon-loop-active {
|
.vjs-icon-loop-active {
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
@ -478,6 +482,20 @@ body.vjs-full-window {
|
||||||
/* filter: invert(1) drop-shadow(1px 0px 0px var(--primary-color)); */
|
/* filter: invert(1) drop-shadow(1px 0px 0px var(--primary-color)); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-icon-theatre-inactive:before {
|
||||||
|
content: url(assets/img/open_theatre.svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-icon-theatre-active:before {
|
||||||
|
content: url(assets/img/close_theatre.svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1350px) {
|
||||||
|
.videoPlayer .vjs-button-theatre {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loop-black:before {
|
.loop-black:before {
|
||||||
filter: brightness(0%);
|
filter: brightness(0%);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default Vue.extend({
|
||||||
{
|
{
|
||||||
icon: 'comment-dots',
|
icon: 'comment-dots',
|
||||||
title: this.$t('About.Chat on Matrix'),
|
title: this.$t('About.Chat on Matrix'),
|
||||||
content: `<a href="https://matrix.to/#/#freetube:matrix.org?via=matrix.org&via=privacytools.io&via=tchncs.de">#freetube:matrix.org</a><br>${this.$t('About.Please read the')} <a href="https://github.com/FreeTubeApp/FreeTube/wiki/Matrix-Channel-Info-&-Rules">${this.$t('About.room rules')}</a>`
|
content: `<a href="https://matrix.to/#/#freetube:matrix.org?via=matrix.org&via=privacytools.io&via=tchncs.de">#freetube:matrix.org</a><br>${this.$t('About.Please read the')} <a href="https://docs.freetubeapp.io/community/matrix/">${this.$t('About.room rules')}</a>`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'language',
|
icon: 'language',
|
||||||
|
|
|
@ -1,42 +1,34 @@
|
||||||
.card {
|
.card {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 85%;
|
width: 85%;
|
||||||
margin: 0 auto;
|
margin: 0 auto 20px;
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelBanner {
|
.channelBanner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
max-height: 200px;
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.defaultChannelBanner {
|
.defaultChannelBanner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
max-height: 200px;
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
height:200px;
|
height:200px;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
background-image: url("~images/defaultBanner.png");
|
background-image: url("images/defaultBanner.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelInfoContainer {
|
.channelInfoContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
|
||||||
margin-top: 300px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--card-bg-color);
|
background-color: var(--card-bg-color);
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelInfo {
|
.channelInfo {
|
||||||
height: 100px;
|
display: flex;
|
||||||
width: 85%;
|
flex-flow: row wrap;
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 30px;
|
justify-content: space-between;
|
||||||
left: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelThumbnail {
|
.channelThumbnail {
|
||||||
|
@ -50,24 +42,19 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
left: 120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelSubCount {
|
.channelSubCount {
|
||||||
position: absolute;
|
|
||||||
color: var(--tertiary-text-color);
|
color: var(--tertiary-text-color);
|
||||||
top: 50px;
|
top: 50px;
|
||||||
left: 120px;
|
left: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subscribeButton {
|
.subscribeButton {
|
||||||
position: absolute;
|
|
||||||
top: 50px;
|
|
||||||
right: 20px;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
|
align-self: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelSearch {
|
.channelSearch {
|
||||||
|
@ -80,28 +67,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelInfoTabs {
|
.channelInfoTabs {
|
||||||
position: absolute;
|
position: relative;
|
||||||
bottom: -16px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-top: -16px;
|
||||||
|
margin-bottom: -13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
-webkit-transition: background 0.2s ease-out;
|
color: var(--tertiary-text-color);
|
||||||
-moz-transition: background 0.2s ease-out;
|
}
|
||||||
-o-transition: background 0.2s ease-out;
|
|
||||||
transition: background 0.2s ease-out;
|
.selectedTab {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border-bottom: 3px solid var(--primary-color);
|
||||||
|
margin-bottom: -3px;
|
||||||
|
font-weight: bold;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:hover {
|
.tab:hover {
|
||||||
background-color: var(--side-nav-hover-color);
|
font-weight: bold;
|
||||||
-moz-transition: background 0.2s ease-in;
|
|
||||||
-o-transition: background 0.2s ease-in;
|
|
||||||
transition: background 0.2s ease-in;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.aboutTab {
|
.aboutTab {
|
||||||
|
@ -118,6 +107,10 @@
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channelSearch {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.elementList {
|
.elementList {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
@ -139,3 +132,14 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thumbnailContainer {
|
||||||
|
display: flex
|
||||||
|
}
|
||||||
|
|
||||||
|
.channelLineContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
|
@ -260,6 +260,14 @@ export default Vue.extend({
|
||||||
this.thumbnailUrl = response.authorThumbnails[2].url
|
this.thumbnailUrl = response.authorThumbnails[2].url
|
||||||
this.channelDescription = autolinker.link(response.description)
|
this.channelDescription = autolinker.link(response.description)
|
||||||
this.relatedChannels = response.relatedChannels.items
|
this.relatedChannels = response.relatedChannels.items
|
||||||
|
this.relatedChannels.forEach(relatedChannel => {
|
||||||
|
relatedChannel.authorThumbnails.map(thumbnail => {
|
||||||
|
if (!thumbnail.url.includes('https')) {
|
||||||
|
thumbnail.url = `https:${thumbnail.url}`
|
||||||
|
}
|
||||||
|
return thumbnail
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
if (response.authorBanners !== null) {
|
if (response.authorBanners !== null) {
|
||||||
const bannerUrl = response.authorBanners[response.authorBanners.length - 1].url
|
const bannerUrl = response.authorBanners[response.authorBanners.length - 1].url
|
||||||
|
|
|
@ -19,18 +19,27 @@
|
||||||
v-else
|
v-else
|
||||||
class="defaultChannelBanner"
|
class="defaultChannelBanner"
|
||||||
>
|
>
|
||||||
<div class="channelInfoContainer">
|
<div
|
||||||
<div class="channelInfo">
|
class="channelInfoContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="channelInfo"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="thumbnailContainer"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
class="channelThumbnail"
|
class="channelThumbnail"
|
||||||
:src="thumbnailUrl"
|
:src="thumbnailUrl"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="channelLineContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="channelName"
|
class="channelName"
|
||||||
>
|
>
|
||||||
{{ channelName }}
|
{{ channelName }}
|
||||||
</span>
|
</span>
|
||||||
<br>
|
|
||||||
<span
|
<span
|
||||||
v-if="subCount !== null"
|
v-if="subCount !== null"
|
||||||
class="channelSubCount"
|
class="channelSubCount"
|
||||||
|
@ -40,6 +49,8 @@
|
||||||
<span v-else>{{ $t("Channel.Subscribers") }}</span>
|
<span v-else>{{ $t("Channel.Subscribers") }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ft-button
|
<ft-button
|
||||||
:label="subscribedText"
|
:label="subscribedText"
|
||||||
background-color="var(--primary-color)"
|
background-color="var(--primary-color)"
|
||||||
|
@ -47,23 +58,28 @@
|
||||||
class="subscribeButton"
|
class="subscribeButton"
|
||||||
@click="handleSubscription"
|
@click="handleSubscription"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ft-flex-box
|
<ft-flex-box
|
||||||
class="channelInfoTabs"
|
class="channelInfoTabs"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="tab"
|
class="tab"
|
||||||
|
:class="(currentTab==='videos')?'selectedTab':''"
|
||||||
@click="changeTab('videos')"
|
@click="changeTab('videos')"
|
||||||
>
|
>
|
||||||
{{ $t("Channel.Videos.Videos").toUpperCase() }}
|
{{ $t("Channel.Videos.Videos").toUpperCase() }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tab"
|
class="tab"
|
||||||
|
:class="(currentTab==='playlists')?'selectedTab':''"
|
||||||
@click="changeTab('playlists')"
|
@click="changeTab('playlists')"
|
||||||
>
|
>
|
||||||
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
|
{{ $t("Channel.Playlists.Playlists").toUpperCase() }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tab"
|
class="tab"
|
||||||
|
:class="(currentTab==='about')?'selectedTab':''"
|
||||||
@click="changeTab('about')"
|
@click="changeTab('about')"
|
||||||
>
|
>
|
||||||
{{ $t("Channel.About.About").toUpperCase() }}
|
{{ $t("Channel.About.About").toUpperCase() }}
|
||||||
|
|
|
@ -5,11 +5,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatingTopButton {
|
.floatingTopButton {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 70px;
|
top: 70px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 350px) {
|
||||||
|
.floatingTopButton {
|
||||||
|
position: absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
@media only screen and (max-width: 680px) {
|
||||||
.card {
|
.card {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
hr {
|
||||||
|
height: 2px;
|
||||||
|
width: 85%;
|
||||||
|
margin: 0 auto;
|
||||||
|
border: 0;
|
||||||
|
background-color: var(--scrollbar-color-hover);
|
||||||
|
}
|
|
@ -1,14 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<general-settings />
|
<general-settings />
|
||||||
|
<hr>
|
||||||
<theme-settings />
|
<theme-settings />
|
||||||
|
<hr>
|
||||||
<player-settings />
|
<player-settings />
|
||||||
|
<hr>
|
||||||
<external-player-settings v-if="usingElectron" />
|
<external-player-settings v-if="usingElectron" />
|
||||||
|
<hr>
|
||||||
<subscription-settings />
|
<subscription-settings />
|
||||||
|
<hr>
|
||||||
<distraction-settings />
|
<distraction-settings />
|
||||||
|
<hr>
|
||||||
<privacy-settings />
|
<privacy-settings />
|
||||||
|
<hr>
|
||||||
<data-settings />
|
<data-settings />
|
||||||
|
<hr>
|
||||||
<proxy-settings />
|
<proxy-settings />
|
||||||
|
<hr>
|
||||||
<sponsor-block-settings />
|
<sponsor-block-settings />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -9,11 +9,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatingTopButton {
|
.floatingTopButton {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 70px;
|
top: 70px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 350px) {
|
||||||
|
.floatingTopButton {
|
||||||
|
position: absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
@media only screen and (max-width: 680px) {
|
||||||
.card {
|
.card {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
|
@ -5,11 +5,46 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatingTopButton {
|
.floatingTopButton {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 70px;
|
top: 70px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trendingInfoTabs {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
margin-top: -3px;
|
||||||
|
color: var(--tertiary-text-color);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedTab {
|
||||||
|
border-bottom: 3px solid var(--primary-color);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
font-weight: bold;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 350px) {
|
||||||
|
.floatingTopButton {
|
||||||
|
position: absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 680px) {
|
@media only screen and (max-width: 680px) {
|
||||||
.card {
|
.card {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
|
@ -4,7 +4,9 @@ import FtCard from '../../components/ft-card/ft-card.vue'
|
||||||
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
||||||
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
||||||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||||
|
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
||||||
|
|
||||||
|
import $ from 'jquery'
|
||||||
import ytrend from 'yt-trending-scraper'
|
import ytrend from 'yt-trending-scraper'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
@ -13,12 +15,20 @@ export default Vue.extend({
|
||||||
'ft-card': FtCard,
|
'ft-card': FtCard,
|
||||||
'ft-loader': FtLoader,
|
'ft-loader': FtLoader,
|
||||||
'ft-element-list': FtElementList,
|
'ft-element-list': FtElementList,
|
||||||
'ft-icon-button': FtIconButton
|
'ft-icon-button': FtIconButton,
|
||||||
|
'ft-flex-box': FtFlexBox
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
shownResults: []
|
shownResults: [],
|
||||||
|
currentTab: 'default',
|
||||||
|
tabInfoValues: [
|
||||||
|
'default',
|
||||||
|
'music',
|
||||||
|
'gaming',
|
||||||
|
'movies'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -42,13 +52,49 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
if (this.trendingCache && this.trendingCache.length > 0) {
|
if (this.trendingCache[this.currentTab] && this.trendingCache[this.currentTab].length > 0) {
|
||||||
this.shownResults = this.trendingCache
|
this.shownResults = this.trendingCache
|
||||||
} else {
|
} else {
|
||||||
this.getTrendingInfo()
|
this.getTrendingInfo()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
changeTab: function (tab, event) {
|
||||||
|
if (event instanceof KeyboardEvent) {
|
||||||
|
if (event.key === 'Tab') {
|
||||||
|
return
|
||||||
|
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
|
||||||
|
// navigate trending tabs with arrow keys
|
||||||
|
const index = this.tabInfoValues.indexOf(tab)
|
||||||
|
// tabs wrap around from leftmost to rightmost, and vice versa
|
||||||
|
tab = (event.key === 'ArrowLeft')
|
||||||
|
? this.tabInfoValues[(index > 0 ? index : this.tabInfoValues.length) - 1]
|
||||||
|
: this.tabInfoValues[(index + 1) % this.tabInfoValues.length]
|
||||||
|
|
||||||
|
const tabNode = $(`#${tab}Tab`)
|
||||||
|
event.target.setAttribute('tabindex', '-1')
|
||||||
|
tabNode.attr('tabindex', '0')
|
||||||
|
tabNode[0].focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
if (event.key !== 'Enter' && event.key !== ' ') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const currentTabNode = $('.trendingInfoTabs > .tab[aria-selected="true"]')
|
||||||
|
const newTabNode = $(`#${tab}Tab`)
|
||||||
|
|
||||||
|
// switch selectability from currently focused tab to new tab
|
||||||
|
$('.trendingInfoTabs > .tab[tabindex="0"]').attr('tabindex', '-1')
|
||||||
|
newTabNode.attr('tabindex', '0')
|
||||||
|
|
||||||
|
currentTabNode.attr('aria-selected', 'false')
|
||||||
|
newTabNode.attr('aria-selected', 'true')
|
||||||
|
this.currentTab = tab
|
||||||
|
this.getTrendingInfo()
|
||||||
|
},
|
||||||
|
|
||||||
getTrendingInfo () {
|
getTrendingInfo () {
|
||||||
if (!this.usingElectron) {
|
if (!this.usingElectron) {
|
||||||
this.getVideoInformationInvidious()
|
this.getVideoInformationInvidious()
|
||||||
|
@ -70,7 +116,7 @@ export default Vue.extend({
|
||||||
console.log('getting local trending')
|
console.log('getting local trending')
|
||||||
const param = {
|
const param = {
|
||||||
parseCreatorOnRise: false,
|
parseCreatorOnRise: false,
|
||||||
page: 'default',
|
page: this.currentTab,
|
||||||
geoLocation: this.region
|
geoLocation: this.region
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +127,9 @@ export default Vue.extend({
|
||||||
|
|
||||||
this.shownResults = returnData
|
this.shownResults = returnData
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
this.$store.commit('setTrendingCache', this.shownResults)
|
this.$store.commit('setTrendingCache', this.shownResults, this.currentTab)
|
||||||
|
}).then(() => {
|
||||||
|
document.querySelector(`#${this.currentTab}Tab`).focus()
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||||
|
@ -112,6 +160,10 @@ export default Vue.extend({
|
||||||
params: { region: this.region }
|
params: { region: this.region }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.currentTab !== 'default') {
|
||||||
|
trendingPayload.params.type = this.currentTab.charAt(0).toUpperCase() + this.currentTab.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
this.invidiousAPICall(trendingPayload).then((result) => {
|
this.invidiousAPICall(trendingPayload).then((result) => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return
|
return
|
||||||
|
@ -125,7 +177,9 @@ export default Vue.extend({
|
||||||
|
|
||||||
this.shownResults = returnData
|
this.shownResults = returnData
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
this.$store.commit('setTrendingCache', this.shownResults)
|
this.$store.commit('setTrendingCache', this.shownResults, this.trendingCache)
|
||||||
|
}).then(() => {
|
||||||
|
document.querySelector(`#${this.currentTab}Tab`).focus()
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||||
|
|
|
@ -8,8 +8,68 @@
|
||||||
v-else
|
v-else
|
||||||
class="card"
|
class="card"
|
||||||
>
|
>
|
||||||
<h3>{{ $t("Trending") }}</h3>
|
<h3>{{ $t("Trending.Trending") }}</h3>
|
||||||
|
<ft-flex-box
|
||||||
|
class="trendingInfoTabs"
|
||||||
|
role="tablist"
|
||||||
|
:aria-label="$t('Trending.Trending Tabs')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="defaultTab"
|
||||||
|
class="tab"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="true"
|
||||||
|
aria-controls="trendingPanel"
|
||||||
|
tabindex="0"
|
||||||
|
:class="(currentTab=='default')?'selectedTab':''"
|
||||||
|
@click="changeTab('default')"
|
||||||
|
@keydown="changeTab('default', $event)"
|
||||||
|
>
|
||||||
|
{{ $t("Trending.Default").toUpperCase() }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="musicTab"
|
||||||
|
class="tab"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
aria-controls="trendingPanel"
|
||||||
|
tabindex="-1"
|
||||||
|
:class="(currentTab=='music')?'selectedTab':''"
|
||||||
|
@click="changeTab('music')"
|
||||||
|
@keydown="changeTab('music', $event)"
|
||||||
|
>
|
||||||
|
{{ $t("Trending.Music").toUpperCase() }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="gamingTab"
|
||||||
|
class="tab"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
aria-controls="trendingPanel"
|
||||||
|
tabindex="-1"
|
||||||
|
:class="(currentTab=='gaming')?'selectedTab':''"
|
||||||
|
@click="changeTab('gaming')"
|
||||||
|
@keydown="changeTab('gaming', $event)"
|
||||||
|
>
|
||||||
|
{{ $t("Trending.Gaming").toUpperCase() }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="moviesTab"
|
||||||
|
class="tab"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
aria-controls="trendingPanel"
|
||||||
|
tabindex="-1"
|
||||||
|
:class="(currentTab=='movies')?'selectedTab':''"
|
||||||
|
@click="changeTab('movies')"
|
||||||
|
@keydown="changeTab('movies', $event)"
|
||||||
|
>
|
||||||
|
{{ $t("Trending.Movies").toUpperCase() }}
|
||||||
|
</div>
|
||||||
|
</ft-flex-box>
|
||||||
<ft-element-list
|
<ft-element-list
|
||||||
|
id="trendingPanel"
|
||||||
|
role="tabpanel"
|
||||||
:data="shownResults"
|
:data="shownResults"
|
||||||
/>
|
/>
|
||||||
</ft-card>
|
</ft-card>
|
||||||
|
|
|
@ -960,13 +960,13 @@ export default Vue.extend({
|
||||||
if (this.removeVideoMetaFiles) {
|
if (this.removeVideoMetaFiles) {
|
||||||
const userData = await this.getUserDataPath()
|
const userData = await this.getUserDataPath()
|
||||||
if (this.isDev) {
|
if (this.isDev) {
|
||||||
const dashFileLocation = `dashFiles/${this.videoId}.xml`
|
const dashFileLocation = `static/dashFiles/${this.videoId}.xml`
|
||||||
const vttFileLocation = `storyboards/${this.videoId}.vtt`
|
const vttFileLocation = `static/storyboards/${this.videoId}.vtt`
|
||||||
// only delete the file it actually exists
|
// only delete the file it actually exists
|
||||||
if (fs.existsSync('dashFiles/') && fs.existsSync(dashFileLocation)) {
|
if (fs.existsSync('static/dashFiles/') && fs.existsSync(dashFileLocation)) {
|
||||||
fs.rmSync(dashFileLocation)
|
fs.rmSync(dashFileLocation)
|
||||||
}
|
}
|
||||||
if (fs.existsSync('storyboards/') && fs.existsSync(vttFileLocation)) {
|
if (fs.existsSync('static/storyboards/') && fs.existsSync(vttFileLocation)) {
|
||||||
fs.rmSync(vttFileLocation)
|
fs.rmSync(vttFileLocation)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1014,9 +1014,10 @@ export default Vue.extend({
|
||||||
fs.mkdirSync('static/dashFiles/')
|
fs.mkdirSync('static/dashFiles/')
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.rm(fileLocation, () => {
|
if (fs.existsSync(fileLocation)) {
|
||||||
|
fs.rmSync(fileLocation)
|
||||||
|
}
|
||||||
fs.writeFileSync(fileLocation, xmlData)
|
fs.writeFileSync(fileLocation, xmlData)
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
fileLocation = `${userData}/dashFiles/${this.videoId}.xml`
|
fileLocation = `${userData}/dashFiles/${this.videoId}.xml`
|
||||||
uriSchema = `file://${fileLocation}`
|
uriSchema = `file://${fileLocation}`
|
||||||
|
|
|
@ -93,7 +93,6 @@
|
||||||
:video-thumbnail="thumbnail"
|
:video-thumbnail="thumbnail"
|
||||||
class="watchVideo"
|
class="watchVideo"
|
||||||
:class="{ theatreWatchVideo: useTheatreMode }"
|
:class="{ theatreWatchVideo: useTheatreMode }"
|
||||||
@theatre-mode="toggleTheatreMode"
|
|
||||||
@pause-player="pausePlayer"
|
@pause-player="pausePlayer"
|
||||||
/>
|
/>
|
||||||
<watch-video-description
|
<watch-video-description
|
||||||
|
|
|
@ -76,7 +76,13 @@ Subscriptions:
|
||||||
Load More Videos: حمّل المزيد من الفيديوهات
|
Load More Videos: حمّل المزيد من الفيديوهات
|
||||||
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: لدى
|
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: لدى
|
||||||
هذا الملف الشخصي عدد كبير من الاشتراكات. يتم فرض وضع RSS لتجنب تقييد الوصول للاشتراكات
|
هذا الملف الشخصي عدد كبير من الاشتراكات. يتم فرض وضع RSS لتجنب تقييد الوصول للاشتراكات
|
||||||
|
Trending:
|
||||||
Trending: 'المحتوى الرائج'
|
Trending: 'المحتوى الرائج'
|
||||||
|
Trending Tabs: علامات التبويب الشائعة
|
||||||
|
Movies: أفلام
|
||||||
|
Gaming: الالعاب
|
||||||
|
Music: الموسيقى
|
||||||
|
Default: الإفتراضي
|
||||||
Most Popular: 'الأكثر شعبية'
|
Most Popular: 'الأكثر شعبية'
|
||||||
Playlists: 'قوائم التشغيل'
|
Playlists: 'قوائم التشغيل'
|
||||||
User Playlists:
|
User Playlists:
|
||||||
|
@ -130,6 +136,11 @@ Settings:
|
||||||
The currently set default instance is $: المثيل الافتراضي المحدد حاليا هو $
|
The currently set default instance is $: المثيل الافتراضي المحدد حاليا هو $
|
||||||
Current Invidious Instance: المثيل الحالي Invidious
|
Current Invidious Instance: المثيل الحالي Invidious
|
||||||
Clear Default Instance: مسح المثيل الافتراضي
|
Clear Default Instance: مسح المثيل الافتراضي
|
||||||
|
External Link Handling:
|
||||||
|
No Action: لا يوجد إجراء
|
||||||
|
Ask Before Opening Link: اسأل قبل فتح الرابط
|
||||||
|
Open Link: افتح الرابط
|
||||||
|
External Link Handling: معالجة الارتباط الخارجي
|
||||||
Theme Settings:
|
Theme Settings:
|
||||||
Theme Settings: 'إعدادات السِمة'
|
Theme Settings: 'إعدادات السِمة'
|
||||||
Match Top Bar with Main Color: 'طابق الشريط العلوي مع اللون الأساسي'
|
Match Top Bar with Main Color: 'طابق الشريط العلوي مع اللون الأساسي'
|
||||||
|
@ -138,6 +149,7 @@ Settings:
|
||||||
Black: 'أسود'
|
Black: 'أسود'
|
||||||
Dark: 'داكن'
|
Dark: 'داكن'
|
||||||
Light: 'فاتح'
|
Light: 'فاتح'
|
||||||
|
Dracula: 'دراكولا'
|
||||||
Main Color Theme:
|
Main Color Theme:
|
||||||
Main Color Theme: 'لون السِمة الأساسي'
|
Main Color Theme: 'لون السِمة الأساسي'
|
||||||
Red: 'أحمر'
|
Red: 'أحمر'
|
||||||
|
@ -156,6 +168,13 @@ Settings:
|
||||||
Amber: 'كهرماني'
|
Amber: 'كهرماني'
|
||||||
Orange: 'برتقالي'
|
Orange: 'برتقالي'
|
||||||
Deep Orange: 'برتقالي داكن'
|
Deep Orange: 'برتقالي داكن'
|
||||||
|
Dracula Cyan: 'دراكولا سماوي'
|
||||||
|
Dracula Green: 'دراكولا أخضر'
|
||||||
|
Dracula Orange: 'دراكولا برتقالي'
|
||||||
|
Dracula Pink: 'دراكولا وردي'
|
||||||
|
Dracula Purple: 'دراكولا إرجواني'
|
||||||
|
Dracula Red: 'دراكولا أحمر'
|
||||||
|
Dracula Yellow: 'دراكولا أصفر'
|
||||||
Secondary Color Theme: 'لون السِمة الثانوي'
|
Secondary Color Theme: 'لون السِمة الثانوي'
|
||||||
#* Main Color Theme
|
#* Main Color Theme
|
||||||
UI Scale: مقياس واجهة المستخدم
|
UI Scale: مقياس واجهة المستخدم
|
||||||
|
@ -637,6 +656,7 @@ Comments:
|
||||||
Newest first: الأحدث أولاً
|
Newest first: الأحدث أولاً
|
||||||
Top comments: أهم التعليقات
|
Top comments: أهم التعليقات
|
||||||
Sort by: الترتيب حسب
|
Sort by: الترتيب حسب
|
||||||
|
Show More Replies: إظهار المزيد من الردود
|
||||||
Up Next: 'التالي'
|
Up Next: 'التالي'
|
||||||
|
|
||||||
# Toast Messages
|
# Toast Messages
|
||||||
|
@ -679,6 +699,9 @@ Tooltips:
|
||||||
التراجع.
|
التراجع.
|
||||||
Region for Trending: الانتشار المحلي (Trend) يسمح لك بأن تشاهد الفيديوهات الأكثر
|
Region for Trending: الانتشار المحلي (Trend) يسمح لك بأن تشاهد الفيديوهات الأكثر
|
||||||
انتشارا حسب الدولة. ليست كل الدول المعروضة في هذه القائمة مدعومة من طرف يوتيوب.
|
انتشارا حسب الدولة. ليست كل الدول المعروضة في هذه القائمة مدعومة من طرف يوتيوب.
|
||||||
|
External Link Handling: "اختر السلوك الافتراضي عند النقر فوق رابط، لا يمكن فتحه\
|
||||||
|
\ في FreeTube.\nبشكل افتراضي، سيفتح FreeTube الرابط الذي تم النقر عليه في المتصفح\
|
||||||
|
\ الافتراضي.\n"
|
||||||
Player Settings:
|
Player Settings:
|
||||||
Proxy Videos Through Invidious: سيتم الاتصال ب Invidious لتقديم مقاطع الفيديو
|
Proxy Videos Through Invidious: سيتم الاتصال ب Invidious لتقديم مقاطع الفيديو
|
||||||
بدلاً من إجراء اتصال مباشر مع يوتيوب. يلغي تفضيل الواجهة البرمجية.
|
بدلاً من إجراء اتصال مباشر مع يوتيوب. يلغي تفضيل الواجهة البرمجية.
|
||||||
|
@ -722,3 +745,8 @@ Open New Window: افتح نافذة جديدة
|
||||||
Default Invidious instance has been cleared: تم مسح مثيل Invidious الافتراضي
|
Default Invidious instance has been cleared: تم مسح مثيل Invidious الافتراضي
|
||||||
Default Invidious instance has been set to $: تم تعيين المثيل الافتراضي Invidious
|
Default Invidious instance has been set to $: تم تعيين المثيل الافتراضي Invidious
|
||||||
إلى $
|
إلى $
|
||||||
|
Search Bar:
|
||||||
|
Clear Input: مسح المدخلات
|
||||||
|
External link opening has been disabled in the general settings: تم تعطيل فتح الارتباط
|
||||||
|
الخارجي في الإعدادات العامة
|
||||||
|
Are you sure you want to open this link?: هل أنت متأكد أنك تريد فتح هذا الرابط؟
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue