* 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:
parent
754f3d650d
commit
82aeaac734
|
@ -297,129 +297,75 @@ 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 }) => {
|
path: `/watch/${videoId}`,
|
||||||
if (videoId) {
|
query: timestamp ? { timestamp } : {}
|
||||||
v.$router.push({
|
})
|
||||||
path: `/watch/${videoId}`,
|
break
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (urlType) {
|
case 'playlist': {
|
||||||
case 'playlist': {
|
const { playlistId, query } = result
|
||||||
if (!url.searchParams.has('list')) {
|
|
||||||
throw new Error('Playlist: "list" field not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
const playlistId = url.searchParams.get('list')
|
this.$router.push({
|
||||||
url.searchParams.delete('list')
|
path: `/playlist/${playlistId}`,
|
||||||
|
query
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const query = {}
|
case 'search': {
|
||||||
for (const [param, value] of url.searchParams) {
|
const { searchQuery, query } = result
|
||||||
query[param] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
v.$router.push({
|
this.$router.push({
|
||||||
path: `/playlist/${playlistId}`,
|
path: `/search/${encodeURIComponent(searchQuery)}`,
|
||||||
query
|
query
|
||||||
})
|
})
|
||||||
break
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'search': {
|
this.showToast({
|
||||||
if (!url.searchParams.has('search_query')) {
|
message: message
|
||||||
throw new Error('Search: "search_query" field not found')
|
})
|
||||||
}
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const searchQuery = url.searchParams.get('search_query')
|
case 'channel': {
|
||||||
url.searchParams.delete('search_query')
|
const { channelId } = result
|
||||||
|
|
||||||
const query = {
|
this.$router.push({
|
||||||
sortBy: this.searchSettings.sortBy,
|
path: `/channel/${channelId}`
|
||||||
time: this.searchSettings.time,
|
})
|
||||||
type: this.searchSettings.type,
|
break
|
||||||
duration: this.searchSettings.duration
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const [param, value] of url.searchParams) {
|
case 'invalid_url': {
|
||||||
query[param] = value
|
// Do nothing
|
||||||
}
|
break
|
||||||
|
}
|
||||||
|
|
||||||
v.$router.push({
|
default: {
|
||||||
path: `/search/${encodeURIComponent(searchQuery)}`,
|
// Unknown URL type
|
||||||
query
|
let message = 'Unknown YouTube url type, cannot be opened in app'
|
||||||
})
|
if (this.$te(message) && this.$t(message) !== '') {
|
||||||
break
|
message = this.$t(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'hashtag': {
|
this.showToast({
|
||||||
// TODO: Implement a hashtag related view
|
message: message
|
||||||
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 = url.pathname.match(channelPattern)[1]
|
|
||||||
if (!channelId) {
|
|
||||||
throw new Error('Channel: could not extract id')
|
|
||||||
}
|
|
||||||
|
|
||||||
v.$router.push({
|
|
||||||
path: `/channel/${channelId}`
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
// Unknown URL type
|
|
||||||
let message = 'Unknown YouTube url type, cannot be opened in app'
|
|
||||||
if (this.$te(message) && this.$t(message) !== '') {
|
|
||||||
message = this.$t(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showToast({
|
|
||||||
message: message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,32 +89,76 @@ 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)
|
this.$router.push({
|
||||||
|
path: `/watch/${videoId}`,
|
||||||
if (videoId) {
|
query: timestamp ? { timestamp } : {}
|
||||||
this.$router.push({
|
})
|
||||||
path: `/watch/${videoId}`,
|
break
|
||||||
query: timestamp ? { timestamp } : {}
|
|
||||||
})
|
|
||||||
} else if (playlistId) {
|
|
||||||
this.$router.push({
|
|
||||||
path: `/playlist/${playlistId}`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
path: `/search/${encodeURIComponent(query)}`,
|
|
||||||
query: {
|
|
||||||
sortBy: this.searchSettings.sortBy,
|
|
||||||
time: this.searchSettings.time,
|
|
||||||
type: this.searchSettings.type,
|
|
||||||
duration: this.searchSettings.duration
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
case 'playlist': {
|
||||||
|
const { playlistId, query } = result
|
||||||
|
|
||||||
|
this.$router.push({
|
||||||
|
path: `/playlist/${playlistId}`,
|
||||||
|
query
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}`,
|
||||||
|
query: {
|
||||||
|
sortBy: this.searchSettings.sortBy,
|
||||||
|
time: this.searchSettings.time,
|
||||||
|
type: this.searchSettings.type,
|
||||||
|
duration: this.searchSettings.duration
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close the filter panel
|
||||||
this.showFilters = false
|
this.showFilters = false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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`
|
||||||
try {
|
//
|
||||||
urlObject = new URL(url)
|
// If `urlType` is "video"
|
||||||
} catch (e) {
|
// - videoId [String]
|
||||||
return false
|
// - 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractors = [
|
let url
|
||||||
// anything with /playlist?list=
|
try {
|
||||||
function() {
|
url = new URL(urlStr)
|
||||||
if (urlObject.pathname === '/playlist' && urlObject.searchParams.has('list')) {
|
} catch {
|
||||||
return urlObject.searchParams.get('list')
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (urlType) {
|
||||||
|
case 'playlist': {
|
||||||
|
if (!url.searchParams.has('list')) {
|
||||||
|
throw new Error('Playlist: "list" field not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
|
||||||
return extractors.reduce((a, c) => a || c(), null) || false
|
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) {
|
||||||
|
|
Loading…
Reference in New Issue