mirror of https://github.com/FreeTubeApp/FreeTube
Fix handling of video published date in video lists (#4752)
* Fix handling of video published date in video lists * Use same date format on the history page as before * Switch to months at 30 days instead of 32 and correct thresholds * Add support for formatting as weeks * According to Invidious everything on the popular tab is a short???
This commit is contained in:
parent
dc3f73e5bc
commit
98aded9701
|
@ -7,7 +7,6 @@ import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
openExternalLink,
|
openExternalLink,
|
||||||
showToast,
|
showToast,
|
||||||
toLocalePublicationString,
|
|
||||||
toDistractionFreeTitle,
|
toDistractionFreeTitle,
|
||||||
deepCopy
|
deepCopy
|
||||||
} from '../../helpers/utils'
|
} from '../../helpers/utils'
|
||||||
|
@ -98,7 +97,7 @@ export default defineComponent({
|
||||||
duration: '',
|
duration: '',
|
||||||
description: '',
|
description: '',
|
||||||
watchProgress: 0,
|
watchProgress: 0,
|
||||||
publishedText: '',
|
published: undefined,
|
||||||
isLive: false,
|
isLive: false,
|
||||||
isUpcoming: false,
|
isUpcoming: false,
|
||||||
isPremium: false,
|
isPremium: false,
|
||||||
|
@ -657,60 +656,60 @@ export default defineComponent({
|
||||||
if (typeof premiereDate === 'string') {
|
if (typeof premiereDate === 'string') {
|
||||||
premiereDate = new Date(premiereDate)
|
premiereDate = new Date(premiereDate)
|
||||||
}
|
}
|
||||||
this.publishedText = premiereDate.toLocaleString()
|
this.uploadedTime = premiereDate.toLocaleString([this.currentLocale, 'en'])
|
||||||
|
this.published = premiereDate.getTime()
|
||||||
} else if (typeof (this.data.premiereTimestamp) !== 'undefined') {
|
} else if (typeof (this.data.premiereTimestamp) !== 'undefined') {
|
||||||
this.publishedText = new Date(this.data.premiereTimestamp * 1000).toLocaleString()
|
this.uploadedTime = new Date(this.data.premiereTimestamp * 1000).toLocaleString([this.currentLocale, 'en'])
|
||||||
} else {
|
this.published = this.data.premiereTimestamp * 1000
|
||||||
this.publishedText = this.data.publishedText
|
} else if (typeof this.data.published === 'number' && !this.isLive) {
|
||||||
}
|
this.published = this.data.published
|
||||||
|
|
||||||
if (this.data.isRSS && this.data.publishedDate != null && !this.isLive) {
|
if (this.inHistory) {
|
||||||
const now = new Date()
|
this.uploadedTime = new Date(this.data.published).toLocaleDateString([this.currentLocale, 'en'])
|
||||||
// Convert from ms to second
|
} else {
|
||||||
// For easier code interpretation the value is made to be positive
|
const now = new Date().getTime()
|
||||||
// `publishedDate` is sometimes a string, e.g. when switched back from another view
|
// Convert from ms to second
|
||||||
const publishedDate = Date.parse(this.data.publishedDate)
|
// For easier code interpretation the value is made to be positive
|
||||||
let timeDiffFromNow = ((now - publishedDate) / 1000)
|
let timeDiffFromNow = ((now - this.data.published) / 1000)
|
||||||
let timeUnit = 'second'
|
let timeUnit = 'second'
|
||||||
|
|
||||||
if (timeDiffFromNow > 60) {
|
if (timeDiffFromNow >= 60) {
|
||||||
timeDiffFromNow /= 60
|
timeDiffFromNow /= 60
|
||||||
timeUnit = 'minute'
|
timeUnit = 'minute'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeUnit === 'minute' && timeDiffFromNow >= 60) {
|
||||||
|
timeDiffFromNow /= 60
|
||||||
|
timeUnit = 'hour'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeUnit === 'hour' && timeDiffFromNow >= 24) {
|
||||||
|
timeDiffFromNow /= 24
|
||||||
|
timeUnit = 'day'
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeDiffFromNowDays = timeDiffFromNow
|
||||||
|
|
||||||
|
if (timeUnit === 'day' && timeDiffFromNow >= 7) {
|
||||||
|
timeDiffFromNow /= 7
|
||||||
|
timeUnit = 'week'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use 30 days per month, just like calculatePublishedDate
|
||||||
|
if (timeUnit === 'week' && timeDiffFromNowDays >= 30) {
|
||||||
|
timeDiffFromNow = timeDiffFromNowDays / 30
|
||||||
|
timeUnit = 'month'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeUnit === 'month' && timeDiffFromNow >= 12) {
|
||||||
|
timeDiffFromNow /= 12
|
||||||
|
timeUnit = 'year'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using `Math.ceil` so that -1.x days ago displayed as 1 day ago
|
||||||
|
// Notice that the value is turned to negative to be displayed as "ago"
|
||||||
|
this.uploadedTime = new Intl.RelativeTimeFormat([this.currentLocale, 'en']).format(Math.ceil(-timeDiffFromNow), timeUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeUnit === 'minute' && timeDiffFromNow > 60) {
|
|
||||||
timeDiffFromNow /= 60
|
|
||||||
timeUnit = 'hour'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeUnit === 'hour' && timeDiffFromNow > 24) {
|
|
||||||
timeDiffFromNow /= 24
|
|
||||||
timeUnit = 'day'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff month might have diff no. of days
|
|
||||||
// To ensure the display is fine we use 31
|
|
||||||
if (timeUnit === 'day' && timeDiffFromNow > 31) {
|
|
||||||
timeDiffFromNow /= 24
|
|
||||||
timeUnit = 'month'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeUnit === 'month' && timeDiffFromNow > 12) {
|
|
||||||
timeDiffFromNow /= 12
|
|
||||||
timeUnit = 'year'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using `Math.ceil` so that -1.x days ago displayed as 1 day ago
|
|
||||||
// Notice that the value is turned to negative to be displayed as "ago"
|
|
||||||
this.uploadedTime = new Intl.RelativeTimeFormat(this.currentLocale).format(Math.ceil(-timeDiffFromNow), timeUnit)
|
|
||||||
} else if (this.publishedText && !this.isLive) {
|
|
||||||
// produces a string according to the template in the locales string
|
|
||||||
this.uploadedTime = toLocalePublicationString({
|
|
||||||
publishText: this.publishedText,
|
|
||||||
isLive: this.isLive,
|
|
||||||
isUpcoming: this.isUpcoming,
|
|
||||||
isRSS: this.data.isRSS
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hideVideoViews) {
|
if (this.hideVideoViews) {
|
||||||
|
@ -732,14 +731,6 @@ export default defineComponent({
|
||||||
// For UX consistency, no progress reading if writing disabled
|
// For UX consistency, no progress reading if writing disabled
|
||||||
this.watchProgress = historyEntry.watchProgress
|
this.watchProgress = historyEntry.watchProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historyEntry.published !== '') {
|
|
||||||
const videoPublished = historyEntry.published
|
|
||||||
const videoPublishedDate = new Date(videoPublished)
|
|
||||||
this.publishedText = videoPublishedDate.toLocaleDateString()
|
|
||||||
} else {
|
|
||||||
this.publishedText = ''
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.watchProgress = 0
|
this.watchProgress = 0
|
||||||
}
|
}
|
||||||
|
@ -751,7 +742,7 @@ export default defineComponent({
|
||||||
title: this.title,
|
title: this.title,
|
||||||
author: this.channelName,
|
author: this.channelName,
|
||||||
authorId: this.channelId,
|
authorId: this.channelId,
|
||||||
published: this.publishedText ? this.publishedText.split(',')[0] : this.publishedText,
|
published: this.published,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
viewCount: this.viewCount,
|
viewCount: this.viewCount,
|
||||||
lengthSeconds: this.data.lengthSeconds,
|
lengthSeconds: this.data.lengthSeconds,
|
||||||
|
|
|
@ -137,13 +137,9 @@
|
||||||
{{ $tc('Global.Counts.View Count', viewCount, {count: parsedViewCount}) }}
|
{{ $tc('Global.Counts.View Count', viewCount, {count: parsedViewCount}) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="uploadedTime !== '' && !isLive && !inHistory"
|
v-if="uploadedTime !== '' && !isLive"
|
||||||
class="uploadedTime"
|
class="uploadedTime"
|
||||||
> • {{ uploadedTime }}</span>
|
> • {{ uploadedTime }}</span>
|
||||||
<span
|
|
||||||
v-if="inHistory"
|
|
||||||
class="uploadedTime"
|
|
||||||
> • {{ publishedText }}</span>
|
|
||||||
<span
|
<span
|
||||||
v-if="isLive && !hideViews"
|
v-if="isLive && !hideViews"
|
||||||
class="viewCount"
|
class="viewCount"
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { defineComponent } from 'vue'
|
||||||
import { mapActions, mapMutations } from 'vuex'
|
import { mapActions, mapMutations } from 'vuex'
|
||||||
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
|
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 { invidiousAPICall } from '../../helpers/api/invidious'
|
||||||
import { getLocalChannelLiveStreams } from '../../helpers/api/local'
|
import { getLocalChannelLiveStreams } from '../../helpers/api/local'
|
||||||
import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
import { parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SubscriptionsLive',
|
name: 'SubscriptionsLive',
|
||||||
|
@ -198,8 +198,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addPublishedDatesLocal(result.videos)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -294,7 +292,7 @@ export default defineComponent({
|
||||||
invidiousAPICall(subscriptionsPayload).then((result) => {
|
invidiousAPICall(subscriptionsPayload).then((result) => {
|
||||||
const videos = result.videos.filter(e => e.type === 'video')
|
const videos = result.videos.filter(e => e.type === 'video')
|
||||||
|
|
||||||
addPublishedDatesInvidious(videos)
|
setPublishedTimestampsInvidious(videos)
|
||||||
|
|
||||||
let name
|
let name
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { defineComponent } from 'vue'
|
||||||
import { mapActions, mapMutations } from 'vuex'
|
import { mapActions, mapMutations } from 'vuex'
|
||||||
import SubscriptionsTabUI from '../subscriptions-tab-ui/subscriptions-tab-ui.vue'
|
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 { invidiousAPICall } from '../../helpers/api/invidious'
|
||||||
import { getLocalChannelVideos } from '../../helpers/api/local'
|
import { getLocalChannelVideos } from '../../helpers/api/local'
|
||||||
import { addPublishedDatesInvidious, addPublishedDatesLocal, parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
import { parseYouTubeRSSFeed, updateVideoListAfterProcessing } from '../../helpers/subscriptions'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SubscriptionsVideos',
|
name: 'SubscriptionsVideos',
|
||||||
|
@ -198,8 +198,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addPublishedDatesLocal(result.videos)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -291,7 +289,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
invidiousAPICall(subscriptionsPayload).then((result) => {
|
invidiousAPICall(subscriptionsPayload).then((result) => {
|
||||||
addPublishedDatesInvidious(result.videos)
|
setPublishedTimestampsInvidious(result.videos)
|
||||||
|
|
||||||
let name
|
let name
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { mapMutations } from 'vuex'
|
||||||
import FtLoader from '../ft-loader/ft-loader.vue'
|
import FtLoader from '../ft-loader/ft-loader.vue'
|
||||||
import FtCard from '../ft-card/ft-card.vue'
|
import FtCard from '../ft-card/ft-card.vue'
|
||||||
import FtListVideoNumbered from '../ft-list-video-numbered/ft-list-video-numbered.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 {
|
import {
|
||||||
getLocalPlaylist,
|
getLocalPlaylist,
|
||||||
parseLocalPlaylistVideo,
|
parseLocalPlaylistVideo,
|
||||||
|
@ -451,6 +451,8 @@ export default defineComponent({
|
||||||
this.playlistTitle = result.title
|
this.playlistTitle = result.title
|
||||||
this.channelName = result.author
|
this.channelName = result.author
|
||||||
this.channelId = result.authorId
|
this.channelId = result.authorId
|
||||||
|
|
||||||
|
setPublishedTimestampsInvidious(result.videos)
|
||||||
this.playlistItems = this.playlistItems.concat(result.videos)
|
this.playlistItems = this.playlistItems.concat(result.videos)
|
||||||
|
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { join } from 'path'
|
||||||
import { PlayerCache } from './PlayerCache'
|
import { PlayerCache } from './PlayerCache'
|
||||||
import {
|
import {
|
||||||
CHANNEL_HANDLE_REGEX,
|
CHANNEL_HANDLE_REGEX,
|
||||||
|
calculatePublishedDate,
|
||||||
escapeHTML,
|
escapeHTML,
|
||||||
extractNumberFromString,
|
extractNumberFromString,
|
||||||
getUserDataPath,
|
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
|
// 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
|
// 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,
|
// 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
|
publishedText = video_.video_info.runs[2].text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const published = calculatePublishedDate(
|
||||||
|
publishedText,
|
||||||
|
video_.is_live,
|
||||||
|
video_.is_upcoming,
|
||||||
|
video_.upcoming
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
videoId: video_.id,
|
videoId: video_.id,
|
||||||
title: video_.title.text,
|
title: video_.title.text,
|
||||||
author: video_.author.name,
|
author: video_.author.name,
|
||||||
authorId: video_.author.id,
|
authorId: video_.author.id,
|
||||||
viewCount,
|
viewCount,
|
||||||
publishedText,
|
published,
|
||||||
lengthSeconds: isNaN(video_.duration.seconds) ? '' : video_.duration.seconds,
|
lengthSeconds: isNaN(video_.duration.seconds) ? '' : video_.duration.seconds,
|
||||||
liveNow: video_.is_live,
|
liveNow: video_.is_live,
|
||||||
isUpcoming: video_.is_upcoming,
|
isUpcoming: video_.is_upcoming,
|
||||||
|
@ -728,6 +735,20 @@ export function parseLocalListVideo(item) {
|
||||||
} else {
|
} else {
|
||||||
/** @type {import('youtubei.js').YTNodes.Video} */
|
/** @type {import('youtubei.js').YTNodes.Video} */
|
||||||
const video = item
|
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 {
|
return {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
|
@ -736,7 +757,7 @@ export function parseLocalListVideo(item) {
|
||||||
authorId: video.author.id,
|
authorId: video.author.id,
|
||||||
description: video.description,
|
description: video.description,
|
||||||
viewCount: video.view_count == null ? null : extractNumberFromString(video.view_count.text),
|
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,
|
lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
|
||||||
liveNow: video.is_live,
|
liveNow: video.is_live,
|
||||||
isUpcoming: video.is_upcoming || video.is_premiere,
|
isUpcoming: video.is_upcoming || video.is_premiere,
|
||||||
|
@ -811,6 +832,14 @@ function parseListItem(item) {
|
||||||
* @param {import('youtubei.js').YTNodes.CompactVideo} video
|
* @param {import('youtubei.js').YTNodes.CompactVideo} video
|
||||||
*/
|
*/
|
||||||
export function parseLocalWatchNextVideo(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 {
|
return {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
|
@ -818,7 +847,7 @@ export function parseLocalWatchNextVideo(video) {
|
||||||
author: video.author.name,
|
author: video.author.name,
|
||||||
authorId: video.author.id,
|
authorId: video.author.id,
|
||||||
viewCount: video.view_count == null ? null : extractNumberFromString(video.view_count.text),
|
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,
|
lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
|
||||||
liveNow: video.is_live,
|
liveNow: video.is_live,
|
||||||
isUpcoming: video.is_premiere
|
isUpcoming: video.is_premiere
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import store from '../store/index'
|
import store from '../store/index'
|
||||||
import { calculatePublishedDate } from './utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filtering and sort based on user preferences
|
* Filtering and sort based on user preferences
|
||||||
|
@ -57,7 +56,7 @@ export function updateVideoListAfterProcessing(videos) {
|
||||||
}
|
}
|
||||||
|
|
||||||
videoList.sort((a, b) => {
|
videoList.sort((a, b) => {
|
||||||
return b.publishedDate - a.publishedDate
|
return b.published - a.published
|
||||||
})
|
})
|
||||||
|
|
||||||
return videoList
|
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
|
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
|
||||||
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
|
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
|
||||||
title: entry.querySelector('title').textContent,
|
title: entry.querySelector('title').textContent,
|
||||||
publishedDate: published,
|
published: published.getTime(),
|
||||||
publishedText: published.toLocaleString(),
|
|
||||||
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
|
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
|
||||||
type: 'video',
|
type: 'video',
|
||||||
lengthSeconds: '0:00',
|
lengthSeconds: '0:00',
|
||||||
isRSS: true
|
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,11 +12,27 @@ export const CHANNEL_HANDLE_REGEX = /^@[\w.-]{3,30}$/
|
||||||
const PUBLISHED_TEXT_REGEX = /(\d+)\s?([a-z]+)/i
|
const PUBLISHED_TEXT_REGEX = /(\d+)\s?([a-z]+)/i
|
||||||
/**
|
/**
|
||||||
* @param {string} publishedText
|
* @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()
|
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)
|
const match = publishedText.match(PUBLISHED_TEXT_REGEX)
|
||||||
|
@ -44,6 +60,26 @@ export function calculatePublishedDate(publishedText) {
|
||||||
return date.getTime() - timeSpan
|
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 }) {
|
export function toLocalePublicationString ({ publishText, isLive = false, isUpcoming = false, isRSS = false }) {
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
return i18n.tc('Global.Counts.Watching Count', 0, { count: 0 })
|
return i18n.tc('Global.Counts.Watching Count', 0, { count: 0 })
|
||||||
|
|
|
@ -12,7 +12,13 @@ import FtSubscribeButton from '../../components/ft-subscribe-button/ft-subscribe
|
||||||
import ChannelAbout from '../../components/channel-about/channel-about.vue'
|
import ChannelAbout from '../../components/channel-about/channel-about.vue'
|
||||||
|
|
||||||
import autolinker from 'autolinker'
|
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 { isNullOrEmpty } from '../../helpers/strings'
|
||||||
import packageDetails from '../../../../package.json'
|
import packageDetails from '../../../../package.json'
|
||||||
import {
|
import {
|
||||||
|
@ -33,10 +39,6 @@ import {
|
||||||
parseLocalListVideo,
|
parseLocalListVideo,
|
||||||
parseLocalSubscriberCount
|
parseLocalSubscriberCount
|
||||||
} from '../../helpers/api/local'
|
} from '../../helpers/api/local'
|
||||||
import {
|
|
||||||
addPublishedDatesInvidious,
|
|
||||||
addPublishedDatesLocal
|
|
||||||
} from '../../helpers/subscriptions'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Channel',
|
name: 'Channel',
|
||||||
|
@ -780,7 +782,6 @@ export default defineComponent({
|
||||||
this.isElementListLoading = false
|
this.isElementListLoading = false
|
||||||
|
|
||||||
if (this.isSubscribedInAnyProfile && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
|
if (this.isSubscribedInAnyProfile && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
|
||||||
addPublishedDatesLocal(this.latestVideos)
|
|
||||||
this.updateSubscriptionVideosCacheByChannel({
|
this.updateSubscriptionVideosCacheByChannel({
|
||||||
channelId: this.id,
|
channelId: this.id,
|
||||||
// create a copy so that we only cache the first page
|
// create a copy so that we only cache the first page
|
||||||
|
@ -917,7 +918,6 @@ export default defineComponent({
|
||||||
this.isElementListLoading = false
|
this.isElementListLoading = false
|
||||||
|
|
||||||
if (this.isSubscribedInAnyProfile && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
|
if (this.isSubscribedInAnyProfile && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
|
||||||
addPublishedDatesLocal(this.latestLive)
|
|
||||||
this.updateSubscriptionLiveCacheByChannel({
|
this.updateSubscriptionLiveCacheByChannel({
|
||||||
channelId: this.id,
|
channelId: this.id,
|
||||||
// create a copy so that we only cache the first page
|
// create a copy so that we only cache the first page
|
||||||
|
@ -992,7 +992,6 @@ export default defineComponent({
|
||||||
thumbnailUrl: youtubeImageUrlToInvidious(thumbnailUrl, this.currentInvidiousInstance)
|
thumbnailUrl: youtubeImageUrlToInvidious(thumbnailUrl, this.currentInvidiousInstance)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.latestVideos = response.latestVideos
|
|
||||||
|
|
||||||
if (response.authorBanners instanceof Array && response.authorBanners.length > 0) {
|
if (response.authorBanners instanceof Array && response.authorBanners.length > 0) {
|
||||||
this.bannerUrl = youtubeImageUrlToInvidious(response.authorBanners[0].url, this.currentInvidiousInstance)
|
this.bannerUrl = youtubeImageUrlToInvidious(response.authorBanners[0].url, this.currentInvidiousInstance)
|
||||||
|
@ -1088,6 +1087,8 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
invidiousAPICall(payload).then((response) => {
|
invidiousAPICall(payload).then((response) => {
|
||||||
|
setPublishedTimestampsInvidious(response.videos)
|
||||||
|
|
||||||
if (more) {
|
if (more) {
|
||||||
this.latestVideos = this.latestVideos.concat(response.videos)
|
this.latestVideos = this.latestVideos.concat(response.videos)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1097,7 +1098,6 @@ export default defineComponent({
|
||||||
this.isElementListLoading = false
|
this.isElementListLoading = false
|
||||||
|
|
||||||
if (this.isSubscribedInAnyProfile && !more && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
|
if (this.isSubscribedInAnyProfile && !more && this.latestVideos.length > 0 && this.videoSortBy === 'newest') {
|
||||||
addPublishedDatesInvidious(this.latestVideos)
|
|
||||||
this.updateSubscriptionVideosCacheByChannel({
|
this.updateSubscriptionVideosCacheByChannel({
|
||||||
channelId: this.id,
|
channelId: this.id,
|
||||||
// create a copy so that we only cache the first page
|
// 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
|
// https://github.com/iv-org/invidious/issues/3801
|
||||||
response.videos.forEach(video => {
|
response.videos.forEach(video => {
|
||||||
video.isUpcoming = false
|
video.isUpcoming = false
|
||||||
delete video.publishedText
|
delete video.published
|
||||||
delete video.premiereTimestamp
|
delete video.premiereTimestamp
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1199,6 +1199,8 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
invidiousAPICall(payload).then((response) => {
|
invidiousAPICall(payload).then((response) => {
|
||||||
|
setPublishedTimestampsInvidious(response.videos)
|
||||||
|
|
||||||
if (more) {
|
if (more) {
|
||||||
this.latestLive.push(...response.videos)
|
this.latestLive.push(...response.videos)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1208,7 +1210,6 @@ export default defineComponent({
|
||||||
this.isElementListLoading = false
|
this.isElementListLoading = false
|
||||||
|
|
||||||
if (this.isSubscribedInAnyProfile && !more && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
|
if (this.isSubscribedInAnyProfile && !more && this.latestLive.length > 0 && this.liveSortBy === 'newest') {
|
||||||
addPublishedDatesInvidious(this.latestLive)
|
|
||||||
this.updateSubscriptionLiveCacheByChannel({
|
this.updateSubscriptionLiveCacheByChannel({
|
||||||
channelId: this.id,
|
channelId: this.id,
|
||||||
// create a copy so that we only cache the first page
|
// create a copy so that we only cache the first page
|
||||||
|
|
|
@ -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 FtLoader from '../../components/ft-loader/ft-loader.vue'
|
||||||
import packageDetails from '../../../../package.json'
|
import packageDetails from '../../../../package.json'
|
||||||
import { getHashtagLocal, parseLocalListVideo } from '../../helpers/api/local'
|
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 { isNullOrEmpty } from '../../helpers/strings'
|
||||||
import { getHashtagInvidious } from '../../helpers/api/invidious'
|
import { getHashtagInvidious } from '../../helpers/api/invidious'
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ export default defineComponent({
|
||||||
getInvidiousHashtag: async function(hashtag, page) {
|
getInvidiousHashtag: async function(hashtag, page) {
|
||||||
try {
|
try {
|
||||||
const videos = await getHashtagInvidious(hashtag, page)
|
const videos = await getHashtagInvidious(hashtag, page)
|
||||||
|
setPublishedTimestampsInvidious(videos)
|
||||||
this.hashtag = '#' + hashtag
|
this.hashtag = '#' + hashtag
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
this.apiUsed = 'invidious'
|
this.apiUsed = 'invidious'
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
getLocalPlaylistContinuation,
|
getLocalPlaylistContinuation,
|
||||||
parseLocalPlaylistVideo,
|
parseLocalPlaylistVideo,
|
||||||
} from '../../helpers/api/local'
|
} from '../../helpers/api/local'
|
||||||
import { extractNumberFromString, showToast } from '../../helpers/utils'
|
import { extractNumberFromString, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
|
||||||
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
|
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -284,6 +284,8 @@ export default defineComponent({
|
||||||
const dateString = new Date(result.updated * 1000)
|
const dateString = new Date(result.updated * 1000)
|
||||||
this.lastUpdated = dateString.toLocaleDateString(this.currentLocale, { year: 'numeric', month: 'short', day: 'numeric' })
|
this.lastUpdated = dateString.toLocaleDateString(this.currentLocale, { year: 'numeric', month: 'short', day: 'numeric' })
|
||||||
|
|
||||||
|
setPublishedTimestampsInvidious(result.videos)
|
||||||
|
|
||||||
this.playlistItems = result.videos
|
this.playlistItems = result.videos
|
||||||
|
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
|
|
|
@ -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 FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||||
|
|
||||||
import { invidiousAPICall } from '../../helpers/api/invidious'
|
import { invidiousAPICall } from '../../helpers/api/invidious'
|
||||||
import { copyToClipboard, showToast } from '../../helpers/utils'
|
import { copyToClipboard, setPublishedTimestampsInvidious, showToast } from '../../helpers/utils'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Popular',
|
name: 'Popular',
|
||||||
|
@ -60,11 +60,15 @@ export default defineComponent({
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shownResults = result.filter((item) => {
|
const items = result.filter((item) => {
|
||||||
return item.type === 'video' || item.type === 'shortVideo' || item.type === 'channel' || item.type === 'playlist'
|
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.isLoading = false
|
||||||
this.$store.commit('setPopularCache', this.shownResults)
|
this.$store.commit('setPopularCache', items)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,7 +2,12 @@ import { defineComponent } from 'vue'
|
||||||
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
||||||
import FtCard from '../../components/ft-card/ft-card.vue'
|
import FtCard from '../../components/ft-card/ft-card.vue'
|
||||||
import FtElementList from '../../components/ft-element-list/ft-element-list.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 { getLocalSearchContinuation, getLocalSearchResults } from '../../helpers/api/local'
|
||||||
import { invidiousAPICall } from '../../helpers/api/invidious'
|
import { invidiousAPICall } from '../../helpers/api/invidious'
|
||||||
|
|
||||||
|
@ -214,6 +219,8 @@ export default defineComponent({
|
||||||
return item.type === 'video' || item.type === 'channel' || item.type === 'playlist' || item.type === 'hashtag'
|
return item.type === 'video' || item.type === 'channel' || item.type === 'playlist' || item.type === 'hashtag'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setPublishedTimestampsInvidious(returnData.filter(item => item.type === 'video'))
|
||||||
|
|
||||||
if (this.searchPage !== 1) {
|
if (this.searchPage !== 1) {
|
||||||
this.shownResults = this.shownResults.concat(returnData)
|
this.shownResults = this.shownResults.concat(returnData)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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 FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||||
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.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 { getLocalTrending } from '../../helpers/api/local'
|
||||||
import { invidiousAPICall } from '../../helpers/api/invidious'
|
import { invidiousAPICall } from '../../helpers/api/invidious'
|
||||||
|
|
||||||
|
@ -146,6 +146,8 @@ export default defineComponent({
|
||||||
return item.type === 'video' || item.type === 'channel' || item.type === 'playlist'
|
return item.type === 'video' || item.type === 'channel' || item.type === 'playlist'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setPublishedTimestampsInvidious(returnData.filter(item => item.type === 'video'))
|
||||||
|
|
||||||
this.shownResults = returnData
|
this.shownResults = returnData
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
this.$store.commit('setTrendingCache', { value: returnData, page: this.currentTab })
|
this.$store.commit('setTrendingCache', { value: returnData, page: this.currentTab })
|
||||||
|
|
Loading…
Reference in New Issue