updateQueryDebounce(input)"
@clear="updateQueryDebounce('')"
/>
{
const videos = result.videos.filter(e => e.type === 'video')
- addPublishedDatesInvidious(videos)
+ setPublishedTimestampsInvidious(videos)
let name
diff --git a/src/renderer/components/subscriptions-videos/subscriptions-videos.js b/src/renderer/components/subscriptions-videos/subscriptions-videos.js
index aea208a2d..11ea86f6b 100644
--- a/src/renderer/components/subscriptions-videos/subscriptions-videos.js
+++ b/src/renderer/components/subscriptions-videos/subscriptions-videos.js
@@ -2,10 +2,10 @@ import { defineComponent } from 'vue'
import { mapActions, mapMutations } from 'vuex'
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
-import { copyToClipboard, showToast } from '../../helpers/utils'
+import { setPublishedTimestampsInvidious, copyToClipboard, showToast } from '../../helpers/utils'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { getLocalChannelVideos } from '../../helpers/api/local'
-import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
+import { parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
export default defineComponent({
name: 'SubscriptionsVideos',
@@ -198,8 +198,6 @@ export default defineComponent({
}
}
- addPublishedDatesLocal(result.videos)
-
return result
} catch (err) {
console.error(err)
@@ -291,7 +289,7 @@ export default defineComponent({
}
invidiousAPICall(subscriptionsPayload).then((result) => {
- addPublishedDatesInvidious(result.videos)
+ setPublishedTimestampsInvidious(result.videos)
let name
diff --git a/src/renderer/components/watch-video-playlist/watch-video-playlist.js b/src/renderer/components/watch-video-playlist/watch-video-playlist.js
index e5244e2c0..1bb413627 100644
--- a/src/renderer/components/watch-video-playlist/watch-video-playlist.js
+++ b/src/renderer/components/watch-video-playlist/watch-video-playlist.js
@@ -3,7 +3,7 @@ import { mapMutations } from 'vuex'
import FtLoader from '../ft-loader/ft-loader.vue'
import FtCard from '../ft-card/ft-card.vue'
import FtListVideoNumbered from '../ft-list-video-numbered/ft-list-video-numbered.vue'
-import { copyToClipboard, showToast } from '../../helpers/utils'
+import { copyToClipboard, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
import {
getLocalPlaylist,
parseLocalPlaylistVideo,
@@ -451,6 +451,8 @@ export default defineComponent({
this.playlistTitle = result.title
this.channelName = result.author
this.channelId = result.authorId
+
+ setPublishedTimestampsInvidious(result.videos)
this.playlistItems = this.playlistItems.concat(result.videos)
this.isLoading = false
diff --git a/src/renderer/helpers/api/local.js b/src/renderer/helpers/api/local.js
index 0f3b08cda..5d14ba5bd 100644
--- a/src/renderer/helpers/api/local.js
+++ b/src/renderer/helpers/api/local.js
@@ -5,6 +5,7 @@ import { join } from 'path'
import { PlayerCache } from './PlayerCache'
import {
CHANNEL_HANDLE_REGEX,
+ calculatePublishedDate,
escapeHTML,
extractNumberFromString,
getUserDataPath,
@@ -681,8 +682,7 @@ export function parseLocalPlaylistVideo(video) {
}
}
- let publishedText = null
-
+ let publishedText
// normal videos have 3 text runs with the last one containing the published date
// live videos have 2 text runs with the number of people watching
// upcoming either videos don't have any info text or the number of people waiting,
@@ -691,13 +691,20 @@ export function parseLocalPlaylistVideo(video) {
publishedText = video_.video_info.runs[2].text
}
+ const published = calculatePublishedDate(
+ publishedText,
+ video_.is_live,
+ video_.is_upcoming,
+ video_.upcoming
+ )
+
return {
videoId: video_.id,
title: video_.title.text,
author: video_.author.name,
authorId: video_.author.id,
viewCount,
- publishedText,
+ published,
lengthSeconds: isNaN(video_.duration.seconds) ? '' : video_.duration.seconds,
liveNow: video_.is_live,
isUpcoming: video_.is_upcoming,
@@ -728,6 +735,20 @@ export function parseLocalListVideo(item) {
} else {
/** @type {import('youtubei.js').YTNodes.Video} */
const video = item
+
+ let publishedText
+
+ if (!video.published?.isEmpty()) {
+ publishedText = video.published.text
+ }
+
+ const published = calculatePublishedDate(
+ publishedText,
+ video.is_live,
+ video.is_upcoming || video.is_premiere,
+ video.upcoming
+ )
+
return {
type: 'video',
videoId: video.id,
@@ -736,7 +757,7 @@ export function parseLocalListVideo(item) {
authorId: video.author.id,
description: video.description,
viewCount: video.view_count == null ? null : extractNumberFromString(video.view_count.text),
- publishedText: (video.published == null || video.published.isEmpty()) ? null : video.published.text,
+ published,
lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
liveNow: video.is_live,
isUpcoming: video.is_upcoming || video.is_premiere,
@@ -811,6 +832,14 @@ function parseListItem(item) {
* @param {import('youtubei.js').YTNodes.CompactVideo} video
*/
export function parseLocalWatchNextVideo(video) {
+ let publishedText
+
+ if (!video.published?.isEmpty()) {
+ publishedText = video.published.text
+ }
+
+ const published = calculatePublishedDate(publishedText, video.is_live, video.is_premiere)
+
return {
type: 'video',
videoId: video.id,
@@ -818,7 +847,7 @@ export function parseLocalWatchNextVideo(video) {
author: video.author.name,
authorId: video.author.id,
viewCount: video.view_count == null ? null : extractNumberFromString(video.view_count.text),
- publishedText: (video.published == null || video.published.isEmpty()) ? null : video.published.text,
+ published,
lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
liveNow: video.is_live,
isUpcoming: video.is_premiere
diff --git a/src/renderer/helpers/subscriptions.js b/src/renderer/helpers/subscriptions.js
index 41bef51bd..f41205244 100644
--- a/src/renderer/helpers/subscriptions.js
+++ b/src/renderer/helpers/subscriptions.js
@@ -1,5 +1,4 @@
import store from '../store/index'
-import { calculatePublishedDate } from './utils'
/**
* Filtering and sort based on user preferences
@@ -57,7 +56,7 @@ export function updateVideoListAfterProcessing(videos) {
}
videoList.sort((a, b) => {
- return b.publishedDate - a.publishedDate
+ return b.published - a.published
})
return videoList
@@ -106,57 +105,10 @@ async function parseRSSEntry(entry, channelId, channelName) {
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
title: entry.querySelector('title').textContent,
- publishedDate: published,
- publishedText: published.toLocaleString(),
+ published: published.getTime(),
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
type: 'video',
lengthSeconds: '0:00',
isRSS: true
}
}
-
-/**
- * @param {{
- * liveNow: boolean,
- * isUpcoming: boolean,
- * premiereDate: Date,
- * publishedText: string,
- * publishedDate: number
- * }[]} videos publishedDate is added by this function,
- * but adding it to the type definition stops vscode warning that the property doesn't exist
- */
-export function addPublishedDatesLocal(videos) {
- videos.forEach(video => {
- if (video.liveNow) {
- video.publishedDate = new Date().getTime()
- } else if (video.isUpcoming) {
- video.publishedDate = video.premiereDate
- } else {
- video.publishedDate = calculatePublishedDate(video.publishedText)
- }
- return video
- })
-}
-
-/**
- * @param {{
- * liveNow: boolean,
- * isUpcoming: boolean,
- * premiereTimestamp: number,
- * published: number,
- * publishedDate: number
- * }[]} videos publishedDate is added by this function,
- * but adding it to the type definition stops vscode warning that the property doesn't exist
- */
-export function addPublishedDatesInvidious(videos) {
- videos.forEach(video => {
- if (video.liveNow) {
- video.publishedDate = new Date().getTime()
- } else if (video.isUpcoming) {
- video.publishedDate = new Date(video.premiereTimestamp * 1000)
- } else {
- video.publishedDate = new Date(video.published * 1000)
- }
- return video
- })
-}
diff --git a/src/renderer/helpers/utils.js b/src/renderer/helpers/utils.js
index 4fde63b77..fd3016062 100644
--- a/src/renderer/helpers/utils.js
+++ b/src/renderer/helpers/utils.js
@@ -12,11 +12,27 @@ export const CHANNEL_HANDLE_REGEX = /^@[\w.-]{3,30}$/
const PUBLISHED_TEXT_REGEX = /(\d+)\s?([a-z]+)/i
/**
* @param {string} publishedText
+ * @param {boolean} isLive
+ * @param {boolean} isUpcoming
+ * @param {Date|undefined} premiereDate
*/
-export function calculatePublishedDate(publishedText) {
+export function calculatePublishedDate(publishedText, isLive = false, isUpcoming = false, premiereDate = undefined) {
const date = new Date()
- if (publishedText === 'Live') {
- return publishedText
+
+ if (isLive) {
+ return date.getTime()
+ } else if (isUpcoming) {
+ if (premiereDate) {
+ return premiereDate.getTime()
+ } else {
+ // should never happen but just to be sure that we always return a number
+ return date.getTime()
+ }
+ }
+
+ if (!publishedText) {
+ console.error("publishedText is missing but the video isn't live or upcoming")
+ return undefined
}
const match = publishedText.match(PUBLISHED_TEXT_REGEX)
@@ -44,6 +60,26 @@ export function calculatePublishedDate(publishedText) {
return date.getTime() - timeSpan
}
+/**
+ * @param {{
+ * liveNow: boolean,
+ * isUpcoming: boolean,
+ * premiereTimestamp: number,
+ * published: number
+ * }[]} videos
+ */
+export function setPublishedTimestampsInvidious(videos) {
+ videos.forEach(video => {
+ if (video.liveNow) {
+ video.published = new Date().getTime()
+ } else if (video.isUpcoming) {
+ video.published = video.premiereTimestamp * 1000
+ } else if (typeof video.published === 'number') {
+ video.published *= 1000
+ }
+ })
+}
+
export function toLocalePublicationString ({ publishText, isLive = false, isUpcoming = false, isRSS = false }) {
if (isLive) {
return i18n.tc('Global.Counts.Watching Count', 0, { count: 0 })
diff --git a/src/renderer/scss-partials/_ft-list-item.scss b/src/renderer/scss-partials/_ft-list-item.scss
index 899948d17..624d21093 100644
--- a/src/renderer/scss-partials/_ft-list-item.scss
+++ b/src/renderer/scss-partials/_ft-list-item.scss
@@ -370,7 +370,9 @@ $watched-transition-duration: 0.5s;
}
&:hover .optionsButton,
- &:has(:focus-visible) .optionsButton {
+ &:has(:focus-visible) .optionsButton,
+ // Keep visible when the drop down is open
+ :deep(.optionsButton:has(> .iconDropdown:focus-within)) {
opacity: 1;
}
diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js
index 90dfb5bd5..7ea6c27e5 100644
--- a/src/renderer/views/Channel/Channel.js
+++ b/src/renderer/views/Channel/Channel.js
@@ -12,7 +12,13 @@ import FtSubscribeButton from '../../components/ft-subscribe-button/ft-subscribe
import ChannelAbout from '../../components/channel-about/channel-about.vue'
import autolinker from 'autolinker'
-import { copyToClipboard, extractNumberFromString, formatNumber, showToast } from '../../helpers/utils'
+import {
+ setPublishedTimestampsInvidious,
+ copyToClipboard,
+ extractNumberFromString,
+ formatNumber,
+ showToast
+} from '../../helpers/utils'
import { isNullOrEmpty } from '../../helpers/strings'
import packageDetails from '../../../../package.json'
import {
@@ -33,10 +39,6 @@ import {
parseLocalListVideo,
parseLocalSubscriberCount
} from '../../helpers/api/local'
-import {
- addPublishedDatesInvidious,
- addPublishedDatesLocal
-} from '../../helpers/subscriptions'
export default defineComponent({
name: 'Channel',
@@ -780,7 +782,6 @@ export default defineComponent({
this.isElementListLoading = false
if (this.isSubscribedInAnyProfile && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
- addPublishedDatesLocal(this.latestVideos)
this.updateSubscriptionVideosCacheByChannel({
channelId: this.id,
// create a copy so that we only cache the first page
@@ -917,7 +918,6 @@ export default defineComponent({
this.isElementListLoading = false
if (this.isSubscribedInAnyProfile && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
- addPublishedDatesLocal(this.latestLive)
this.updateSubscriptionLiveCacheByChannel({
channelId: this.id,
// create a copy so that we only cache the first page
@@ -992,7 +992,6 @@ export default defineComponent({
thumbnailUrl: youtubeImageUrlToInvidious(thumbnailUrl, this.currentInvidiousInstance)
}
})
- this.latestVideos = response.latestVideos
if (response.authorBanners instanceof Array && response.authorBanners.length > 0) {
this.bannerUrl = youtubeImageUrlToInvidious(response.authorBanners[0].url, this.currentInvidiousInstance)
@@ -1088,6 +1087,8 @@ export default defineComponent({
}
invidiousAPICall(payload).then((response) => {
+ setPublishedTimestampsInvidious(response.videos)
+
if (more) {
this.latestVideos = this.latestVideos.concat(response.videos)
} else {
@@ -1097,7 +1098,6 @@ export default defineComponent({
this.isElementListLoading = false
if (this.isSubscribedInAnyProfile && !more && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
- addPublishedDatesInvidious(this.latestVideos)
this.updateSubscriptionVideosCacheByChannel({
channelId: this.id,
// create a copy so that we only cache the first page
@@ -1143,7 +1143,7 @@ export default defineComponent({
// https://github.com/iv-org/invidious/issues/3801
response.videos.forEach(video => {
video.isUpcoming = false
- delete video.publishedText
+ delete video.published
delete video.premiereTimestamp
})
@@ -1199,6 +1199,8 @@ export default defineComponent({
}
invidiousAPICall(payload).then((response) => {
+ setPublishedTimestampsInvidious(response.videos)
+
if (more) {
this.latestLive.push(...response.videos)
} else {
@@ -1208,7 +1210,6 @@ export default defineComponent({
this.isElementListLoading = false
if (this.isSubscribedInAnyProfile && !more && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
- addPublishedDatesInvidious(this.latestLive)
this.updateSubscriptionLiveCacheByChannel({
channelId: this.id,
// create a copy so that we only cache the first page
diff --git a/src/renderer/views/Hashtag/Hashtag.js b/src/renderer/views/Hashtag/Hashtag.js
index 61d0b8bea..b0edbff84 100644
--- a/src/renderer/views/Hashtag/Hashtag.js
+++ b/src/renderer/views/Hashtag/Hashtag.js
@@ -5,7 +5,7 @@ import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import packageDetails from '../../../../package.json'
import { getHashtagLocal, parseLocalListVideo } from '../../helpers/api/local'
-import { copyToClipboard, showToast } from '../../helpers/utils'
+import { copyToClipboard, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
import { isNullOrEmpty } from '../../helpers/strings'
import { getHashtagInvidious } from '../../helpers/api/invidious'
@@ -73,6 +73,7 @@ export default defineComponent({
getInvidiousHashtag: async function(hashtag, page) {
try {
const videos = await getHashtagInvidious(hashtag, page)
+ setPublishedTimestampsInvidious(videos)
this.hashtag = '#' + hashtag
this.isLoading = false
this.apiUsed = 'invidious'
diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js
index 8dfc3c786..936f96abe 100644
--- a/src/renderer/views/Playlist/Playlist.js
+++ b/src/renderer/views/Playlist/Playlist.js
@@ -12,7 +12,7 @@ import {
getLocalPlaylistContinuation,
parseLocalPlaylistVideo,
} from '../../helpers/api/local'
-import { extractNumberFromString, showToast } from '../../helpers/utils'
+import { extractNumberFromString, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
export default defineComponent({
@@ -113,6 +113,17 @@ export default defineComponent({
}
},
+ searchVideoModeAllowed() {
+ return this.isUserPlaylistRequested
+ },
+ searchQueryTextRequested() {
+ return this.$route.query.searchQueryText
+ },
+ searchQueryTextPresent() {
+ const searchQueryText = this.searchQueryTextRequested
+ return typeof searchQueryText === 'string' && searchQueryText !== ''
+ },
+
isUserPlaylistRequested: function () {
return this.$route.query.playlistType === 'user'
},
@@ -181,6 +192,11 @@ export default defineComponent({
},
created: function () {
this.getPlaylistInfoDebounce = debounce(this.getPlaylistInfo, 100)
+
+ if (this.searchVideoModeAllowed && this.searchQueryTextPresent) {
+ this.playlistInVideoSearchMode = true
+ this.videoSearchQuery = this.searchQueryTextRequested
+ }
},
mounted: function () {
this.getPlaylistInfoDebounce()
@@ -284,6 +300,8 @@ export default defineComponent({
const dateString = new Date(result.updated * 1000)
this.lastUpdated = dateString.toLocaleDateString(this.currentLocale, { year: 'numeric', month: 'short', day: 'numeric' })
+ setPublishedTimestampsInvidious(result.videos)
+
this.playlistItems = result.videos
this.isLoading = false
diff --git a/src/renderer/views/Playlist/Playlist.vue b/src/renderer/views/Playlist/Playlist.vue
index 2d93f542a..f4e3c183b 100644
--- a/src/renderer/views/Playlist/Playlist.vue
+++ b/src/renderer/views/Playlist/Playlist.vue
@@ -22,6 +22,9 @@
:view-count="viewCount"
:info-source="infoSource"
:more-video-data-available="moreVideoDataAvailable"
+ :search-video-mode-allowed="searchVideoModeAllowed"
+ :search-video-mode-enabled="playlistInVideoSearchMode"
+ :search-query-text="searchQueryTextRequested"
class="playlistInfo"
:class="{
promptOpen,
diff --git a/src/renderer/views/Popular/Popular.js b/src/renderer/views/Popular/Popular.js
index 1c0dc544a..a85e3eb3d 100644
--- a/src/renderer/views/Popular/Popular.js
+++ b/src/renderer/views/Popular/Popular.js
@@ -5,7 +5,7 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import { invidiousAPICall } from '../../helpers/api/invidious'
-import { copyToClipboard, showToast } from '../../helpers/utils'
+import { copyToClipboard, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
export default defineComponent({
name: 'Popular',
@@ -60,11 +60,15 @@ export default defineComponent({
return
}
- this.shownResults = result.filter((item) => {
+ const items = result.filter((item) => {
return item.type === 'video' || item.type === 'shortVideo' || item.type === 'channel' || item.type === 'playlist'
})
+ setPublishedTimestampsInvidious(items.filter(item => item.type === 'video' || item.type === 'shortVideo'))
+
+ this.shownResults = items
+
this.isLoading = false
- this.$store.commit('setPopularCache', this.shownResults)
+ this.$store.commit('setPopularCache', items)
},
/**
diff --git a/src/renderer/views/Search/Search.js b/src/renderer/views/Search/Search.js
index 4bb5f85ea..0e767e3fd 100644
--- a/src/renderer/views/Search/Search.js
+++ b/src/renderer/views/Search/Search.js
@@ -2,7 +2,12 @@ import { defineComponent } from 'vue'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
-import { copyToClipboard, searchFiltersMatch, showToast } from '../../helpers/utils'
+import {
+ copyToClipboard,
+ searchFiltersMatch,
+ setPublishedTimestampsInvidious,
+ showToast
+} from '../../helpers/utils'
import { getLocalSearchContinuation, getLocalSearchResults } from '../../helpers/api/local'
import { invidiousAPICall } from '../../helpers/api/invidious'
import TopNavEvents from '../../components/top-nav/top-nav-events'
@@ -219,6 +224,8 @@ export default defineComponent({
return item.type === 'video' || item.type === 'channel' || item.type === 'playlist' || item.type === 'hashtag'
})
+ setPublishedTimestampsInvidious(returnData.filter(item => item.type === 'video'))
+
if (this.searchPage !== 1) {
this.shownResults = this.shownResults.concat(returnData)
} else {
diff --git a/src/renderer/views/Trending/Trending.js b/src/renderer/views/Trending/Trending.js
index b84c117db..d239a5a6d 100644
--- a/src/renderer/views/Trending/Trending.js
+++ b/src/renderer/views/Trending/Trending.js
@@ -6,7 +6,7 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
-import { copyToClipboard, showToast } from '../../helpers/utils'
+import { copyToClipboard, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
import { getLocalTrending } from '../../helpers/api/local'
import { invidiousAPICall } from '../../helpers/api/invidious'
@@ -146,6 +146,8 @@ export default defineComponent({
return item.type === 'video' || item.type === 'channel' || item.type === 'playlist'
})
+ setPublishedTimestampsInvidious(returnData.filter(item => item.type === 'video'))
+
this.shownResults = returnData
this.isLoading = false
this.$store.commit('setTrendingCache', { value: returnData, page: this.currentTab })
diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.css b/src/renderer/views/UserPlaylists/UserPlaylists.css
index c7cea712b..d0da46adb 100644
--- a/src/renderer/views/UserPlaylists/UserPlaylists.css
+++ b/src/renderer/views/UserPlaylists/UserPlaylists.css
@@ -16,6 +16,36 @@
vertical-align: middle;
}
+.searchInputsRow {
+ display: grid;
+
+ /* 2 columns */
+ grid-template-columns: 1fr auto;
+ column-gap: 16px;
+}
+@media only screen and (max-width: 800px) {
+ .searchInputsRow {
+ /* Switch to 2 rows from 2 columns */
+ grid-template-columns: auto;
+ grid-template-rows: auto auto;
+ }
+}
+
+.optionsRow {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ grid-template-rows: 1fr;
+ align-items: center;
+}
+@media only screen and (max-width: 800px) {
+ .optionsRow {
+ /* Switch to 2 rows from 2 columns */
+ grid-template-columns: auto;
+ grid-template-rows: auto auto;
+ align-items: stretch;
+ }
+}
+
.sortSelect {
/* Put it on the right */
margin-inline-start: auto;
diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.js b/src/renderer/views/UserPlaylists/UserPlaylists.js
index 95e2d01c8..e4d7f3f28 100644
--- a/src/renderer/views/UserPlaylists/UserPlaylists.js
+++ b/src/renderer/views/UserPlaylists/UserPlaylists.js
@@ -10,6 +10,7 @@ import FtSelect from '../../components/ft-select/ft-select.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtInput from '../../components/ft-input/ft-input.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
+import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
const SORT_BY_VALUES = {
NameAscending: 'name_ascending',
@@ -37,6 +38,7 @@ export default defineComponent({
'ft-element-list': FtElementList,
'ft-icon-button': FtIconButton,
'ft-input': FtInput,
+ 'ft-toggle-switch': FtToggleSwitch,
},
data: function () {
return {
@@ -45,6 +47,7 @@ export default defineComponent({
searchDataLimit: 100,
showLoadMoreButton: false,
query: '',
+ doSearchPlaylistsWithMatchingVideos: false,
activeData: [],
sortBy: SORT_BY_VALUES.LatestPlayedFirst,
}
@@ -165,6 +168,10 @@ export default defineComponent({
this.searchDataLimit = 100
this.filterPlaylistAsync()
},
+ doSearchPlaylistsWithMatchingVideos() {
+ this.searchDataLimit = 100
+ this.filterPlaylistAsync()
+ },
fullData() {
this.activeData = this.fullData
this.filterPlaylist()
@@ -209,15 +216,22 @@ export default defineComponent({
if (this.lowerCaseQuery === '') {
this.activeData = this.fullData
this.showLoadMoreButton = this.allPlaylists.length > this.activeData.length
- } else {
- const filteredPlaylists = this.allPlaylists.filter((playlist) => {
- if (typeof (playlist.playlistName) !== 'string') { return false }
-
- return playlist.playlistName.toLowerCase().includes(this.lowerCaseQuery)
- })
- this.showLoadMoreButton = filteredPlaylists.length > this.searchDataLimit
- this.activeData = filteredPlaylists.length < this.searchDataLimit ? filteredPlaylists : filteredPlaylists.slice(0, this.searchDataLimit)
+ return
}
+
+ const filteredPlaylists = this.allPlaylists.filter((playlist) => {
+ if (typeof (playlist.playlistName) !== 'string') { return false }
+
+ if (this.doSearchPlaylistsWithMatchingVideos) {
+ if (playlist.videos.some((v) => v.title.toLowerCase().includes(this.lowerCaseQuery))) {
+ return true
+ }
+ }
+
+ return playlist.playlistName.toLowerCase().includes(this.lowerCaseQuery)
+ })
+ this.showLoadMoreButton = filteredPlaylists.length > this.searchDataLimit
+ this.activeData = filteredPlaylists.length < this.searchDataLimit ? filteredPlaylists : filteredPlaylists.slice(0, this.searchDataLimit)
},
createNewPlaylist: function () {
diff --git a/src/renderer/views/UserPlaylists/UserPlaylists.vue b/src/renderer/views/UserPlaylists/UserPlaylists.vue
index 3735dfaef..60a444df5 100644
--- a/src/renderer/views/UserPlaylists/UserPlaylists.vue
+++ b/src/renderer/views/UserPlaylists/UserPlaylists.vue
@@ -19,24 +19,39 @@
class="newPlaylistButton"
@click="createNewPlaylist"
/>
- query = input"
- @clear="query = ''"
- />
-
+ class="searchInputsRow"
+ >
+ query = input"
+ @clear="query = ''"
+ />
+
+
+
+
+