Compare commits

...

7 Commits

Author SHA1 Message Date
ChunkyProgrammer 977ef1c2e9 Merge branch 'development' into add-command-line-arg-to-search 2024-04-30 21:09:00 -04:00
Jason 4bb53f780c
Improve search filter & modal accessibility (#3975)
* Add title and aria-pressed attributes to filter button

* Add radio button hover and focus styling with accent-color

* Programmatically apply focus after clicking to open filter

* Implement filter-button-visible icon styling

* Use more standard means to grab grandchild ref

* Make search filters heading not selectable

* Additional icon and radio button styling updates

* Implement modal focus management with portal-vue

For the importance of modal focus management, see: https://accessibility.huit.harvard.edu/technique-accessible-modal-dialogs

* Add themes for handling ft-prompt styling

This is a portal-compatible way of affecting prompt styling, as ':deep' does not work for portals.

* Require title in ft-prompt to ensure accessible properties are functional

* Set initial radio button value on load

* Update App.vue

* Fix z-index and modal error issue

* Remove unnecessary additionl focus restoration logic in add-video-prompt
2024-05-01 08:38:32 +08:00
absidue a5f3410378
Filter settings sections at build time instead of run time (#5040) 2024-04-30 06:58:14 -04:00
absidue 4c511869fc
Lazy load thumbnails for hidden channels (#5041) 2024-04-30 10:39:45 +00:00
Elias Groß 2f4c757741
Translated using Weblate (German)
Currently translated at 100.0% (830 of 830 strings)

Co-authored-by: Elias Groß <eliasgross378@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/de/
Translation: FreeTube/Translations
2024-04-30 11:07:12 +02:00
ChunkyProgrammer 9b990bc5a8
Choose best quality thumbnail for Invidious comment author (#4917) 2024-04-30 08:27:45 +08:00
ChunkyProgrammer 7505a5bb8c
Fetch invidious instances from file then use api (#5011)
* fetch invidious instances from file then use api

* remove unused return statements
2024-04-30 07:32:58 +08:00
30 changed files with 404 additions and 276 deletions

View File

@ -64,6 +64,7 @@
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"marked": "^12.0.2", "marked": "^12.0.2",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"portal-vue": "^2.1.7",
"process": "^0.11.10", "process": "^0.11.10",
"swiper": "^11.1.1", "swiper": "^11.1.1",
"video.js": "7.21.5", "video.js": "7.21.5",

View File

@ -11,6 +11,7 @@ import FtToast from './components/ft-toast/ft-toast.vue'
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue' import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
import FtPlaylistAddVideoPrompt from './components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.vue' import FtPlaylistAddVideoPrompt from './components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.vue'
import FtCreatePlaylistPrompt from './components/ft-create-playlist-prompt/ft-create-playlist-prompt.vue' import FtCreatePlaylistPrompt from './components/ft-create-playlist-prompt/ft-create-playlist-prompt.vue'
import FtSearchFilters from './components/ft-search-filters/ft-search-filters.vue'
import { marked } from 'marked' import { marked } from 'marked'
import { IpcChannels } from '../constants' import { IpcChannels } from '../constants'
import packageDetails from '../../package.json' import packageDetails from '../../package.json'
@ -34,6 +35,7 @@ export default defineComponent({
FtProgressBar, FtProgressBar,
FtPlaylistAddVideoPrompt, FtPlaylistAddVideoPrompt,
FtCreatePlaylistPrompt, FtCreatePlaylistPrompt,
FtSearchFilters
}, },
data: function () { data: function () {
return { return {
@ -46,6 +48,7 @@ export default defineComponent({
latestBlogUrl: '', latestBlogUrl: '',
updateChangelog: '', updateChangelog: '',
changeLogTitle: '', changeLogTitle: '',
isPromptOpen: false,
lastExternalLinkToBeOpened: '', lastExternalLinkToBeOpened: '',
showExternalLinkOpeningPrompt: false, showExternalLinkOpeningPrompt: false,
externalLinkOpeningPromptValues: [ externalLinkOpeningPromptValues: [
@ -77,6 +80,9 @@ export default defineComponent({
showCreatePlaylistPrompt: function () { showCreatePlaylistPrompt: function () {
return this.$store.getters.getShowCreatePlaylistPrompt return this.$store.getters.getShowCreatePlaylistPrompt
}, },
showSearchFilters: function () {
return this.$store.getters.getShowSearchFilters
},
windowTitle: function () { windowTitle: function () {
const routePath = this.$route.path const routePath = this.$route.path
if (!routePath.startsWith('/channel/') && !routePath.startsWith('/watch/') && !routePath.startsWith('/hashtag/')) { if (!routePath.startsWith('/channel/') && !routePath.startsWith('/watch/') && !routePath.startsWith('/hashtag/')) {
@ -143,12 +149,6 @@ export default defineComponent({
secColor: 'checkThemeSettings', secColor: 'checkThemeSettings',
locale: 'setLocale', locale: 'setLocale',
$route () {
// react to route changes...
// Hide top nav filter panel on page change
this.$refs.topNav?.hideFilters()
}
}, },
created () { created () {
this.checkThemeSettings() this.checkThemeSettings()
@ -159,11 +159,17 @@ export default defineComponent({
this.grabUserSettings().then(async () => { this.grabUserSettings().then(async () => {
this.checkThemeSettings() this.checkThemeSettings()
await this.fetchInvidiousInstances() await this.fetchInvidiousInstancesFromFile()
if (this.defaultInvidiousInstance === '') { if (this.defaultInvidiousInstance === '') {
await this.setRandomCurrentInvidiousInstance() await this.setRandomCurrentInvidiousInstance()
} }
this.fetchInvidiousInstances().then(e => {
if (this.defaultInvidiousInstance === '') {
this.setRandomCurrentInvidiousInstance()
}
})
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()
@ -295,6 +301,10 @@ export default defineComponent({
this.showBlogBanner = false this.showBlogBanner = false
}, },
handlePromptPortalUpdate: function(newVal) {
this.isPromptOpen = newVal
},
openDownloadsPage: function () { openDownloadsPage: function () {
const url = 'https://freetubeapp.io#download' const url = 'https://freetubeapp.io#download'
openExternalLink(url) openExternalLink(url)
@ -538,13 +548,14 @@ export default defineComponent({
'getYoutubeUrlInfo', 'getYoutubeUrlInfo',
'getExternalPlayerCmdArgumentsData', 'getExternalPlayerCmdArgumentsData',
'fetchInvidiousInstances', 'fetchInvidiousInstances',
'fetchInvidiousInstancesFromFile',
'setRandomCurrentInvidiousInstance', 'setRandomCurrentInvidiousInstance',
'setupListenersToSyncWindows', 'setupListenersToSyncWindows',
'updateBaseTheme', 'updateBaseTheme',
'updateMainColor', 'updateMainColor',
'updateSecColor', 'updateSecColor',
'showOutlines', 'showOutlines',
'hideOutlines' 'hideOutlines',
]) ])
} }
}) })

View File

@ -8,11 +8,63 @@
isLocaleRightToLeft: isLocaleRightToLeft isLocaleRightToLeft: isLocaleRightToLeft
}" }"
> >
<top-nav ref="topNav" /> <portal-target
<side-nav ref="sideNav" /> name="promptPortal"
@change="handlePromptPortalUpdate"
/>
<ft-prompt
v-if="showReleaseNotes"
:label="changeLogTitle"
@click="showReleaseNotes = !showReleaseNotes"
>
<span
class="changeLogText"
v-html="updateChangelog"
/>
<ft-flex-box>
<ft-button
:label="$t('Download From Site')"
@click="openDownloadsPage"
/>
<ft-button
:label="$t('Close')"
@click="showReleaseNotes = !showReleaseNotes"
/>
</ft-flex-box>
</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-search-filters
v-if="showSearchFilters"
/>
<ft-playlist-add-video-prompt
v-if="showAddToPlaylistPrompt"
/>
<ft-create-playlist-prompt
v-if="showCreatePlaylistPrompt"
/>
<ft-toast />
<ft-progress-bar
v-if="showProgressBar"
/>
<top-nav
ref="topNav"
:inert="isPromptOpen"
/>
<side-nav
ref="sideNav"
:inert="isPromptOpen"
/>
<ft-flex-box <ft-flex-box
class="flexBox routerView" class="flexBox routerView"
role="main" role="main"
:inert="isPromptOpen"
> >
<div <div
v-if="showUpdatesBanner || showBlogBanner" v-if="showUpdatesBanner || showBlogBanner"
@ -43,48 +95,9 @@
ref="router" ref="router"
class="routerView" class="routerView"
/> />
<!-- </keep-alive> --> <!-- </keep-alive> -->
</transition> </transition>
</ft-flex-box> </ft-flex-box>
<ft-prompt
v-if="showReleaseNotes"
:label="changeLogTitle"
@click="showReleaseNotes = !showReleaseNotes"
>
<span
class="changeLogText"
v-html="updateChangelog"
/>
<ft-flex-box>
<ft-button
:label="$t('Download From Site')"
@click="openDownloadsPage"
/>
<ft-button
:label="$t('Close')"
@click="showReleaseNotes = !showReleaseNotes"
/>
</ft-flex-box>
</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-playlist-add-video-prompt
v-if="showAddToPlaylistPrompt"
/>
<ft-create-playlist-prompt
v-if="showCreatePlaylistPrompt"
/>
<ft-toast />
<ft-progress-bar
v-if="showProgressBar"
/>
</div> </div>
</template> </template>

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue' import { defineComponent, nextTick } from 'vue'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtPrompt from '../ft-prompt/ft-prompt.vue' import FtPrompt from '../ft-prompt/ft-prompt.vue'
@ -24,6 +24,9 @@ export default defineComponent({
} }
}, },
computed: { computed: {
title: function () {
return this.$t('User Playlists.CreatePlaylistPrompt.New Playlist Name')
},
allPlaylists: function () { allPlaylists: function () {
return this.$store.getters.getAllPlaylists return this.$store.getters.getAllPlaylists
}, },
@ -34,7 +37,7 @@ export default defineComponent({
mounted: function () { mounted: function () {
this.playlistName = this.newPlaylistVideoObject.title this.playlistName = this.newPlaylistVideoObject.title
// Faster to input required playlist name // Faster to input required playlist name
this.$refs.playlistNameInput.focus() nextTick(() => this.$refs.playlistNameInput.focus())
}, },
methods: { methods: {
createNewPlaylist: function () { createNewPlaylist: function () {

View File

@ -1,9 +1,10 @@
<template> <template>
<ft-prompt <ft-prompt
:label="title"
@click="hideCreatePlaylistPrompt" @click="hideCreatePlaylistPrompt"
> >
<h2 class="center"> <h2 class="center">
{{ $t('User Playlists.CreatePlaylistPrompt.New Playlist Name') }} {{ title }}
</h2> </h2>
<ft-flex-box> <ft-flex-box>
<ft-input <ft-input

View File

@ -40,7 +40,6 @@
.tag-icon { .tag-icon {
border-radius: 50%; border-radius: 50%;
block-size: 24px;
vertical-align: middle; vertical-align: middle;
} }

View File

@ -37,6 +37,9 @@
:src="tag.icon" :src="tag.icon"
alt="" alt=""
class="tag-icon" class="tag-icon"
height="24"
width="24"
loading="lazy"
> >
</router-link> </router-link>
<span>{{ (tag.preferredName) ? tag.preferredName : tag.name }}</span> <span>{{ (tag.preferredName) ? tag.preferredName : tag.name }}</span>

View File

@ -6,16 +6,6 @@
text-align: center; text-align: center;
} }
/* Style for `ft-prompt` */
:deep(.promptCard) {
/* Currently only this prompt has enough content to make prompt too high */
max-block-size: 95%;
/* Some child(s) will grow vertically */
display: flex;
flex-direction: column;
}
.searchInputsRow { .searchInputsRow {
display: grid; display: grid;

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue' import { defineComponent, nextTick } from 'vue'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
@ -44,11 +44,16 @@ export default defineComponent({
doSearchPlaylistsWithMatchingVideos: false, doSearchPlaylistsWithMatchingVideos: false,
updateQueryDebounce: function() {}, updateQueryDebounce: function() {},
lastShownAt: Date.now(), lastShownAt: Date.now(),
lastActiveElement: null,
sortBy: SORT_BY_VALUES.LatestUpdatedFirst, sortBy: SORT_BY_VALUES.LatestUpdatedFirst,
} }
}, },
computed: { computed: {
title: function () {
return this.$tc('User Playlists.AddVideoPrompt.Select a playlist to add your N videos to', this.toBeAddedToPlaylistVideoCount, {
videoCount: this.toBeAddedToPlaylistVideoCount,
})
},
showingCreatePlaylistPrompt: function () { showingCreatePlaylistPrompt: function () {
return this.$store.getters.getShowCreatePlaylistPrompt return this.$store.getters.getShowCreatePlaylistPrompt
}, },
@ -185,7 +190,7 @@ export default defineComponent({
if (val > oldVal) { if (val > oldVal) {
// Focus back to search input only when playlist added // Focus back to search input only when playlist added
// Allow search and easier deselecting new created playlist // Allow search and easier deselecting new created playlist
this.$refs.searchBar.focus() nextTick(() => this.$refs.searchBar.focus())
} }
}, },
@ -195,19 +200,17 @@ export default defineComponent({
// Only care when CreatePlaylistPrompt hidden // Only care when CreatePlaylistPrompt hidden
// Shift focus from button to prevent unwanted click event // Shift focus from button to prevent unwanted click event
// due to enter key press in CreatePlaylistPrompt // due to enter key press in CreatePlaylistPrompt
this.$refs.searchBar.focus() nextTick(() => this.$refs.searchBar.focus())
}, },
}, },
mounted: function () { mounted: function () {
this.lastActiveElement = document.activeElement
this.updateQueryDebounce = debounce(this.updateQuery, 500) this.updateQueryDebounce = debounce(this.updateQuery, 500)
// User might want to search first if they have many playlists
this.$refs.searchBar.focus()
document.addEventListener('keydown', this.keyboardShortcutHandler) document.addEventListener('keydown', this.keyboardShortcutHandler)
// User might want to search first if they have many playlists
nextTick(() => this.$refs.searchBar.focus())
}, },
beforeDestroy() { beforeDestroy() {
document.removeEventListener('keydown', this.keyboardShortcutHandler) document.removeEventListener('keydown', this.keyboardShortcutHandler)
this.lastActiveElement?.focus()
}, },
methods: { methods: {
hide: function () { hide: function () {

View File

@ -1,11 +1,11 @@
<template> <template>
<ft-prompt <ft-prompt
theme="flex-column"
:label="title"
@click="hide" @click="hide"
> >
<h2 class="heading"> <h2 class="heading">
{{ $tc('User Playlists.AddVideoPrompt.Select a playlist to add your N videos to', toBeAddedToPlaylistVideoCount, { {{ title }}
videoCount: toBeAddedToPlaylistVideoCount,
}) }}
</h2> </h2>
<p class="selected-count"> <p class="selected-count">
{{ $tc('User Playlists.AddVideoPrompt.N playlists selected', selectedPlaylistCount, { {{ $tc('User Playlists.AddVideoPrompt.N playlists selected', selectedPlaylistCount, {

View File

@ -7,7 +7,7 @@
background-color: rgb(0 0 0 / 70%); background-color: rgb(0 0 0 / 70%);
/* Higher than components like playlist info */ /* Higher than components like playlist info */
z-index: 200; z-index: 201;
padding: 15px; padding: 15px;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@ -17,6 +17,7 @@
.promptCard { .promptCard {
overflow-y: scroll; overflow-y: scroll;
max-block-size: 95%;
} }
.promptCard.autosize { .promptCard.autosize {
@ -34,6 +35,31 @@
box-sizing: border-box; box-sizing: border-box;
} }
.promptCard.flex-column {
/* Some child(ren) will grow vertically */
display: flex;
flex-direction: column;
}
.promptCard.slim {
max-inline-size: 70%;
inset-inline-start: 15%;
padding-block: 10px;
}
.center { .center {
text-align: center; text-align: center;
} }
@media only screen and (width <= 680px) {
.promptCard.slim {
padding-block: 5px;
}
}
@media only screen and (width <= 500px) {
.promptCard.slim {
max-inline-size: 80%;
inset-inline-start: 10%;
}
}

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue' import { defineComponent, nextTick } from 'vue'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import FtCard from '../../components/ft-card/ft-card.vue' import FtCard from '../../components/ft-card/ft-card.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
@ -15,7 +15,7 @@ export default defineComponent({
props: { props: {
label: { label: {
type: String, type: String,
default: '' required: true
}, },
extraLabels: { extraLabels: {
type: Array, type: Array,
@ -36,6 +36,10 @@ export default defineComponent({
autosize: { autosize: {
type: Boolean, type: Boolean,
default: false default: false
},
theme: {
type: String,
default: 'base'
} }
}, },
emits: ['click'], emits: ['click'],
@ -50,21 +54,22 @@ export default defineComponent({
return sanitizeForHtmlId(this.label) return sanitizeForHtmlId(this.label)
} }
}, },
beforeDestroy: function () {
document.removeEventListener('keydown', this.closeEventFunction, true)
this.lastActiveElement?.focus()
},
mounted: function () { mounted: function () {
this.lastActiveElement = document.activeElement this.lastActiveElement = document.activeElement
this.$nextTick(() => {
document.addEventListener('keydown', this.closeEventFunction, true) document.addEventListener('keydown', this.closeEventFunction, true)
document.querySelector('.prompt').addEventListener('keydown', this.arrowKeys, true) document.querySelector('.prompt').addEventListener('keydown', this.arrowKeys, true)
this.promptButtons = Array.from( this.promptButtons = Array.from(
document.querySelector('.prompt .promptCard .ft-flex-box').childNodes document.querySelector('.prompt .promptCard .ft-flex-box').childNodes
).filter((e) => { ).filter((e) => {
return e.id && e.id.startsWith('prompt') return e.id && e.id.startsWith('prompt')
})
this.focusItem(0)
}) })
this.focusItem(0) },
beforeDestroy: function () {
document.removeEventListener('keydown', this.closeEventFunction, true)
nextTick(() => this.lastActiveElement?.focus())
}, },
methods: { methods: {
click: function (value) { click: function (value) {

View File

@ -1,52 +1,57 @@
<template> <template>
<div <portal to="promptPortal">
class="prompt" <div
tabindex="-1" ref="openPrompt"
@click="handleHide" class="prompt"
@keydown.enter="handleHide" tabindex="-1"
> @click="handleHide"
<ft-card @keydown.enter="handleHide"
class="promptCard"
:class="{ autosize }"
:aria-labelledby="('dialog-' + sanitizedLabel)"
> >
<slot> <ft-card
<h2 class="promptCard"
:id="'dialog-' + sanitizedLabel" :class="{ autosize, [theme]: true }"
class="center" role="dialog"
> aria-modal="true"
{{ label }} :aria-labelledby="('dialog-' + sanitizedLabel)"
</h2> >
<p <slot>
v-for="extraLabel in extraLabels" <h2
:key="extraLabel" :id="'dialog-' + sanitizedLabel"
class="center" class="center"
> >
<strong> {{ label }}
{{ extraLabel }} </h2>
</strong> <p
</p> v-for="extraLabel in extraLabels"
<ft-flex-box> :key="extraLabel"
<ft-button class="center"
v-for="(option, index) in optionNames" >
:id="'prompt-' + sanitizedLabel + '-' + index" <strong>
:key="index" {{ extraLabel }}
:label="option" </strong>
@click="click(optionValues[index])" </p>
/> <ft-flex-box>
<ft-button <ft-button
v-if="showClose" v-for="(option, index) in optionNames"
:id="'prompt-' + sanitizedLabel + '-close'" :id="'prompt-' + sanitizedLabel + '-' + index"
:label="$t('Close')" :key="index"
:tabindex="0" :label="option"
text-color="'var(--accent-color)'" @click="click(optionValues[index])"
background-color="'var(--text-with-accent-color)'" />
@click="hide" <ft-button
/> v-if="showClose"
</ft-flex-box> :id="'prompt-' + sanitizedLabel + '-close'"
</slot> :label="$t('Close')"
</ft-card> :tabindex="0"
</div> text-color="'var(--accent-color)'"
background-color="'var(--text-with-accent-color)'"
@click="hide"
/>
</ft-flex-box>
</slot>
</ft-card>
</div>
</portal>
</template> </template>
<script src="./ft-prompt.js" /> <script src="./ft-prompt.js" />

View File

@ -11,7 +11,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
} }
.pure-checkbox input[type="checkbox"]:focus + label::before, .pure-radiobutton input[type="checkbox"]:focus + label::before, .pure-checkbox input[type="radio"]:focus + label::before, .pure-radiobutton input[type="radio"]:focus + label::before, .pure-checkbox input[type="checkbox"]:hover + label::before, .pure-radiobutton input[type="checkbox"]:hover + label::before, .pure-checkbox input[type="radio"]:hover + label::before, .pure-radiobutton input[type="radio"]:hover + label::before { .pure-checkbox input[type="checkbox"]:focus + label::before, .pure-radiobutton input[type="checkbox"]:focus + label::before, .pure-checkbox input[type="radio"]:focus + label::before, .pure-radiobutton input[type="radio"]:focus + label::before, .pure-checkbox input[type="checkbox"]:hover + label::before, .pure-radiobutton input[type="checkbox"]:hover + label::before, .pure-checkbox input[type="radio"]:hover + label::before, .pure-radiobutton input[type="radio"]:hover + label::before {
border-color: var(--primary-color); border: 2px solid var(--primary-color);
} }
.pure-checkbox input[type="checkbox"]:active + label::before, .pure-radiobutton input[type="checkbox"]:active + label::before, .pure-checkbox input[type="radio"]:active + label::before, .pure-radiobutton input[type="radio"]:active + label::before { transition-duration: 0s; } .pure-checkbox input[type="checkbox"]:active + label::before, .pure-radiobutton input[type="checkbox"]:active + label::before, .pure-checkbox input[type="radio"]:active + label::before, .pure-radiobutton input[type="radio"]:active + label::before { transition-duration: 0s; }
@ -22,20 +22,20 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
display: block; display: block;
margin-block-end: -20px; margin-block-start: 10px;
} }
.pure-checkbox input[type="checkbox"] + label::before, .pure-radiobutton input[type="checkbox"] + label::before, .pure-checkbox input[type="radio"] + label::before, .pure-radiobutton input[type="radio"] + label::before { .pure-checkbox input[type="checkbox"] + label::before, .pure-radiobutton input[type="checkbox"] + label::before, .pure-checkbox input[type="radio"] + label::before, .pure-radiobutton input[type="radio"] + label::before {
box-sizing: content-box; box-sizing: content-box;
content: ''; content: '';
color: var(--primary-color); color: var(--primary-text-color);
position: absolute; position: absolute;
inset-block-start: 50%; inset-block-start: 50%;
inset-inline-start: 0; inset-inline-start: 0;
inline-size: 14px; inline-size: 14px;
block-size: 14px; block-size: 14px;
margin-block-start: -9px; margin-block-start: -9px;
border: 2px solid var(--primary-color); border: 2px solid var(--primary-text-color);
text-align: center; text-align: center;
transition: all 0.4s ease; transition: all 0.4s ease;
} }
@ -43,7 +43,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
.pure-checkbox input[type="checkbox"] + label::after, .pure-radiobutton input[type="checkbox"] + label::after, .pure-checkbox input[type="radio"] + label::after, .pure-radiobutton input[type="radio"] + label::after { .pure-checkbox input[type="checkbox"] + label::after, .pure-radiobutton input[type="checkbox"] + label::after, .pure-checkbox input[type="radio"] + label::after, .pure-radiobutton input[type="radio"] + label::after {
box-sizing: content-box; box-sizing: content-box;
content: ''; content: '';
background-color: var(--primary-color); background-color: var(--primary-text-color);
position: absolute; position: absolute;
inset-block-start: 50%; inset-block-start: 50%;
inset-inline-start: 4px; inset-inline-start: 4px;
@ -84,23 +84,33 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
animation: borderscale 300ms ease-in; animation: borderscale 300ms ease-in;
} }
.pure-radiobutton input[type="radio"]:focus + label::after{
background-color: var(--primary-color);
}
.pure-checkbox input[type="radio"]:checked + label::after, .pure-radiobutton input[type="radio"]:checked + label::after { transform: scale(1); } .pure-checkbox input[type="radio"]:checked + label::after, .pure-radiobutton input[type="radio"]:checked + label::after { transform: scale(1); }
.pure-checkbox input[type="radio"] + label::before, .pure-radiobutton input[type="radio"] + label::before, .pure-checkbox input[type="radio"] + label::after, .pure-radiobutton input[type="radio"] + label::after { border-radius: 50%; } .pure-checkbox input[type="radio"] + label::before, .pure-radiobutton input[type="radio"] + label::before, .pure-checkbox input[type="radio"] + label::after, .pure-radiobutton input[type="radio"] + label::after { border-radius: 50%; }
.pure-checkbox input[type="checkbox"]:checked + label::before, .pure-radiobutton input[type="checkbox"]:checked + label::before { .pure-checkbox input[type="checkbox"]:checked + label::before, .pure-radiobutton input[type="checkbox"]:checked + label::before {
animation: borderscale 200ms ease-in; animation: borderscale 200ms ease-in;
background: var(--primary-color); background: var(--primary-text-color);
} }
.pure-radiobutton input[type="checkbox"]:checked + label::after { transform: rotate(-45deg) scale(1); } .pure-radiobutton input[type="checkbox"]:checked + label::after { transform: rotate(-45deg) scale(1); }
@keyframes @keyframes
borderscale { 50% { borderscale { 50% {
box-shadow: 0 0 0 2px var(--primary-color); box-shadow: 0 0 0 2px var(--primary-text-color);
} }
} }
.radioTitle { .radioTitle {
margin-block-end: -20px; margin-block: 0;
}
@media only screen and (width <= 680px) {
.pure-checkbox input[type="checkbox"] + label, .pure-radiobutton input[type="checkbox"] + label, .pure-checkbox input[type="radio"] + label, .pure-radiobutton input[type="radio"] + label {
margin-block-start: 3px;
}
} }

View File

@ -18,6 +18,10 @@ export default defineComponent({
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
},
initialValueIndex: {
type: Number,
default: 0
} }
}, },
emits: ['change'], emits: ['change'],
@ -35,7 +39,7 @@ export default defineComponent({
}, },
mounted: function () { mounted: function () {
this.id = this._uid this.id = this._uid
this.selectedValue = this.values[0] this.selectedValue = this.values[this.initialValueIndex]
}, },
methods: { methods: {
updateSelectedValue: function (value) { updateSelectedValue: function (value) {

View File

@ -14,7 +14,7 @@
v-model="selectedValue" v-model="selectedValue"
:name="inputName" :name="inputName"
:value="values[index]" :value="values[index]"
:checked="index === 0" :checked="index === initialValueIndex"
:disabled="disabled" :disabled="disabled"
class="radio" class="radio"
type="radio" type="radio"

View File

@ -1,17 +1,7 @@
.searchFilterInner {
max-inline-size: 800px;
margin-inline: auto;
padding-block: 20px 70px;
padding-inline: 20px;
max-block-size: 410px;
overflow-y: auto;
background-color: var(--card-bg-color);
box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
opacity: 0.9;
}
.center { .center {
margin-block: 5px 10px;
text-align: center; text-align: center;
user-select: none;
} }
.searchRadio { .searchRadio {
@ -19,13 +9,22 @@
} }
.radioFlexBox { .radioFlexBox {
max-inline-size: 1000px;
margin-block: 0; margin-block: 0;
margin-inline: auto; margin-inline: auto;
} }
@media only screen and (width <= 600px) { .searchFilterCloseButtonContainer {
display: flex;
justify-content: center;
}
@media only screen and (width <= 680px) {
.searchRadio { .searchRadio {
border-inline-end: 0; border-inline-end: 0;
padding-block-start: 0;
}
.radioFlexBox {
flex-flow: column;
} }
} }

View File

@ -1,16 +1,24 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { mapActions } from 'vuex'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtRadioButton from '../ft-radio-button/ft-radio-button.vue' import FtRadioButton from '../ft-radio-button/ft-radio-button.vue'
import FtPrompt from '../ft-prompt/ft-prompt.vue'
import FtButton from '../ft-button/ft-button.vue'
export default defineComponent({ export default defineComponent({
name: 'FtSearchFilters', name: 'FtSearchFilters',
components: { components: {
'ft-flex-box': FtFlexBox, 'ft-flex-box': FtFlexBox,
'ft-radio-button': FtRadioButton 'ft-radio-button': FtRadioButton,
'ft-prompt': FtPrompt,
'ft-button': FtButton
}, },
emits: ['filterValueUpdated'],
data: function () { data: function () {
return { return {
searchSortByStartIndex: 0,
searchTimeStartIndex: 0,
searchTypeStartIndex: 0,
searchDurationStartIndex: 0,
sortByValues: [ sortByValues: [
'relevance', 'relevance',
'rating', 'rating',
@ -41,11 +49,15 @@ export default defineComponent({
} }
}, },
computed: { computed: {
title: function () {
return this.$t('Search Filters.Search Filters')
},
searchSettings: function () { searchSettings: function () {
return this.$store.getters.getSearchSettings return this.$store.getters.getSearchSettings
}, },
filterValueChanged: function() { searchFilterValueChanged: function() {
return [ return [
this.$refs.sortByRadio.selectedValue !== this.sortByValues[0], this.$refs.sortByRadio.selectedValue !== this.sortByValues[0],
this.$refs.timeRadio.selectedValue !== this.timeValues[0], this.$refs.timeRadio.selectedValue !== this.timeValues[0],
@ -93,6 +105,12 @@ export default defineComponent({
] ]
} }
}, },
created: function () {
this.searchSortByStartIndex = this.sortByValues.indexOf(this.searchSettings.sortBy)
this.searchTimeStartIndex = this.timeValues.indexOf(this.searchSettings.time)
this.searchTypeStartIndex = this.typeValues.indexOf(this.searchSettings.type)
this.searchDurationStartIndex = this.durationValues.indexOf(this.searchSettings.duration)
},
methods: { methods: {
isVideoOrMovieOrAll(type) { isVideoOrMovieOrAll(type) {
return type === 'video' || type === 'movie' || type === 'all' return type === 'video' || type === 'movie' || type === 'all'
@ -100,7 +118,7 @@ export default defineComponent({
updateSortBy: function (value) { updateSortBy: function (value) {
this.$store.commit('setSearchSortBy', value) this.$store.commit('setSearchSortBy', value)
this.$emit('filterValueUpdated', this.filterValueChanged) this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
}, },
updateTime: function (value) { updateTime: function (value) {
@ -110,7 +128,7 @@ export default defineComponent({
this.$store.commit('setSearchType', 'all') this.$store.commit('setSearchType', 'all')
} }
this.$store.commit('setSearchTime', value) this.$store.commit('setSearchTime', value)
this.$emit('filterValueUpdated', this.filterValueChanged) this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
}, },
updateType: function (value) { updateType: function (value) {
@ -126,7 +144,7 @@ export default defineComponent({
this.$store.commit('setSearchSortBy', this.sortByValues[0]) this.$store.commit('setSearchSortBy', this.sortByValues[0])
} }
this.$store.commit('setSearchType', value) this.$store.commit('setSearchType', value)
this.$emit('filterValueUpdated', this.filterValueChanged) this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
}, },
updateDuration: function (value) { updateDuration: function (value) {
@ -136,7 +154,11 @@ export default defineComponent({
this.updateType('all') this.updateType('all')
} }
this.$store.commit('setSearchDuration', value) this.$store.commit('setSearchDuration', value)
this.$emit('filterValueUpdated', this.filterValueChanged) this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
} },
...mapActions([
'hideSearchFilters'
])
} }
}) })

View File

@ -1,8 +1,15 @@
<template> <template>
<div> <ft-prompt
<div class="searchFilterInner"> theme="slim"
<h2 class="center"> :label="title"
{{ $t("Search Filters.Search Filters") }} @click="hideSearchFilters"
>
<div>
<h2
class="center"
name="title"
>
{{ title }}
</h2> </h2>
<ft-flex-box class="radioFlexBox"> <ft-flex-box class="radioFlexBox">
<ft-radio-button <ft-radio-button
@ -10,6 +17,7 @@
:title="$t('Search Filters.Sort By.Sort By')" :title="$t('Search Filters.Sort By.Sort By')"
:labels="sortByLabels" :labels="sortByLabels"
:values="sortByValues" :values="sortByValues"
:initial-value-index="searchSortByStartIndex"
class="searchRadio" class="searchRadio"
@change="updateSortBy" @change="updateSortBy"
/> />
@ -18,6 +26,7 @@
:title="$t('Search Filters.Time.Time')" :title="$t('Search Filters.Time.Time')"
:labels="timeLabels" :labels="timeLabels"
:values="timeValues" :values="timeValues"
:initial-value-index="searchTimeStartIndex"
class="searchRadio" class="searchRadio"
@change="updateTime" @change="updateTime"
/> />
@ -26,6 +35,7 @@
:title="$t('Search Filters.Type.Type')" :title="$t('Search Filters.Type.Type')"
:labels="typeLabels" :labels="typeLabels"
:values="typeValues" :values="typeValues"
:initial-value-index="searchTypeStartIndex"
class="searchRadio" class="searchRadio"
@change="updateType" @change="updateType"
/> />
@ -34,12 +44,21 @@
:title="$t('Search Filters.Duration.Duration')" :title="$t('Search Filters.Duration.Duration')"
:labels="durationLabels" :labels="durationLabels"
:values="durationValues" :values="durationValues"
:initial-value-index="searchDurationStartIndex"
class="searchRadio" class="searchRadio"
@change="updateDuration" @change="updateDuration"
/> />
</ft-flex-box> </ft-flex-box>
<div class="searchFilterCloseButtonContainer">
<ft-button
:label="$t('Close')"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="hideSearchFilters"
/>
</div>
</div> </div>
</div> </ft-prompt>
</template> </template>
<script src="./ft-search-filters.js" /> <script src="./ft-search-filters.js" />

View File

@ -1,7 +1,6 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import FtInput from '../ft-input/ft-input.vue' import FtInput from '../ft-input/ft-input.vue'
import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue'
import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue' import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue'
import TopNavEvents from './top-nav-events' import TopNavEvents from './top-nav-events'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
@ -16,15 +15,12 @@ export default defineComponent({
name: 'TopNav', name: 'TopNav',
components: { components: {
FtInput, FtInput,
FtSearchFilters,
FtProfileSelector FtProfileSelector
}, },
data: () => { data: () => {
return { return {
component: this, component: this,
showSearchContainer: true, showSearchContainer: true,
showFilters: false,
searchFilterValueChanged: false,
historyIndex: 1, historyIndex: 1,
isForwardOrBack: false, isForwardOrBack: false,
isArrowBackwardDisabled: true, isArrowBackwardDisabled: true,
@ -85,6 +81,10 @@ export default defineComponent({
return this.$store.getters.getExpandSideBar return this.$store.getters.getExpandSideBar
}, },
searchFilterValueChanged: function () {
return this.$store.getters.getSearchFilterValueChanged
},
forwardText: function () { forwardText: function () {
return this.$t('Forward') return this.$t('Forward')
}, },
@ -222,9 +222,6 @@ export default defineComponent({
} }
} }
}) })
// Close the filter panel
this.showFilters = false
}, },
focusSearch: function () { focusSearch: function () {
@ -301,11 +298,6 @@ export default defineComponent({
toggleSearchContainer: function () { toggleSearchContainer: function () {
this.showSearchContainer = !this.showSearchContainer this.showSearchContainer = !this.showSearchContainer
this.showFilters = false
},
handleSearchFilterValueChanged: function (filterValueChanged) {
this.searchFilterValueChanged = filterValueChanged
}, },
navigateHistory: function () { navigateHistory: function () {
@ -360,14 +352,12 @@ export default defineComponent({
navigate: function (route) { navigate: function (route) {
this.$router.push('/' + route) this.$router.push('/' + route)
}, },
hideFilters: function () {
this.showFilters = false
},
updateSearchInputText: function ({ detail: { query } }) { updateSearchInputText: function ({ detail: { query } }) {
this.$refs.searchInput.updateInputData(query) this.$refs.searchInput.updateInputData(query)
}, },
...mapActions([ ...mapActions([
'getYoutubeUrlInfo', 'getYoutubeUrlInfo',
'showSearchFilters'
]) ])
} }
}) })

View File

@ -89,15 +89,17 @@
} }
.navFilterIcon { .navFilterIcon {
$effect-distance: 10px; $effect-distance: 20px;
margin-inline-start: $effect-distance; margin-inline-start: $effect-distance;
&.filterChanged { &.filterChanged {
box-shadow: 0 0 $effect-distance var(--primary-color); box-shadow: 0 0 $effect-distance var(--primary-color);
color: var(--primary-color);
@include top-nav-is-colored { @include top-nav-is-colored {
box-shadow: 0 0 $effect-distance var(--text-with-main-color); box-shadow: 0 0 $effect-distance var(--text-with-main-color);
color: var(--text-with-main-color);
} }
} }
} }
@ -209,18 +211,4 @@
flex: 1; flex: 1;
} }
} }
.searchFilters {
inset-inline: 0;
margin-block: 10px 20px;
margin-inline: 220px 20px;
position: absolute;
transition: margin 150ms ease-in-out;
@media only screen and (width <= 680px) {
inset-inline: 0;
margin-block: 95px 0;
margin-inline: 10px;
}
}
} }

View File

@ -94,18 +94,13 @@
class="navFilterIcon navIcon" class="navFilterIcon navIcon"
:class="{ filterChanged: searchFilterValueChanged }" :class="{ filterChanged: searchFilterValueChanged }"
:icon="['fas', 'filter']" :icon="['fas', 'filter']"
:title="$t('Search Filters.Search Filters')"
role="button" role="button"
tabindex="0" tabindex="0"
@click="showFilters = !showFilters" @click="showSearchFilters"
@keydown.enter.prevent="showFilters = !showFilters" @keydown.enter.prevent="showSearchFilters"
/> />
</div> </div>
<ft-search-filters
v-if="!hideSearchBar"
v-show="showFilters"
class="searchFilters"
@filterValueUpdated="handleSearchFilterValueChanged"
/>
</div> </div>
<ft-profile-selector class="side profiles" /> <ft-profile-selector class="side profiles" />
</div> </div>

View File

@ -146,7 +146,7 @@ function parseInvidiousCommentData(response) {
return response.comments.map((comment) => { return response.comments.map((comment) => {
comment.showReplies = false comment.showReplies = false
comment.authorLink = comment.authorId comment.authorLink = comment.authorId
comment.authorThumb = youtubeImageUrlToInvidious(comment.authorThumbnails[1].url) comment.authorThumb = youtubeImageUrlToInvidious(comment.authorThumbnails.at(-1).url)
comment.likes = comment.likeCount comment.likes = comment.likeCount
comment.text = autolinker.link(stripHTML(invidiousImageUrlToInvidious(comment.contentHtml, getCurrentInstance()))) comment.text = autolinker.link(stripHTML(invidiousImageUrlToInvidious(comment.contentHtml, getCurrentInstance())))
comment.dataType = 'invidious' comment.dataType = 'invidious'

View File

@ -95,6 +95,7 @@ import {
faMonero faMonero
} from '@fortawesome/free-brands-svg-icons' } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import PortalVue from 'portal-vue'
Vue.config.devtools = process.env.NODE_ENV === 'development' Vue.config.devtools = process.env.NODE_ENV === 'development'
Vue.config.performance = process.env.NODE_ENV === 'development' Vue.config.performance = process.env.NODE_ENV === 'development'
@ -201,6 +202,7 @@ new Vue({
i18n, i18n,
render: h => h(App) render: h => h(App)
}) })
Vue.use(PortalVue)
// to avoid accessing electron api from web app build // to avoid accessing electron api from web app build
if (process.env.IS_ELECTRON) { if (process.env.IS_ELECTRON) {

View File

@ -16,14 +16,26 @@ const getters = {
} }
const actions = { const actions = {
async fetchInvidiousInstancesFromFile({ commit }) {
const url = createWebURL('/static/invidious-instances.json')
const fileData = await (await fetch(url)).json()
const instances = fileData.filter(e => {
return process.env.SUPPORTS_LOCAL_API || e.cors
}).map(e => {
return e.url
})
commit('setInvidiousInstancesList', instances)
},
/// fetch invidious instances from site and overwrite static file.
async fetchInvidiousInstances({ commit }) { async fetchInvidiousInstances({ commit }) {
const requestUrl = 'https://api.invidious.io/instances.json' const requestUrl = 'https://api.invidious.io/instances.json'
let instances = []
try { try {
const response = await fetchWithTimeout(15_000, requestUrl) const response = await fetchWithTimeout(15_000, requestUrl)
const json = await response.json() const json = await response.json()
instances = json.filter((instance) => { const instances = json.filter((instance) => {
return !(instance[0].includes('.onion') || return !(instance[0].includes('.onion') ||
instance[0].includes('.i2p') || instance[0].includes('.i2p') ||
!instance[1].api || !instance[1].api ||
@ -31,6 +43,12 @@ const actions = {
}).map((instance) => { }).map((instance) => {
return instance[1].uri.replace(/\/$/, '') return instance[1].uri.replace(/\/$/, '')
}) })
if (instances.length !== 0) {
commit('setInvidiousInstancesList', instances)
} else {
console.warn('using static file for invidious instances')
}
} catch (err) { } catch (err) {
if (err.name === 'TimeoutError') { if (err.name === 'TimeoutError') {
console.error('Fetching the Invidious instance list timed out after 15 seconds. Falling back to local copy.') console.error('Fetching the Invidious instance list timed out after 15 seconds. Falling back to local copy.')
@ -38,20 +56,6 @@ const actions = {
console.error(err) console.error(err)
} }
} }
// If the invidious instance fetch isn't returning anything interpretable
if (instances.length === 0) {
console.warn('reading static file for invidious instances')
const url = createWebURL('/static/invidious-instances.json')
const fileData = await (await fetch(url)).json()
instances = fileData.filter(e => {
return process.env.SUPPORTS_LOCAL_API || e.cors
}).map(e => {
return e.url
})
}
commit('setInvidiousInstancesList', instances)
}, },
setRandomCurrentInvidiousInstance({ commit, state }) { setRandomCurrentInvidiousInstance({ commit, state }) {

View File

@ -33,6 +33,8 @@ const state = {
showProgressBar: false, showProgressBar: false,
showAddToPlaylistPrompt: false, showAddToPlaylistPrompt: false,
showCreatePlaylistPrompt: false, showCreatePlaylistPrompt: false,
showSearchFilters: false,
searchFilterValueChanged: false,
progressBarPercentage: 0, progressBarPercentage: 0,
toBeAddedToPlaylistVideoList: [], toBeAddedToPlaylistVideoList: [],
newPlaylistDefaultProperties: {}, newPlaylistDefaultProperties: {},
@ -94,6 +96,10 @@ const getters = {
return state.searchSettings return state.searchSettings
}, },
getSearchFilterValueChanged () {
return state.searchFilterValueChanged
},
getShowAddToPlaylistPrompt () { getShowAddToPlaylistPrompt () {
return state.showAddToPlaylistPrompt return state.showAddToPlaylistPrompt
}, },
@ -102,6 +108,10 @@ const getters = {
return state.showCreatePlaylistPrompt return state.showCreatePlaylistPrompt
}, },
getShowSearchFilters () {
return state.showSearchFilters
},
getToBeAddedToPlaylistVideoList () { getToBeAddedToPlaylistVideoList () {
return state.toBeAddedToPlaylistVideoList return state.toBeAddedToPlaylistVideoList
}, },
@ -168,7 +178,7 @@ const getters = {
getLastVideoRefreshTimestampByProfile: (state) => (profileId) => { getLastVideoRefreshTimestampByProfile: (state) => (profileId) => {
return state.lastVideoRefreshTimestampByProfile[profileId] return state.lastVideoRefreshTimestampByProfile[profileId]
} },
} }
const actions = { const actions = {
@ -378,6 +388,14 @@ const actions = {
commit('setShowCreatePlaylistPrompt', false) commit('setShowCreatePlaylistPrompt', false)
}, },
showSearchFilters ({ commit }) {
commit('setShowSearchFilters', true)
},
hideSearchFilters ({ commit }) {
commit('setShowSearchFilters', false)
},
updateShowProgressBar ({ commit }, value) { updateShowProgressBar ({ commit }, value) {
commit('setShowProgressBar', value) commit('setShowProgressBar', value)
}, },
@ -839,6 +857,10 @@ const mutations = {
state.showCreatePlaylistPrompt = payload state.showCreatePlaylistPrompt = payload
}, },
setShowSearchFilters (state, payload) {
state.showSearchFilters = payload
},
setToBeAddedToPlaylistVideoList (state, payload) { setToBeAddedToPlaylistVideoList (state, payload) {
state.toBeAddedToPlaylistVideoList = payload state.toBeAddedToPlaylistVideoList = payload
}, },
@ -899,6 +921,10 @@ const mutations = {
state.cachedPlaylist = value state.cachedPlaylist = value
}, },
setSearchFilterValueChanged (state, value) {
state.searchFilterValueChanged = value
},
setSearchSortBy (state, value) { setSearchSortBy (state, value) {
state.searchSettings.sortBy = value state.searchSettings.sortBy = value
}, },

View File

@ -23,19 +23,24 @@ export default defineComponent({
'general-settings': GeneralSettings, 'general-settings': GeneralSettings,
'theme-settings': ThemeSettings, 'theme-settings': ThemeSettings,
'player-settings': PlayerSettings, 'player-settings': PlayerSettings,
'external-player-settings': ExternalPlayerSettings,
'subscription-settings': SubscriptionSettings, 'subscription-settings': SubscriptionSettings,
'privacy-settings': PrivacySettings, 'privacy-settings': PrivacySettings,
'data-settings': DataSettings, 'data-settings': DataSettings,
'distraction-settings': DistractionSettings, 'distraction-settings': DistractionSettings,
'proxy-settings': ProxySettings,
'sponsor-block-settings': SponsorBlockSettings, 'sponsor-block-settings': SponsorBlockSettings,
'download-settings': DownloadSettings,
'parental-control-settings': ParentControlSettings, 'parental-control-settings': ParentControlSettings,
'experimental-settings': ExperimentalSettings,
'password-settings': PasswordSettings, 'password-settings': PasswordSettings,
'password-dialog': PasswordDialog, 'password-dialog': PasswordDialog,
'ft-toggle-switch': FtToggleSwitch 'ft-toggle-switch': FtToggleSwitch,
...(process.env.IS_ELECTRON
? {
'proxy-settings': ProxySettings,
'download-settings': DownloadSettings,
'external-player-settings': ExternalPlayerSettings,
'experimental-settings': ExperimentalSettings
}
: {})
}, },
data: function () { data: function () {
return { return {
@ -53,11 +58,14 @@ export default defineComponent({
type: 'player-settings', type: 'player-settings',
title: this.$t('Settings.Player Settings.Player Settings') title: this.$t('Settings.Player Settings.Player Settings')
}, },
{ ...(process.env.IS_ELECTRON
type: 'external-player-settings', ? [
title: this.$t('Settings.External Player Settings.External Player Settings'), {
usingElectron: true type: 'external-player-settings',
}, title: this.$t('Settings.External Player Settings.External Player Settings')
}
]
: []),
{ {
type: 'subscription-settings', type: 'subscription-settings',
title: this.$t('Settings.Subscription Settings.Subscription Settings') title: this.$t('Settings.Subscription Settings.Subscription Settings')
@ -74,16 +82,18 @@ export default defineComponent({
type: 'data-settings', type: 'data-settings',
title: this.$t('Settings.Data Settings.Data Settings') title: this.$t('Settings.Data Settings.Data Settings')
}, },
{ ...(process.env.IS_ELECTRON
type: 'proxy-settings', ? [
title: this.$t('Settings.Proxy Settings.Proxy Settings'), {
usingElectron: true type: 'proxy-settings',
}, title: this.$t('Settings.Proxy Settings.Proxy Settings')
{ },
type: 'download-settings', {
title: this.$t('Settings.Download Settings.Download Settings'), type: 'download-settings',
usingElectron: true title: this.$t('Settings.Download Settings.Download Settings')
}, }
]
: []),
{ {
type: 'parental-control-settings', type: 'parental-control-settings',
title: this.$t('Settings.Parental Control Settings.Parental Control Settings') title: this.$t('Settings.Parental Control Settings.Parental Control Settings')
@ -92,11 +102,14 @@ export default defineComponent({
type: 'sponsor-block-settings', type: 'sponsor-block-settings',
title: this.$t('Settings.SponsorBlock Settings.SponsorBlock Settings'), title: this.$t('Settings.SponsorBlock Settings.SponsorBlock Settings'),
}, },
{ ...(process.env.IS_ELECTRON
type: 'experimental-settings', ? [
title: this.$t('Settings.Experimental Settings.Experimental Settings'), {
usingElectron: true type: 'experimental-settings',
}, title: this.$t('Settings.Experimental Settings.Experimental Settings')
},
]
: []),
{ {
type: 'password-settings', type: 'password-settings',
title: this.$t('Settings.Password Settings.Password Settings') title: this.$t('Settings.Password Settings.Password Settings')
@ -122,20 +135,13 @@ export default defineComponent({
}, },
settingsSectionComponents: function () { settingsSectionComponents: function () {
let settingsSections
if (!process.env.IS_ELECTRON) {
settingsSections = this.settingsComponentsData.filter((settingsComponent) => !settingsComponent.usingElectron)
} else {
settingsSections = this.settingsComponentsData
}
if (this.settingsSectionSortEnabled) { if (this.settingsSectionSortEnabled) {
return settingsSections.toSorted((a, b) => return this.settingsComponentsData.toSorted((a, b) =>
a.title.toLowerCase().localeCompare(b.title.toLowerCase(), this.locale) a.title.toLowerCase().localeCompare(b.title.toLowerCase(), this.locale)
) )
} }
return settingsSections return this.settingsComponentsData
}, },
}, },
created: function () { created: function () {

View File

@ -23,10 +23,6 @@
"url": "https://inv.tux.pizza", "url": "https://inv.tux.pizza",
"cors": true "cors": true
}, },
{
"url": "https://invidious.flokinet.to",
"cors": true
},
{ {
"url": "https://invidious.privacydev.net", "url": "https://invidious.privacydev.net",
"cors": true "cors": true
@ -40,11 +36,11 @@
"cors": true "cors": true
}, },
{ {
"url": "https://invidious.protokolla.fi", "url": "https://iv.nboeck.de",
"cors": true "cors": true
}, },
{ {
"url": "https://iv.nboeck.de", "url": "https://invidious.protokolla.fi",
"cors": true "cors": true
}, },
{ {
@ -56,11 +52,7 @@
"cors": true "cors": true
}, },
{ {
"url": "https://inv.n8pjl.ca", "url": "https://inv.us.projectsegfau.lt",
"cors": true
},
{
"url": "https://iv.datura.network",
"cors": true "cors": true
}, },
{ {
@ -87,6 +79,10 @@
"url": "https://vid.lilay.dev", "url": "https://vid.lilay.dev",
"cors": true "cors": true
}, },
{
"url": "https://iv.datura.network",
"cors": true
},
{ {
"url": "https://yt.drgnz.club", "url": "https://yt.drgnz.club",
"cors": true "cors": true

View File

@ -653,6 +653,7 @@ Settings:
zu verhindern zu verhindern
Set Password: Passwort festlegen Set Password: Passwort festlegen
Expand All Settings Sections: Alle Einstellungsabschnitte aufklappen Expand All Settings Sections: Alle Einstellungsabschnitte aufklappen
Sort Settings Sections (A-Z): Einstellungsbereiche sortieren (A-Z)
About: About:
#On About page #On About page
About: Über About: Über
@ -1258,3 +1259,4 @@ checkmark: ✓
Moments Ago: vor wenigen Augenblicken Moments Ago: vor wenigen Augenblicken
Feed: Feed:
Refresh Feed: '{subscriptionName} auffrischen' Refresh Feed: '{subscriptionName} auffrischen'
Feed Last Updated: '{feedName} Feed zuletzt aktualisiert: {date}'

View File

@ -6681,6 +6681,11 @@ pluralize@^8.0.0:
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
portal-vue@^2.1.7:
version "2.1.7"
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.7.tgz#ea08069b25b640ca08a5b86f67c612f15f4e4ad4"
integrity sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g==
postcss-calc@^9.0.1: postcss-calc@^9.0.1:
version "9.0.1" version "9.0.1"
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6"