Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TextEncoder } from 'util'
import { clearImmediate } from 'timers'

global.TextEncoder = TextEncoder
global.clearImmediate = clearImmediate
28 changes: 27 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@
"email": "[email protected]",
"url": "https://github.com/FreeTubeApp/FreeTube"
},
"jest": {
"testEnvironment": "jsdom",
"moduleNameMapper": {
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
},
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"setupFiles": [
"./jest-setup.js"
],
"transform": {
"^[^.]+.vue$": "@vue/vue2-jest",
".*\\.(js)$": "babel-jest"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/FreeTubeApp/FreeTube.git"
Expand Down Expand Up @@ -48,6 +66,7 @@
"prettier": "prettier --write \"{src,_scripts}/**/*.{js,vue}\"",
"rebuild:electron": "electron-builder install-app-deps",
"release": "run-s test build",
"test": "npx jest test",
"ci": "yarn install --silent --frozen-lockfile"
},
"dependencies": {
Expand Down Expand Up @@ -76,14 +95,17 @@
"vue-router": "^3.6.5",
"vuex": "^3.6.2",
"youtubei.js": "^2.9.0",
"yt-channel-info": "^3.2.1"
"yt-channel-info": "freetubeapp/yt-channel-info#1e80fc79bdc86355ca48425f03bc8a64d7451133"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.20.2",
"@double-great/stylelint-a11y": "^2.0.2",
"@vue/test-utils": "1",
"@vue/vue2-jest": "29",
"babel-jest": "29.x",
"babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
Expand All @@ -102,6 +124,8 @@
"eslint-plugin-vue": "^9.9.0",
"eslint-plugin-vuejs-accessibility": "^2.1.0",
"html-webpack-plugin": "^5.3.2",
"jest": "29.x",
"jest-environment-jsdom": "^29.4.1",
"js-yaml": "^4.1.0",
"json-minimizer-webpack-plugin": "^4.0.0",
"lefthook": "^1.2.8",
Expand All @@ -113,6 +137,7 @@
"rimraf": "^4.1.2",
"sass": "^1.58.0",
"sass-loader": "^13.2.0",
"sinon": "^15.0.1",
"stylelint": "^14.16.1",
"stylelint-config-sass-guidelines": "^9.0.1",
"stylelint-config-standard": "^29.0.0",
Expand All @@ -121,6 +146,7 @@
"vue-devtools": "^5.1.4",
"vue-eslint-parser": "^9.1.0",
"vue-loader": "^15.10.0",
"vue-template-compiler": "^2.x",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ft-list-video/ft-list-video.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ export default defineComponent({
this.publishedText = this.data.publishedText
}

if (typeof (this.data.publishedText) !== 'undefined' && this.data.publishedText !== null && !this.isLive) {
if (this.data.publishedText && !this.isLive) {
// produces a string according to the template in the locales string
this.uploadedTime = toLocalePublicationString({
publishText: this.publishedText,
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/helpers/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as invidious from './invidious'
import * as local from './local'

export const channelShorts = async (api, channelId, idType, sortBy) => {
const shortsFunc = api === 'invidious' ? invidious.channelShortsInvidious : local.channelShortsLocal
const result = await shortsFunc(channelId, idType, sortBy)
return api === 'invidious' ? invidious.parseShortsResponse(result) : local.parseShortsResponse(result)
}
Comment on lines +1 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not a fan of this change, as it will have to be reverted when #3143 is merged as YouTube.js works differently to yt-channel-info.

16 changes: 16 additions & 0 deletions src/renderer/helpers/api/invidious.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,19 @@ function parseInvidiousCommentData(response) {
return comment
})
}

export function parseShortsResponse (response) {
return {
shorts: response.videos,
continuationString: response.continuation
}
}

export async function channelShortsInvidious (channelId, idType, sortBy = 'newest') {
const payload = {
resource: `channels/${channelId}/shorts`,
id: '',
params: { sortBy }
}
return exports.invidiousAPICall(payload)
}
14 changes: 14 additions & 0 deletions src/renderer/helpers/api/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getUserDataPath,
toLocalePublicationString
} from '../utils'
import ytch from 'yt-channel-info'

/**
* Creates a lightweight Innertube instance, which is faster to create or
Expand Down Expand Up @@ -543,3 +544,16 @@ export function filterFormats(formats, allowAv1 = false) {
return [...audioFormats, ...h264Formats]
}
}

export function parseShortsResponse (response) {
return {
shorts: response.items,
continuationString: response.continuation
}
}

export async function channelShortsLocal (channelId, idType, sortBy) {
return await ytch.getChannelShorts(
{ channelId, channelIdType: idType, sortBy: sortBy }
)
}
31 changes: 17 additions & 14 deletions src/renderer/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -595,23 +595,26 @@ export function getVideoParamsFromUrl(url) {
* @param {number} minUpperCase the minimum number of consecutive upper case characters to match
* @returns {string} the title with upper case characters removed
*/
export function toDistractionFreeTitle(title, minUpperCase = 3) {
const firstValidCharIndex = (word) => {
const reg = /[\p{L}]/u
return word.search(reg)
}

const capitalizedWord = (word) => {
const chars = word.split('')
const index = firstValidCharIndex(word)
chars[index] = chars[index].toUpperCase()
return chars.join('')
}

export function toDistractionFreeTitle (title, minUpperCase = 3) {
const reg = RegExp(`[\\p{Lu}|']{${minUpperCase},}`, 'ug')
return title.replace(reg, x => capitalizedWord(x.toLowerCase()))
}

export function formatNumber(number, options = undefined) {
const firstValidCharIndex = (word) => {
const reg = /[\p{L}]/u
return word.search(reg)
}
export const capitalizedWord = (word) => {
const chars = word.split('')
const index = firstValidCharIndex(word)
chars[index] = chars[index].toUpperCase()
return chars.join('')
}

export function formatNumber (number, options = undefined) {
return Intl.NumberFormat([i18n.locale.replace('_', '-'), 'en'], options).format(number)
}

export const otherAPI = (current) => {
return current === 'local' ? 'invidious' : 'local'
}
53 changes: 50 additions & 3 deletions src/renderer/views/Channel/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import FtShareButton from '../../components/ft-share-button/ft-share-button.vue'
import ytch from 'yt-channel-info'
import autolinker from 'autolinker'
import { MAIN_PROFILE_ID } from '../../../constants'
import { copyToClipboard, formatNumber, showToast } from '../../helpers/utils'
import { capitalizedWord, copyToClipboard, formatNumber, otherAPI, showToast } from '../../helpers/utils'
import packageDetails from '../../../../package.json'
import { invidiousAPICall, invidiousGetChannelInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
import { channelShorts } from '../../helpers/api'

export default defineComponent({
name: 'Search',
Expand Down Expand Up @@ -45,20 +46,28 @@ export default defineComponent({
subCount: 0,
searchPage: 2,
videoContinuationString: '',
shortsContinuationString: '',
playlistContinuationString: '',
searchContinuationString: '',
channelDescription: '',
videoSortBy: 'newest',
shortsSortBy: 'newest',
playlistSortBy: 'last',
lastSearchQuery: '',
relatedChannels: [],
latestVideos: [],
latestShorts: [],
latestPlaylists: [],
searchResults: [],
shownElementList: [],
apiUsed: '',
isFamilyFriendly: false,
errorMessage: '',
shortSelectValues: [
'newest',
'oldest',
'popular'
],
videoSelectValues: [
'newest',
'oldest',
Expand Down Expand Up @@ -126,7 +135,15 @@ export default defineComponent({
}
},

videoSelectNames: function () {
shortSelectNames: function() {
return [
this.$t('Channel.Shorts.Sort Types.Newest'),
this.$t('Channel.Shorts.Sort Types.Oldest'),
this.$t('Channel.Shorts.Sort Types.Most Popular')
]
},

videoSelectNames: function() {
return [
this.$t('Channel.Videos.Sort Types.Newest'),
this.$t('Channel.Videos.Sort Types.Oldest'),
Expand Down Expand Up @@ -183,6 +200,7 @@ export default defineComponent({
this.searchPage = 2
this.relatedChannels = []
this.latestVideos = []
this.latestShorts = []
this.latestPlaylists = []
this.searchResults = []
this.shownElementList = []
Expand All @@ -197,6 +215,7 @@ export default defineComponent({
this.getChannelVideosLocal()
this.getPlaylistsLocal()
}
this.getChannelShorts(this.apiUsed)
},

videoSortBy () {
Expand All @@ -214,6 +233,11 @@ export default defineComponent({
}
},

shortsSortBy () {
this.latestShorts = []
this.getChannelShorts(this.apiUsed)
},

playlistSortBy () {
this.isElementListLoading = true
this.latestPlaylists = []
Expand Down Expand Up @@ -245,6 +269,7 @@ export default defineComponent({
this.getChannelVideosLocal()
this.getPlaylistsLocal()
}
this.getChannelShorts(this.apiUsed)
},
methods: {
goToChannel: function (id) {
Expand Down Expand Up @@ -346,7 +371,29 @@ export default defineComponent({
})
},

channelLocalNextPage: function () {
getChannelShorts: async function(api) {
this.isElementListLoading = true
try {
const result = await channelShorts(api, this.id, this.idType, this.shortsSortBy, this.shortsContinuationString)
this.latestShorts = result.shorts
this.shortsContinuationString = result.continuationString
this.isElementListLoading = false
} catch (err) {
console.error(err)
const errorMessage = this.$t(`${capitalizedWord(api)} API Error (Click to copy)`)
showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => {
copyToClipboard(err.responseJSON.error)
})
if (process.env.IS_ELECTRON && this.backendPreference === api && this.backendFallback) {
showToast(this.$t(`Falling back to ${capitalizedWord(otherAPI(api))} API`))
this.getChannelShorts(otherAPI(api))
} else {
this.isLoading = false
}
}
},

channelLocalNextPage: function() {
ytch.getChannelVideosMore({ continuation: this.videoContinuationString }).then((response) => {
this.latestVideos = this.latestVideos.concat(response.items)
this.videoContinuationString = response.continuation
Expand Down
36 changes: 36 additions & 0 deletions src/renderer/views/Channel/Channel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@
>
{{ $t("Channel.Videos.Videos").toUpperCase() }}
</div>
<div
id="shortsTab"
class="tab"
:class="(currentTab==='shorts')?'selectedTab':''"
role="tab"
aria-selected="true"
aria-controls="shortsPanel"
tabindex="0"
@click="changeTab('shorts')"
@keydown.left.right.enter.space="changeTab('shorts', $event)"
>
{{ $t("Channel.Shorts.Shorts").toUpperCase() }}
</div>
<div
id="playlistsTab"
class="tab"
Expand Down Expand Up @@ -183,6 +196,15 @@
:placeholder="$t('Search Filters.Sort By.Sort By')"
@change="playlistSortBy = $event"
/>
<ft-select
v-show="currentTab === 'shorts'"
class="sortSelect"
:value="shortSelectValues[0]"
:select-names="shortSelectNames"
:select-values="shortSelectValues"
:placeholder="$t('Search Filters.Sort By.Sort By')"
@change="shortsSortBy = $event"
/>
<ft-loader
v-if="isElementListLoading"
/>
Expand All @@ -204,6 +226,20 @@
{{ $t("Channel.Videos.This channel does not currently have any videos") }}
</p>
</ft-flex-box>
<ft-element-list
v-show="currentTab === 'shorts'"
id="shortsPanel"
:data="latestShorts"
role="tabpanel"
aria-labelledby="shortsTab"
/>
<ft-flex-box
v-if="currentTab === 'shorts' && latestShorts.length === 0"
>
<p class="message">
{{ $t("Channel.Shorts.This channel does not currently have any shorts") }}
</p>
</ft-flex-box>
<ft-element-list
v-show="currentTab === 'playlists'"
id="playlistPanel"
Expand Down
7 changes: 7 additions & 0 deletions static/locales/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,13 @@ Channel:
Your search results have returned 0 results: Your search results have returned 0
results
Sort By: Sort By
Shorts:
Shorts: Shorts
This channel does not currently have any shorts: This channel does not currently have any shorts
Sort Types:
Newest: Newest
Oldest: Oldest
Most Popular: Most Popular
Videos:
Videos: Videos
This channel does not currently have any videos: This channel does not currently
Expand Down
1 change: 1 addition & 0 deletions test/__mocks__/styleMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
Loading