mirror of
https://github.com/FreeTubeApp/FreeTube.git
synced 2025-12-05 01:10:31 +00:00
Add some missing jsdoc comments, add jsdoc eslint plugin (#6048)
* Add some missing jsdoc comments, add jsdoc eslint plugin * remove left over addition Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> * move jsdoc eslint rule configuration * use dash for list instead * Enable some additional rules * Apply suggestions from code review Co-authored-by: PikachuEXE <git@pikachuexe.net> --------- Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> Co-authored-by: PikachuEXE <git@pikachuexe.net>
This commit is contained in:
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@@ -20,6 +20,7 @@ updates:
|
||||
- "@eslint/*"
|
||||
- "yaml-eslint-parser"
|
||||
- "vue-eslint-parser"
|
||||
- "neostandard"
|
||||
stylelint:
|
||||
patterns:
|
||||
- "stylelint"
|
||||
|
||||
@@ -26,11 +26,11 @@ class ProcessLocalesPlugin {
|
||||
}
|
||||
this.outputDir = options.outputDir
|
||||
|
||||
/** @type {Map<str, any>} */
|
||||
/** @type {Map<string, any>} */
|
||||
this.locales = new Map()
|
||||
this.localeNames = []
|
||||
|
||||
/** @type {Map<str, any>} */
|
||||
/** @type {Map<string, any>} */
|
||||
this.cache = new Map()
|
||||
|
||||
this.filePaths = []
|
||||
|
||||
@@ -32,7 +32,7 @@ function getMappings(shakaLocales, freeTubeLocales) {
|
||||
* @type {[string, string][]}
|
||||
* Using this structure as it gets passed to `new Map()` in the player component
|
||||
* The first element is the FreeTube locale, the second one is the shaka-player one
|
||||
**/
|
||||
*/
|
||||
const mappings = []
|
||||
|
||||
for (const locale of freeTubeLocales) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import eslintPluginJsonc from 'eslint-plugin-jsonc'
|
||||
import eslintPluginYml from 'eslint-plugin-yml'
|
||||
import yamlEslintParser from 'yaml-eslint-parser'
|
||||
import neostandard from 'neostandard'
|
||||
import jsdoc from 'eslint-plugin-jsdoc'
|
||||
|
||||
import activeLocales from './static/locales/activeLocales.json' with { type: 'json' }
|
||||
|
||||
@@ -40,6 +41,7 @@ export default [
|
||||
],
|
||||
plugins: {
|
||||
unicorn: eslintPluginUnicorn,
|
||||
jsdoc,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
@@ -115,6 +117,15 @@ export default [
|
||||
'@intlify/vue-i18n/no-deprecated-tc': 'off',
|
||||
'vue/require-explicit-emits': 'error',
|
||||
'vue/no-unused-emit-declarations': 'error',
|
||||
|
||||
'jsdoc/check-alignment': 'error',
|
||||
'jsdoc/check-property-names': 'error',
|
||||
'jsdoc/check-param-names': 'error',
|
||||
'jsdoc/check-syntax': 'error',
|
||||
'jsdoc/check-template-names': 'error',
|
||||
'jsdoc/check-types': 'error',
|
||||
'jsdoc/no-bad-blocks': 'error',
|
||||
'jsdoc/no-multi-asterisks': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"electron-builder": "^25.1.8",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsdoc": "^50.4.3",
|
||||
"eslint-plugin-jsonc": "^2.16.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
|
||||
@@ -280,14 +280,17 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['change-tab', 'search', 'subscribed'])
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const hideChannelSubscriptions = computed(() => {
|
||||
return store.getters.getHideChannelSubscriptions
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const hideSharingActions = computed(() => {
|
||||
return store.getters.getHideSharingActions
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const hideUnsubscribeButton = computed(() => {
|
||||
return store.getters.getHideUnsubscribeButton
|
||||
})
|
||||
|
||||
@@ -198,10 +198,12 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<'grid' | 'list'>} */
|
||||
const listType = computed(() => {
|
||||
return store.getters.getListType
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<string[]>} */
|
||||
const forbiddenTitles = computed(() => {
|
||||
if (!props.hideForbiddenTitles) { return [] }
|
||||
return JSON.parse(store.getters.getForbiddenTitles)
|
||||
@@ -211,10 +213,12 @@ const hideVideo = computed(() => {
|
||||
return forbiddenTitles.value.some((text) => props.data.postContent.content.title?.toLowerCase().includes(text.toLowerCase()))
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<'local' | 'invidious'>} */
|
||||
const backendPreference = computed(() => {
|
||||
return store.getters.getBackendPreference
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const backendFallback = computed(() => {
|
||||
return store.getters.getBackendFallback
|
||||
})
|
||||
@@ -226,11 +230,13 @@ const isInvidiousAllowed = computed(() => {
|
||||
let postType = ''
|
||||
let postText = ''
|
||||
let postId = ''
|
||||
/** @type {string[]?} */
|
||||
let authorThumbnails = null
|
||||
let postContent = ''
|
||||
let author = ''
|
||||
let authorId = ''
|
||||
let voteCount = 0
|
||||
/** @type {number?} */
|
||||
let commentCount = null
|
||||
|
||||
parseCommunityData()
|
||||
|
||||
@@ -104,10 +104,12 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['move-video-down', 'move-video-up', 'remove-from-playlist'])
|
||||
|
||||
/** @type {import('vue').ComputedRef<'grid' | 'list'>} */
|
||||
const listType = computed(() => {
|
||||
return store.getters.getListType
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<'grid' | 'list'>} */
|
||||
const displayValue = computed(() => {
|
||||
return props.display === '' ? listType.value : props.display
|
||||
})
|
||||
|
||||
@@ -91,14 +91,17 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<string>} */
|
||||
const currentInvidiousInstanceUrl = computed(() => {
|
||||
return store.getters.getCurrentInvidiousInstanceUrl
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<'grid' | 'list'>} */
|
||||
const listType = computed(() => {
|
||||
return store.getters.getListType
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const hideChannelSubscriptions = computed(() => {
|
||||
return store.getters.getHideChannelSubscriptions
|
||||
})
|
||||
@@ -106,8 +109,11 @@ const hideChannelSubscriptions = computed(() => {
|
||||
let id = ''
|
||||
let thumbnail = ''
|
||||
let name = ''
|
||||
/** @type {number?} */
|
||||
let subscriberCount = null
|
||||
/** @type {number?} */
|
||||
let videoCount = null
|
||||
/** @type {string?} */
|
||||
let handle = null
|
||||
let description = ''
|
||||
|
||||
@@ -155,6 +161,7 @@ function parseLocalData() {
|
||||
|
||||
function parseInvidiousData() {
|
||||
// Can be prefixed with `https://` or `//` (protocol relative)
|
||||
/** @type {string} */
|
||||
const thumbnailUrl = props.data.authorThumbnails[2].url
|
||||
|
||||
thumbnail = youtubeImageUrlToInvidious(thumbnailUrl, currentInvidiousInstanceUrl.value)
|
||||
|
||||
@@ -67,13 +67,18 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<'list'| 'grid'>} */
|
||||
const listType = computed(() => {
|
||||
return store.getters.getListType
|
||||
})
|
||||
|
||||
/** @type {string} */
|
||||
const title = props.data.title
|
||||
/** @type {number} */
|
||||
const channelCount = props.data.channelCount
|
||||
/** @type {number} */
|
||||
const videoCount = props.data.videoCount
|
||||
/** @type {string} */
|
||||
const url = `/hashtag/${encodeURIComponent(title.substring(1))}`
|
||||
|
||||
const formattedChannelCount = computed(() => {
|
||||
|
||||
@@ -141,18 +141,22 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['move-video-down', 'move-video-up', 'remove-from-playlist'])
|
||||
|
||||
/** @type {import('vue').ComputedRef<'video' | 'shortVideo' | 'channel' | 'playlist' | 'community'>} */
|
||||
const finalDataType = computed(() => {
|
||||
return props.data.type ?? props.dataType
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const hideLiveStreams = computed(() => {
|
||||
return store.getters.getHideLiveStreams
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const hideUpcomingPremieres = computed(() => {
|
||||
return store.getters.getHideUpcomingPremieres
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<{name : string, preferredName: string, icon: string}[]>} */
|
||||
const channelsHidden = computed(() => {
|
||||
// Some component users like channel view will have this disabled
|
||||
if (!props.useChannelsHiddenPreference) { return [] }
|
||||
@@ -166,6 +170,7 @@ const channelsHidden = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/** @type {string[]} */
|
||||
const forbiddenTitles = computed(() => {
|
||||
if (!props.hideForbiddenTitles) { return [] }
|
||||
return JSON.parse(store.getters.getForbiddenTitles)
|
||||
|
||||
@@ -106,10 +106,12 @@ const currentChapter = computed(() => {
|
||||
return props.chapters[currentIndex.value]
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<string>} */
|
||||
const currentTitle = computed(() => {
|
||||
return currentChapter.value.title
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const compact = computed(() => {
|
||||
return !props.chapters[0].thumbnail
|
||||
})
|
||||
|
||||
@@ -60,6 +60,7 @@ function onTimestamp(timestamp) {
|
||||
|
||||
/**
|
||||
* @param {string} descriptionText
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseDescriptionHtml(descriptionText) {
|
||||
return descriptionText
|
||||
|
||||
@@ -46,6 +46,7 @@ defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const playNextVideo = computed(() => {
|
||||
return store.getters.getPlayNextVideo
|
||||
})
|
||||
|
||||
@@ -355,7 +355,7 @@ export default defineComponent({
|
||||
* color: string,
|
||||
* skip: 'autoSkip' | 'promptToSkip' | 'showInSeekBar' | 'doNothing'
|
||||
* }
|
||||
* }} */
|
||||
}} */
|
||||
const categoryData = {}
|
||||
|
||||
sponsorCategories.forEach(x => {
|
||||
@@ -537,7 +537,7 @@ export default defineComponent({
|
||||
* @param {'dash'|'audio'|'legacy'} format
|
||||
* @param {boolean} useAutoQuality
|
||||
* @returns {shaka.extern.PlayerConfiguration}
|
||||
**/
|
||||
*/
|
||||
function getPlayerConfig(format, useAutoQuality = false) {
|
||||
return {
|
||||
// YouTube uses these values and they seem to work well in FreeTube too,
|
||||
@@ -1872,7 +1872,7 @@ export default defineComponent({
|
||||
|
||||
/**
|
||||
* @param {WheelEvent} event
|
||||
* */
|
||||
*/
|
||||
function mouseScrollVolume(event) {
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
event.preventDefault()
|
||||
@@ -2150,7 +2150,7 @@ export default defineComponent({
|
||||
/**
|
||||
* @param {shaka.util.Error} error
|
||||
* @param {string} context
|
||||
* @param {object=} details
|
||||
* @param {object?} details
|
||||
*/
|
||||
function handleError(error, context, details) {
|
||||
logShakaError(error, context, props.videoId, details)
|
||||
|
||||
@@ -42,7 +42,7 @@ export class AudioTrackSelection extends shaka.ui.SettingsMenu {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {shaka.extern.TrackList=} tracks
|
||||
* @param {shaka.extern.TrackList?} tracks
|
||||
*/
|
||||
updateAudioTracks_(tracks) {
|
||||
if (!tracks) {
|
||||
|
||||
@@ -19,7 +19,7 @@ export default defineComponent({
|
||||
* Allows to render the dropdown conditionally
|
||||
* 'Channel' will exclude embed links
|
||||
* 'Video' (default) keeps the original behaviour
|
||||
**/
|
||||
*/
|
||||
type: String,
|
||||
default: 'Video'
|
||||
},
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @param {string} attribute
|
||||
* @returns {string}
|
||||
*/
|
||||
export function sanitizeForHtmlId(attribute) {
|
||||
return attribute.replaceAll(/\s+/g, '')
|
||||
}
|
||||
|
||||
@@ -142,6 +142,11 @@ export async function invidiousGetCommentReplies({ id, replyToken }) {
|
||||
return { commentData: parseInvidiousCommentData(response), continuation: response.continuation ?? null }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string?} currentInstance
|
||||
* @returns {string}
|
||||
*/
|
||||
export function youtubeImageUrlToInvidious(url, currentInstance = null) {
|
||||
if (url == null) {
|
||||
return null
|
||||
|
||||
@@ -550,15 +550,15 @@ export async function getLocalArtistTopicChannelReleasesContinuation(channel, co
|
||||
* @param {boolean} onlyIdNameThumbnail
|
||||
*/
|
||||
export function parseLocalChannelHeader(channel, onlyIdNameThumbnail = false) {
|
||||
/** @type {string=} */
|
||||
/** @type {string?} */
|
||||
let id
|
||||
/** @type {string} */
|
||||
let name
|
||||
/** @type {string=} */
|
||||
/** @type {string?} */
|
||||
let thumbnailUrl
|
||||
/** @type {string=} */
|
||||
/** @type {string?} */
|
||||
let bannerUrl
|
||||
/** @type {string=} */
|
||||
/** @type {string?} */
|
||||
let subscriberText
|
||||
/** @type {string[]} */
|
||||
const tags = []
|
||||
@@ -766,7 +766,7 @@ export function parseLocalChannelShorts(shorts, channelId, channelName) {
|
||||
/**
|
||||
* @param {import('youtubei.js').YTNodes.Playlist|import('youtubei.js').YTNodes.GridPlaylist|import('youtubei.js').YTNodes.LockupView} playlist
|
||||
* @param {string} channelId
|
||||
* @param {string} chanelName
|
||||
* @param {string} channelName
|
||||
*/
|
||||
export function parseLocalListPlaylist(playlist, channelId = undefined, channelName = undefined) {
|
||||
if (playlist.type === 'LockupView') {
|
||||
|
||||
@@ -4,11 +4,11 @@ import { getLocalChannel, parseLocalChannelHeader } from './api/local'
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {{
|
||||
* preference: string,
|
||||
* fallback: boolean,
|
||||
* invalid: boolean,
|
||||
* }} backendOptions
|
||||
*/
|
||||
* preference: string,
|
||||
* fallback: boolean,
|
||||
* invalid: boolean,
|
||||
* }} backendOptions
|
||||
*/
|
||||
async function findChannelById(id, backendOptions) {
|
||||
try {
|
||||
if (!process.env.SUPPORTS_LOCAL_API || backendOptions.preference === 'invidious') {
|
||||
@@ -37,11 +37,11 @@ async function findChannelById(id, backendOptions) {
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {{
|
||||
* preference: string,
|
||||
* fallback: boolean,
|
||||
* }} backendOptions
|
||||
* @returns {Promise<{icon: string, iconHref: string, preferredName: string} | { invalidId: boolean }>}
|
||||
*/
|
||||
* preference: string,
|
||||
* fallback: boolean,
|
||||
* }} backendOptions
|
||||
* @returns {Promise<{icon: string, iconHref: string, preferredName: string} | { invalidId: boolean }>}
|
||||
*/
|
||||
export async function findChannelTagInfo(id, backendOptions) {
|
||||
if (!checkYoutubeChannelId(id)) return { invalidId: true }
|
||||
try {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { sponsorBlockSkipSegments } from '../sponsorblock'
|
||||
* @param {shaka.util.Error} error
|
||||
* @param {string} context
|
||||
* @param {string} videoId
|
||||
* @param {object=} details
|
||||
* @param {object?} details
|
||||
*/
|
||||
export function logShakaError(error, context, videoId, details) {
|
||||
const { Severity, Category, Code } = shaka.util.Error
|
||||
@@ -189,13 +189,13 @@ export function sortCaptions(captions) {
|
||||
* This function cleans it up, so that we can use it.
|
||||
*
|
||||
* Here is a list of things this function does:
|
||||
* * Removes bogus roles and labels
|
||||
* * Extracts the languages from the audio URLs if available and adds it to the adapation sets
|
||||
* * Adds roles and labels when possible to add support for multiple audio tracks
|
||||
* - Removes bogus roles and labels
|
||||
* - Extracts the languages from the audio URLs if available and adds it to the adapation sets
|
||||
* - Adds roles and labels when possible to add support for multiple audio tracks
|
||||
*
|
||||
* Things this function does not do:
|
||||
* * Separate DRC (Stable Volume) from their original counterparts
|
||||
* * Tag HDR video streams (Invidious puts all video streams in the same adaptation set,
|
||||
* - Separate DRC (Stable Volume) from their original counterparts
|
||||
* - Tag HDR video streams (Invidious puts all video streams in the same adaptation set,
|
||||
* to tag HDR and SDR streams we would have to separate them out into multiple adaptation sets)
|
||||
* @param {shaka.extern.xml.Node[]} periods
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,9 @@ export function isKeyboardEventKeyPrintableChar(eventKey) {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} title
|
||||
*/
|
||||
export function translateWindowTitle(title) {
|
||||
switch (title) {
|
||||
case 'Subscriptions':
|
||||
|
||||
@@ -65,7 +65,7 @@ export function updateVideoListAfterProcessing(videos) {
|
||||
/**
|
||||
* @param {string} rssString
|
||||
* @param {string} channelId
|
||||
*/
|
||||
*/
|
||||
export async function parseYouTubeRSSFeed(rssString, channelId) {
|
||||
// doesn't need to be asynchronous, but doing it allows us to do the relatively slow DOM querying in parallel
|
||||
try {
|
||||
|
||||
@@ -12,6 +12,10 @@ export const CHANNEL_HANDLE_REGEX = /^@[\w.-]{3,30}$/
|
||||
|
||||
const PUBLISHED_TEXT_REGEX = /(\d+)\s?([a-z]+)/i
|
||||
|
||||
/**
|
||||
* @param {string} sortPreference
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function getIconForSortPreference(sortPreference) {
|
||||
switch (sortPreference) {
|
||||
case 'name_descending':
|
||||
@@ -192,6 +196,11 @@ export function toLocalePublicationString ({ publishText, isLive = false, isUpco
|
||||
return i18n.t('Video.Publicationtemplate', { number: match[1], unit })
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('youtubei.js/dist/src/parser/classes/PlayerStoryboardSpec').StoryboardData} storyboard
|
||||
* @param {number} videoLengthSeconds
|
||||
* @returns {string}
|
||||
*/
|
||||
export function buildVTTFileLocally(storyboard, videoLengthSeconds) {
|
||||
let vttString = 'WEBVTT\n\n'
|
||||
// how many images are in one image
|
||||
@@ -249,6 +258,11 @@ export function buildVTTFileLocally(storyboard, videoLengthSeconds) {
|
||||
return vttString
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {number} time
|
||||
* @param {Function} action
|
||||
*/
|
||||
export function showToast(message, time = null, action = null) {
|
||||
FtToastEvents.dispatchEvent(new CustomEvent('toast-open', {
|
||||
detail: {
|
||||
@@ -260,13 +274,14 @@ export function showToast(message, time = null, action = null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This writes to the clipboard. If an error occurs during the copy,
|
||||
* a toast with the error is shown. If the copy is successful and
|
||||
* there is a success message, a toast with that message is shown.
|
||||
* @param {string} content the content to be copied to the clipboard
|
||||
* @param {null|string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
|
||||
* @param {null|string} messageOnError the message to be displayed as a toast when the copy fails (optional)
|
||||
*/
|
||||
* This writes to the clipboard. If an error occurs during the copy,
|
||||
* a toast with the error is shown. If the copy is successful and
|
||||
* there is a success message, a toast with that message is shown.
|
||||
* @param {string} content the content to be copied to the clipboard
|
||||
* @param {object} [options] - Optional settings for the copy operation.
|
||||
* @param {null|string} options.messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
|
||||
* @param {null|string} options.messageOnError the message to be displayed as a toast when the copy fails (optional)
|
||||
*/
|
||||
export async function copyToClipboard(content, { messageOnSuccess = null, messageOnError = null } = {}) {
|
||||
if (navigator.clipboard !== undefined && window.isSecureContext) {
|
||||
try {
|
||||
@@ -370,7 +385,7 @@ export async function showOpenDialog (options) {
|
||||
/**
|
||||
* @param {object} response the response from `showOpenDialog`
|
||||
* @param {number} index which file to read (defaults to the first in the response)
|
||||
* @returns the text contents of the selected file
|
||||
* @returns {string} the text contents of the selected file
|
||||
*/
|
||||
export function readFileFromDialog(response, index = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -396,6 +411,10 @@ export function readFileFromDialog(response, index = 0) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{defaultPath: string, filters: {name: string, extensions: string[]}[]}} options
|
||||
* @returns { Promise<import('electron').SaveDialogReturnValue> | {canceled: boolean?, filePath: string } | { canceled: boolean?, handle?: Promise<FileSystemFileHandle> }}
|
||||
*/
|
||||
export async function showSaveDialog (options) {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
const { ipcRenderer } = require('electron')
|
||||
@@ -423,10 +442,10 @@ export async function showSaveDialog (options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to a file picked out from the `showSaveDialog` picker
|
||||
* @param {object} response the response from `showSaveDialog`
|
||||
* @param {string} content the content to be written to the file selected by the dialog
|
||||
*/
|
||||
* Write to a file picked out from the `showSaveDialog` picker
|
||||
* @param {object} response the response from `showSaveDialog`
|
||||
* @param {string} content the content to be written to the file selected by the dialog
|
||||
*/
|
||||
export async function writeFileFromDialog (response, content) {
|
||||
if (process.env.IS_ELECTRON) {
|
||||
const { filePath } = response
|
||||
@@ -475,7 +494,11 @@ export function createWebURL(path) {
|
||||
return `${origin}${windowPath}/${path}`
|
||||
}
|
||||
|
||||
// strip html tags but keep <br>, <b>, </b> <s>, </s>, <i>, </i>
|
||||
/**
|
||||
* strip html tags but keep <br>, <b>, </b> <s>, </s>, <i>, </i>
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
export function stripHTML(value) {
|
||||
return value.replaceAll(/(<(?!br|\/?[abis]|img>)([^>]+)>)/gi, '')
|
||||
}
|
||||
@@ -522,6 +545,11 @@ export function formatDurationAsTimestamp(lengthSeconds) {
|
||||
return timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{sortBy? : string, time?: string, duration?: string, features: string[]}?} filtersA
|
||||
* @param {{sortBy? : string, time?: string, duration?: string, features: string[]}?} filtersB
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function searchFiltersMatch(filtersA, filtersB) {
|
||||
return filtersA?.sortBy === filtersB?.sortBy &&
|
||||
filtersA?.time === filtersB?.time &&
|
||||
@@ -530,6 +558,10 @@ export function searchFiltersMatch(filtersA, filtersB) {
|
||||
filtersA?.features?.length === filtersB?.features?.length && filtersA?.features?.every((val, index) => val === filtersB?.features[index])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filenameOriginal
|
||||
* @returns {string}
|
||||
*/
|
||||
export function replaceFilenameForbiddenChars(filenameOriginal) {
|
||||
let filenameNew = filenameOriginal
|
||||
let forbiddenChars = {}
|
||||
@@ -563,6 +595,9 @@ export function replaceFilenameForbiddenChars(filenameOriginal) {
|
||||
return filenameNew
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function getSystemLocale() {
|
||||
let locale
|
||||
if (process.env.IS_ELECTRON) {
|
||||
@@ -715,6 +750,11 @@ export function toDistractionFreeTitle(title, minUpperCase = 3) {
|
||||
.replace(reg, x => capitalizedWord(x.toLowerCase()))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} number
|
||||
* @param {Intl.NumberFormatOptions?} options
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatNumber(number, options = undefined) {
|
||||
return Intl.NumberFormat([i18n.locale, 'en'], options).format(number)
|
||||
}
|
||||
@@ -730,6 +770,13 @@ export function getTodayDateStrLocalTimezone() {
|
||||
return timeNowStr.split('T')[0]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} date
|
||||
* @param {boolean} hideSeconds
|
||||
* @param {boolean} useThirtyDayMonths
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getRelativeTimeFromDate(date, hideSeconds = false, useThirtyDayMonths = true) {
|
||||
if (!date) {
|
||||
return ''
|
||||
@@ -800,8 +847,9 @@ export function escapeHTML(untrusted) {
|
||||
|
||||
/**
|
||||
* Performs a deep copy of a javascript object
|
||||
* @param {Object} obj
|
||||
* @returns {Object}
|
||||
* @template T
|
||||
* @param {T} obj
|
||||
* @returns {T}
|
||||
*/
|
||||
export function deepCopy(obj) {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
@@ -811,7 +859,8 @@ export function deepCopy(obj) {
|
||||
* Check if the `name` of the error is `TimeoutError` to know if the error was caused by a timeout or something else.
|
||||
* @param {number} timeoutMs
|
||||
* @param {RequestInfo|URL} input
|
||||
* @param {RequestInit=} init
|
||||
* @param {RequestInit?} init
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function fetchWithTimeout(timeoutMs, input, init) {
|
||||
const timeoutSignal = AbortSignal.timeout(timeoutMs)
|
||||
@@ -839,6 +888,10 @@ export async function fetchWithTimeout(timeoutMs, input, init) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
* @param {HTMLInputElement} inputElement
|
||||
*/
|
||||
export function ctrlFHandler(event, inputElement) {
|
||||
switch (event.key) {
|
||||
case 'F':
|
||||
@@ -879,11 +932,13 @@ export function base64EncodeUtf8(text) {
|
||||
* @param {string} channelId
|
||||
* @param {'all'} type
|
||||
* @returns {string}
|
||||
*
|
||||
*
|
||||
* @param {string} channelId
|
||||
* @param {'all' | 'videos' | 'live' | 'shorts'} type
|
||||
* @param {'newest' | 'popular'} sortBy
|
||||
* @param {('newest' | 'popular')?} [sortBy]
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
export function getChannelPlaylistId(channelId, type, sortBy) {
|
||||
switch (type) {
|
||||
case 'videos':
|
||||
|
||||
@@ -712,7 +712,7 @@ export default defineComponent({
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
const about = await channel.getAbout()
|
||||
|
||||
@@ -784,7 +784,7 @@ export default defineComponent({
|
||||
} else {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
let videosTab = await channel.getVideos()
|
||||
|
||||
@@ -934,7 +934,7 @@ export default defineComponent({
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
let liveTab = await channel.getLiveStreams()
|
||||
|
||||
@@ -1426,7 +1426,7 @@ export default defineComponent({
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
|
||||
if (this.isArtistTopicChannel) {
|
||||
@@ -1561,7 +1561,7 @@ export default defineComponent({
|
||||
try {
|
||||
/**
|
||||
* @type {import('youtubei.js').YT.Channel}
|
||||
*/
|
||||
*/
|
||||
const channel = this.channelInstance
|
||||
const podcastTab = await channel.getPodcasts()
|
||||
|
||||
|
||||
@@ -722,7 +722,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (result.storyboards?.type === 'PlayerStoryboardSpec') {
|
||||
let source = result.storyboards.boards
|
||||
/** @type {import('youtubei.js/dist/src/parser/classes/PlayerStoryboardSpec').StoryboardData[]} */
|
||||
let source = result.storyboardSpec.boards
|
||||
if (window.innerWidth < 500) {
|
||||
source = source.filter((board) => board.thumbnail_height <= 90)
|
||||
}
|
||||
@@ -1485,6 +1486,10 @@ export default defineComponent({
|
||||
return result.adaptiveFormats
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import('youtubei.js/dist/src/parser/classes/PlayerStoryboardSpec').StoryboardData} storyboardInfo
|
||||
* @returns {string}
|
||||
*/
|
||||
createLocalStoryboardUrls: function (storyboardInfo) {
|
||||
const results = buildVTTFileLocally(storyboardInfo, this.videoLengthSeconds)
|
||||
|
||||
|
||||
77
yarn.lock
77
yarn.lock
@@ -978,6 +978,15 @@
|
||||
minimatch "^9.0.3"
|
||||
plist "^3.1.0"
|
||||
|
||||
"@es-joy/jsdoccomment@~0.49.0":
|
||||
version "0.49.0"
|
||||
resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz#e5ec1eda837c802eca67d3b29e577197f14ba1db"
|
||||
integrity sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==
|
||||
dependencies:
|
||||
comment-parser "1.4.1"
|
||||
esquery "^1.6.0"
|
||||
jsdoc-type-pratt-parser "~4.1.0"
|
||||
|
||||
"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
@@ -2277,6 +2286,11 @@ app-builder-lib@25.1.8:
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
|
||||
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
|
||||
|
||||
are-docs-informative@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963"
|
||||
integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==
|
||||
|
||||
are-we-there-yet@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd"
|
||||
@@ -2996,6 +3010,11 @@ commander@^8.3.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
|
||||
comment-parser@1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc"
|
||||
integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==
|
||||
|
||||
common-path-prefix@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"
|
||||
@@ -3330,7 +3349,7 @@ debug@2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.7:
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.6, debug@^4.3.7:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
@@ -3988,6 +4007,11 @@ es-module-lexer@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527"
|
||||
integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==
|
||||
|
||||
es-module-lexer@^1.5.3:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78"
|
||||
integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==
|
||||
|
||||
es-object-atoms@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
|
||||
@@ -4138,6 +4162,23 @@ eslint-plugin-import@^2.31.0:
|
||||
string.prototype.trimend "^1.0.8"
|
||||
tsconfig-paths "^3.15.0"
|
||||
|
||||
eslint-plugin-jsdoc@^50.4.3:
|
||||
version "50.4.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.4.3.tgz#38adf595555933775943771e906422b25cdfc780"
|
||||
integrity sha512-uWtwFxGRv6B8sU63HZM5dAGDhgsatb+LONwmILZJhdRALLOkCX2HFZhdL/Kw2ls8SQMAVEfK+LmnEfxInRN8HA==
|
||||
dependencies:
|
||||
"@es-joy/jsdoccomment" "~0.49.0"
|
||||
are-docs-informative "^0.0.2"
|
||||
comment-parser "1.4.1"
|
||||
debug "^4.3.6"
|
||||
escape-string-regexp "^4.0.0"
|
||||
espree "^10.1.0"
|
||||
esquery "^1.6.0"
|
||||
parse-imports "^2.1.1"
|
||||
semver "^7.6.3"
|
||||
spdx-expression-parse "^4.0.0"
|
||||
synckit "^0.9.1"
|
||||
|
||||
eslint-plugin-jsonc@^2.16.0:
|
||||
version "2.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz#e90eca15aa2e172f5aca52a77fc8c819f52862d7"
|
||||
@@ -5964,6 +6005,11 @@ jsbn@1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
|
||||
integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
|
||||
|
||||
jsdoc-type-pratt-parser@~4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz#ff6b4a3f339c34a6c188cbf50a16087858d22113"
|
||||
integrity sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==
|
||||
|
||||
jsesc@^3.0.2, jsesc@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
||||
@@ -7036,6 +7082,14 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-imports@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-imports/-/parse-imports-2.2.1.tgz#0a6e8b5316beb5c9905f50eb2bbb8c64a4805642"
|
||||
integrity sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==
|
||||
dependencies:
|
||||
es-module-lexer "^1.5.3"
|
||||
slashes "^3.0.12"
|
||||
|
||||
parse-json@^5.0.0, parse-json@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
||||
@@ -8341,6 +8395,11 @@ slash@^5.1.0:
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
|
||||
integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
|
||||
|
||||
slashes@^3.0.12:
|
||||
version "3.0.12"
|
||||
resolved "https://registry.yarnpkg.com/slashes/-/slashes-3.0.12.tgz#3d664c877ad542dc1509eaf2c50f38d483a6435a"
|
||||
integrity sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==
|
||||
|
||||
slice-ansi@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
|
||||
@@ -8451,6 +8510,14 @@ spdx-expression-parse@^3.0.0:
|
||||
spdx-exceptions "^2.1.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-expression-parse@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794"
|
||||
integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==
|
||||
dependencies:
|
||||
spdx-exceptions "^2.1.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-license-ids@^3.0.0:
|
||||
version "3.0.11"
|
||||
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95"
|
||||
@@ -8853,6 +8920,14 @@ synckit@^0.9.0:
|
||||
"@pkgr/core" "^0.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
synckit@^0.9.1:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62"
|
||||
integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
table@^6.8.2:
|
||||
version "6.8.2"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58"
|
||||
|
||||
Reference in New Issue
Block a user