* Update top nav search input to direct to channel panel when channel URL provided (#1221)

* $ Extract function which extract details from a Youtube URL

* * Update top nav handling to use extract function to handle input text if it's Youtube URL

* - Remove no longer used function
This commit is contained in:
PikachuEXE 2021-04-29 01:21:16 +08:00 committed by GitHub
parent 754f3d650d
commit 82aeaac734
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 253 additions and 147 deletions

View File

@ -297,61 +297,22 @@ export default Vue.extend({
}, },
handleYoutubeLink: function (href) { handleYoutubeLink: function (href) {
const v = this this.$store.dispatch('getYoutubeUrlInfo', href).then((result) => {
switch (result.urlType) {
case 'video': {
const { videoId, timestamp } = result
// Assume it's a video this.$router.push({
this.$store.dispatch('getVideoParamsFromUrl', href).then(({ videoId, timestamp }) => {
if (videoId) {
v.$router.push({
path: `/watch/${videoId}`, path: `/watch/${videoId}`,
query: timestamp ? { timestamp } : {} query: timestamp ? { timestamp } : {}
}) })
} else {
// Could be a playlist, channel, search query or hashtag
// If it's none of these, do nothing
//
// There's a limitation where some unknown URL types will be
// determined to be channels
// This is due to the ambiguity of some of the existing
// channel URL formats and there's not much that can be
// done to remedy it
const url = new URL(href)
let urlType = 'unknown'
const channelPattern =
/^\/(?:c\/|channel\/|user\/)?([^/]+)(?:\/join)?\/?$/
const typePatterns = new Map([
['playlist', /^\/playlist\/?$/],
['search', /^\/results\/?$/],
['hashtag', /^\/hashtag\/([^/?&#]+)$/],
['channel', channelPattern]
])
for (const [type, pattern] of typePatterns) {
const matchFound = pattern.test(url.pathname)
if (matchFound) {
urlType = type
break break
} }
}
switch (urlType) {
case 'playlist': { case 'playlist': {
if (!url.searchParams.has('list')) { const { playlistId, query } = result
throw new Error('Playlist: "list" field not found')
}
const playlistId = url.searchParams.get('list') this.$router.push({
url.searchParams.delete('list')
const query = {}
for (const [param, value] of url.searchParams) {
query[param] = value
}
v.$router.push({
path: `/playlist/${playlistId}`, path: `/playlist/${playlistId}`,
query query
}) })
@ -359,25 +320,9 @@ export default Vue.extend({
} }
case 'search': { case 'search': {
if (!url.searchParams.has('search_query')) { const { searchQuery, query } = result
throw new Error('Search: "search_query" field not found')
}
const searchQuery = url.searchParams.get('search_query') this.$router.push({
url.searchParams.delete('search_query')
const query = {
sortBy: this.searchSettings.sortBy,
time: this.searchSettings.time,
type: this.searchSettings.type,
duration: this.searchSettings.duration
}
for (const [param, value] of url.searchParams) {
query[param] = value
}
v.$router.push({
path: `/search/${encodeURIComponent(searchQuery)}`, path: `/search/${encodeURIComponent(searchQuery)}`,
query query
}) })
@ -398,17 +343,19 @@ export default Vue.extend({
} }
case 'channel': { case 'channel': {
const channelId = url.pathname.match(channelPattern)[1] const { channelId } = result
if (!channelId) {
throw new Error('Channel: could not extract id')
}
v.$router.push({ this.$router.push({
path: `/channel/${channelId}` path: `/channel/${channelId}`
}) })
break break
} }
case 'invalid_url': {
// Do nothing
break
}
default: { default: {
// Unknown URL type // Unknown URL type
let message = 'Unknown YouTube url type, cannot be opened in app' let message = 'Unknown YouTube url type, cannot be opened in app'
@ -421,7 +368,6 @@ export default Vue.extend({
}) })
} }
} }
}
}) })
}, },

View File

@ -3,7 +3,6 @@ import FtInput from '../ft-input/ft-input.vue'
import FtSearchFilters from '../ft-search-filters/ft-search-filters.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 $ from 'jquery' import $ from 'jquery'
import router from '../../router/index.js'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import ytSuggest from 'youtube-suggest' import ytSuggest from 'youtube-suggest'
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
@ -90,22 +89,63 @@ export default Vue.extend({
searchInput.blur() searchInput.blur()
} }
const { videoId, timestamp } = await this.$store.dispatch('getVideoParamsFromUrl', query) this.$store.dispatch('getYoutubeUrlInfo', query).then((result) => {
const playlistId = await this.$store.dispatch('getPlaylistIdFromUrl', query) switch (result.urlType) {
case 'video': {
const { videoId, timestamp } = result
console.log(playlistId)
if (videoId) {
this.$router.push({ this.$router.push({
path: `/watch/${videoId}`, path: `/watch/${videoId}`,
query: timestamp ? { timestamp } : {} query: timestamp ? { timestamp } : {}
}) })
} else if (playlistId) { break
}
case 'playlist': {
const { playlistId, query } = result
this.$router.push({ this.$router.push({
path: `/playlist/${playlistId}` path: `/playlist/${playlistId}`,
query
}) })
} else { break
router.push({ }
case 'search': {
const { searchQuery, query } = result
this.$router.push({
path: `/search/${encodeURIComponent(searchQuery)}`,
query
})
break
}
case 'hashtag': {
// TODO: Implement a hashtag related view
let message = 'Hashtags have not yet been implemented, try again later'
if (this.$te(message) && this.$t(message) !== '') {
message = this.$t(message)
}
this.showToast({
message: message
})
break
}
case 'channel': {
const { channelId } = result
this.$router.push({
path: `/channel/${channelId}`
})
break
}
case 'invalid_url':
default: {
this.$router.push({
path: `/search/${encodeURIComponent(query)}`, path: `/search/${encodeURIComponent(query)}`,
query: { query: {
sortBy: this.searchSettings.sortBy, sortBy: this.searchSettings.sortBy,
@ -115,7 +155,10 @@ export default Vue.extend({
} }
}) })
} }
}
})
// Close the filter panel
this.showFilters = false this.showFilters = false
}, },

View File

@ -239,25 +239,142 @@ const actions = {
return extractors.reduce((a, c) => a || c(), null) || paramsObject return extractors.reduce((a, c) => a || c(), null) || paramsObject
}, },
getPlaylistIdFromUrl (_, url) { getYoutubeUrlInfo (_, urlStr) {
/** @type {URL} */ // Returns
let urlObject // - urlType [String] `video`, `playlist`
//
// If `urlType` is "video"
// - videoId [String]
// - timestamp [String]
//
// If `urlType` is "playlist"
// - playlistId [String]
// - query [Object]
//
// If `urlType` is "search"
// - searchQuery [String]
// - query [Object]
//
// If `urlType` is "hashtag"
// Nothing else
//
// If `urlType` is "channel"
// - channelId [String]
//
// If `urlType` is "unknown"
// Nothing else
//
// If `urlType` is "invalid_url"
// Nothing else
const { videoId, timestamp } = actions.getVideoParamsFromUrl(null, urlStr)
if (videoId) {
return {
urlType: 'video',
videoId,
timestamp
}
}
let url
try { try {
urlObject = new URL(url) url = new URL(urlStr)
} catch (e) { } catch {
return false return {
urlType: 'invalid_url'
}
}
let urlType = 'unknown'
const channelPattern =
/^\/(?:c\/|channel\/|user\/)?([^/]+)(?:\/join)?\/?$/
const typePatterns = new Map([
['playlist', /^\/playlist\/?$/],
['search', /^\/results\/?$/],
['hashtag', /^\/hashtag\/([^/?&#]+)$/],
['channel', channelPattern]
])
for (const [type, pattern] of typePatterns) {
const matchFound = pattern.test(url.pathname)
if (matchFound) {
urlType = type
break
}
} }
const extractors = [ switch (urlType) {
// anything with /playlist?list= case 'playlist': {
function() { if (!url.searchParams.has('list')) {
if (urlObject.pathname === '/playlist' && urlObject.searchParams.has('list')) { throw new Error('Playlist: "list" field not found')
return urlObject.searchParams.get('list')
} }
}
]
return extractors.reduce((a, c) => a || c(), null) || false const playlistId = url.searchParams.get('list')
url.searchParams.delete('list')
const query = {}
for (const [param, value] of url.searchParams) {
query[param] = value
}
return {
urlType: 'playlist',
playlistId,
query
}
}
case 'search': {
if (!url.searchParams.has('search_query')) {
throw new Error('Search: "search_query" field not found')
}
const searchQuery = url.searchParams.get('search_query')
url.searchParams.delete('search_query')
const query = {
sortBy: this.searchSettings.sortBy,
time: this.searchSettings.time,
type: this.searchSettings.type,
duration: this.searchSettings.duration
}
for (const [param, value] of url.searchParams) {
query[param] = value
}
return {
urlType: 'search',
searchQuery,
query
}
}
case 'hashtag': {
return {
urlType: 'hashtag'
}
}
case 'channel': {
const channelId = url.pathname.match(channelPattern)[1]
if (!channelId) {
throw new Error('Channel: could not extract id')
}
return {
urlType: 'channel',
channelId
}
}
default: {
// Unknown URL type
return {
urlType: 'unknown'
}
}
}
}, },
padNumberWithLeadingZeros(_, payload) { padNumberWithLeadingZeros(_, payload) {