Merge branch 'development' into piped-support

This commit is contained in:
Chunky programmer 2023-07-04 14:19:42 -04:00
commit 36105af0f9
29 changed files with 401 additions and 276 deletions

View File

@ -90,9 +90,9 @@
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"electron": "^22.3.14",
"electron": "^22.3.15",
"electron-builder": "^23.6.0",
"eslint": "^8.43.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.27.5",
@ -124,7 +124,7 @@
"vue-devtools": "^5.1.4",
"vue-eslint-parser": "^9.3.1",
"vue-loader": "^15.10.0",
"webpack": "^5.88.0",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"yaml-eslint-parser": "^1.2.2"

View File

@ -11,6 +11,8 @@ import baseHandlers from '../datastores/handlers/base'
import { extractExpiryTimestamp, ImageCache } from './ImageCache'
import { existsSync } from 'fs'
import packageDetails from '../../package.json'
if (process.argv.includes('--version')) {
app.exit()
} else {
@ -263,6 +265,12 @@ function runApp() {
})
}
const fixedUserAgent = session.defaultSession.getUserAgent()
.split(' ')
.filter(part => !part.includes('Electron') && !part.includes(packageDetails.productName))
.join(' ')
session.defaultSession.setUserAgent(fixedUserAgent)
// Set CONSENT cookie on reasonable domains
const consentCookieDomains = [
'https://www.youtube.com',
@ -279,10 +287,19 @@ function runApp() {
// make InnerTube requests work with the fetch function
// InnerTube rejects requests if the referer isn't YouTube or empty
const innertubeRequestFilter = { urls: ['https://www.youtube.com/youtubei/*'] }
const innertubeAndMediaRequestFilter = { urls: ['https://www.youtube.com/youtubei/*', 'https://*.googlevideo.com/videoplayback?*'] }
session.defaultSession.webRequest.onBeforeSendHeaders(innertubeAndMediaRequestFilter, ({ requestHeaders, url }, callback) => {
requestHeaders.Referer = 'https://www.youtube.com/'
requestHeaders.Origin = 'https://www.youtube.com'
if (url.startsWith('https://www.youtube.com/youtubei/')) {
requestHeaders['Sec-Fetch-Site'] = 'same-origin'
} else {
// YouTube doesn't send the Content-Type header for the media requests, so we shouldn't either
delete requestHeaders['Content-Type']
}
session.defaultSession.webRequest.onBeforeSendHeaders(innertubeRequestFilter, ({ requestHeaders }, callback) => {
requestHeaders.referer = 'https://www.youtube.com'
// eslint-disable-next-line n/no-callback-literal
callback({ requestHeaders })
})

View File

@ -48,13 +48,7 @@ export default defineComponent({
},
hideUpcomingPremieres: function () {
return this.$store.getters.getHideUpcomingPremieres
}
},
methods: {
onVisibilityChanged: function (visible) {
this.visible = visible
},
/**
* Show or Hide results in the list
*
@ -70,10 +64,9 @@ export default defineComponent({
// hide livestreams
return false
}
if (this.hideUpcomingPremieres &&
// Observed for premieres in Local API Channels.
(data.durationText === 'PREMIERE' ||
(data.premiereDate != null ||
// viewCount is our only method of detecting premieres in RSS
// data without sending an additional request.
// If we ever get a better flag, use it here instead.
@ -98,6 +91,11 @@ export default defineComponent({
}
return true
}
},
methods: {
onVisibilityChanged: function (visible) {
this.visible = visible
}
}
})

View File

@ -12,6 +12,7 @@ import {
isNullOrEmpty
} from '../../helpers/utils'
import { getPipedUrlInfo } from '../../helpers/api/piped'
import { deArrowData } from '../../helpers/sponsorblock'
export default defineComponent({
name: 'FtListVideo',
@ -347,6 +348,14 @@ export default defineComponent({
currentLocale: function () {
return this.$i18n.locale.replace('_', '-')
},
useDeArrowTitles: function () {
return this.$store.getters.getUseDeArrowTitles
},
deArrowCache: function () {
return this.$store.getters.getDeArrowCache(this.id)
}
},
watch: {
historyIndex() {
@ -358,6 +367,25 @@ export default defineComponent({
this.checkIfWatched()
},
methods: {
getDeArrowDataEntry: async function() {
// Read from local cache or remote
// Write to cache if read from remote
if (!this.useDeArrowTitles) { return null }
if (this.deArrowCache) { return this.deArrowCache }
const videoId = this.id
const data = await deArrowData(this.id)
const cacheData = { videoId, title: null }
if (Array.isArray(data?.titles) && data.titles.length > 0 && (data.titles[0].locked || data.titles[0].votes > 0)) {
cacheData.title = data.titles[0].title
}
// Save data to cache whether data available or not to prevent duplicate requests
this.$store.commit('addVideoToDeArrowCache', cacheData)
return cacheData
},
handleExternalPlayer: function () {
this.$emit('pause-player')
@ -428,9 +456,9 @@ export default defineComponent({
}
},
parseVideoData: function () {
parseVideoData: async function () {
this.id = this.data.videoId
this.title = this.data.title
this.title = (await this.getDeArrowDataEntry())?.title ?? this.data.title
// this.thumbnail = this.data.videoThumbnails[4].url
this.channelName = this.data.author ?? null

View File

@ -110,12 +110,7 @@ export default defineComponent({
}
})
this.updateAllSubscriptionsList([])
this.updateProfileSubscriptions({
activeProfile: MAIN_PROFILE_ID,
videoList: [],
errorChannels: []
})
this.clearSubscriptionsCache()
}
},
@ -129,8 +124,7 @@ export default defineComponent({
'updateProfile',
'removeProfile',
'updateActiveProfile',
'updateAllSubscriptionsList',
'updateProfileSubscriptions'
'clearSubscriptionsCache',
])
}
})

View File

@ -38,6 +38,10 @@ export default defineComponent({
},
sponsorBlockShowSkippedToast: function () {
return this.$store.getters.getSponsorBlockShowSkippedToast
},
useDeArrowTitles: function () {
return this.$store.getters.getUseDeArrowTitles
}
},
methods: {
@ -45,6 +49,10 @@ export default defineComponent({
this.updateUseSponsorBlock(value)
},
handleUpdateUseDeArrowTitles: function (value) {
this.updateUseDeArrowTitles(value)
},
handleUpdateSponsorBlockUrl: function (value) {
const sponsorBlockUrlWithoutTrailingSlash = value.replace(/\/$/, '')
const sponsorBlockUrlWithoutApiSuffix = sponsorBlockUrlWithoutTrailingSlash.replace(/\/api$/, '')
@ -58,7 +66,8 @@ export default defineComponent({
...mapActions([
'updateUseSponsorBlock',
'updateSponsorBlockUrl',
'updateSponsorBlockShowSkippedToast'
'updateSponsorBlockShowSkippedToast',
'updateUseDeArrowTitles'
])
}
})

View File

@ -8,11 +8,20 @@
:default-value="useSponsorBlock"
@change="handleUpdateSponsorBlock"
/>
<ft-toggle-switch
:label="$t('Settings.SponsorBlock Settings.UseDeArrowTitles')"
:default-value="useDeArrowTitles"
:tooltip="$t('Tooltips.SponsorBlock Settings.UseDeArrowTitles')"
@change="handleUpdateUseDeArrowTitles"
/>
</ft-flex-box>
<div
v-if="useSponsorBlock"
v-if="useSponsorBlock || useDeArrowTitles"
>
<ft-flex-box class="settingsFlexStart500px">
<ft-flex-box
v-if="useSponsorBlock"
class="settingsFlexStart500px"
>
<ft-toggle-switch
:label="$t('Settings.SponsorBlock Settings.Notify when sponsor segment is skipped')"
:default-value="sponsorBlockShowSkippedToast"
@ -28,7 +37,9 @@
@input="handleUpdateSponsorBlockUrl"
/>
</ft-flex-box>
<ft-flex-box>
<ft-flex-box
v-if="useSponsorBlock"
>
<ft-sponsor-block-category
v-for="category in categories"
:key="category"

View File

@ -158,10 +158,16 @@ export async function getLocalVideoInfo(id, attemptBypass = false) {
const androidInnertube = await createInnertube({ clientType: ClientType.ANDROID, generateSessionLocally: false })
const androidInfo = await androidInnertube.getBasicInfo(id, 'ANDROID')
if (androidInfo.playability_status.status === 'OK') {
info.streaming_data = androidInfo.streaming_data
// Sometimes when YouTube detects a third party client or has applied an IP-ratelimit,
// they replace the response with a different video id
// https://github.com/TeamNewPipe/NewPipe/issues/8713
// https://github.com/TeamPiped/Piped/issues/2487
if (androidInfo.basic_info.id !== id) {
console.error(`Failed to fetch android formats. Wrong video ID in response: ${androidInfo.basic_info.id}, expected: ${id}`)
} else if (androidInfo.playability_status.status !== 'OK') {
console.error('Failed to fetch android formats', JSON.stringify(androidInfo.playability_status))
} else {
console.error('Failed to fetch android formats', JSON.parse(JSON.stringify(androidInfo.playability_status)))
info.streaming_data = androidInfo.streaming_data
}
} catch (error) {
console.error('Failed to fetch android formats')

View File

@ -1,16 +1,17 @@
import store from '../store/index'
export async function sponsorBlockSkipSegments(videoId, categories) {
async function getVideoHash(videoId) {
const videoIdBuffer = new TextEncoder().encode(videoId)
const hashBuffer = await crypto.subtle.digest('SHA-256', videoIdBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const videoIdHashPrefix = hashArray
return hashArray
.map(byte => byte.toString(16).padStart(2, '0'))
.slice(0, 4)
.join('')
}
export async function sponsorBlockSkipSegments(videoId, categories) {
const videoIdHashPrefix = await getVideoHash(videoId)
const requestUrl = `${store.getters.getSponsorBlockUrl}/api/skipSegments/${videoIdHashPrefix}?categories=${JSON.stringify(categories)}`
try {
@ -30,3 +31,23 @@ export async function sponsorBlockSkipSegments(videoId, categories) {
throw error
}
}
export async function deArrowData(videoId) {
const videoIdHashPrefix = (await getVideoHash(videoId)).substring(0, 4)
const requestUrl = `${store.getters.getSponsorBlockUrl}/api/branding/${videoIdHashPrefix}`
try {
const response = await fetch(requestUrl)
// 404 means that there are no segments registered for the video
if (response.status === 404) {
return undefined
}
const json = await response.json()
return json[videoId] ?? undefined
} catch (error) {
console.error('failed to fetch DeArrow data', requestUrl, error)
throw error
}
}

View File

@ -287,6 +287,7 @@ const state = {
settingsPassword: '',
allowDashAv1Formats: false,
commentAutoLoadEnabled: false,
useDeArrowTitles: false,
}
const stateWithSideEffects = {

View File

@ -1,39 +1,41 @@
import { MAIN_PROFILE_ID } from '../../../constants'
const defaultCacheEntryValueForForOneChannel = {
videos: null,
}
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj))
}
const state = {
allSubscriptionsList: [],
profileSubscriptions: {
activeProfile: MAIN_PROFILE_ID,
videoList: [],
errorChannels: []
}
subscriptionsCachePerChannel: {},
}
const getters = {
getAllSubscriptionsList: () => {
return state.allSubscriptionsList
getSubscriptionsCacheEntriesForOneChannel: (state) => (channelId) => {
return state.subscriptionsCachePerChannel[channelId]
},
getProfileSubscriptions: () => {
return state.profileSubscriptions
}
}
const actions = {
updateAllSubscriptionsList ({ commit }, subscriptions) {
commit('setAllSubscriptionsList', subscriptions)
clearSubscriptionsCache: ({ commit }) => {
commit('clearSubscriptionsCachePerChannel')
},
updateSubscriptionsCacheForOneChannel: ({ commit }, payload) => {
commit('updateSubscriptionsCacheForOneChannel', payload)
},
updateProfileSubscriptions ({ commit }, subscriptions) {
commit('setProfileSubscriptions', subscriptions)
}
}
const mutations = {
setAllSubscriptionsList (state, allSubscriptionsList) {
state.allSubscriptionsList = allSubscriptionsList
updateSubscriptionsCacheForOneChannel(state, { channelId, videos }) {
const existingObject = state.subscriptionsCachePerChannel[channelId]
const newObject = existingObject != null ? existingObject : deepCopy(defaultCacheEntryValueForForOneChannel)
if (videos != null) { newObject.videos = videos }
state.subscriptionsCachePerChannel[channelId] = newObject
},
clearSubscriptionsCachePerChannel(state) {
state.subscriptionsCachePerChannel = {}
},
setProfileSubscriptions (state, profileSubscriptions) {
state.profileSubscriptions = profileSubscriptions
}
}
export default {

View File

@ -27,6 +27,7 @@ const state = {
movies: null
},
cachedPlaylist: null,
deArrowCache: {},
showProgressBar: false,
progressBarPercentage: 0,
regionNames: [],
@ -57,6 +58,10 @@ const getters = {
return state.sessionSearchHistory
},
getDeArrowCache: (state) => (videoId) => {
return state.deArrowCache[videoId]
},
getPopularCache () {
return state.popularCache
},
@ -565,21 +570,14 @@ const actions = {
showExternalPlayerUnsupportedActionToast(externalPlayer, 'looping playlists')
}
}
if (cmdArgs.supportsYtdlProtocol) {
args.push(`${cmdArgs.playlistUrl}ytdl://${payload.playlistId}`)
} else {
args.push(`${cmdArgs.playlistUrl}https://youtube.com/playlist?list=${payload.playlistId}`)
}
args.push(`${cmdArgs.playlistUrl}https://youtube.com/playlist?list=${payload.playlistId}`)
} else {
if (payload.playlistId != null && payload.playlistId !== '' && !ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, 'opening playlists')
}
if (payload.videoId != null) {
if (cmdArgs.supportsYtdlProtocol) {
args.push(`${cmdArgs.videoUrl}ytdl://${payload.videoId}`)
} else {
args.push(`${cmdArgs.videoUrl}https://www.youtube.com/watch?v=${payload.videoId}`)
}
args.push(`${cmdArgs.videoUrl}https://www.youtube.com/watch?v=${payload.videoId}`)
}
}
@ -611,6 +609,18 @@ const mutations = {
state.sessionSearchHistory = history
},
setDeArrowCache (state, cache) {
state.deArrowCache = cache
},
addVideoToDeArrowCache (state, payload) {
const sameVideo = state.deArrowCache[payload.videoId]
if (!sameVideo) {
state.deArrowCache[payload.videoId] = payload
}
},
addToSessionSearchHistory (state, payload) {
const sameSearch = state.sessionSearchHistory.findIndex((search) => {
return search.query === payload.query && searchFiltersMatch(payload.searchSettings, search.searchSettings)

View File

@ -262,7 +262,6 @@ export default defineComponent({
}
this.id = this.$route.params.id
let currentTab = this.$route.params.currentTab ?? 'videos'
this.searchPage = 2
this.relatedChannels = []
this.latestVideos = []
@ -290,23 +289,7 @@ export default defineComponent({
this.showLiveSortBy = true
this.showPlaylistSortBy = true
if (this.hideChannelShorts && currentTab === 'shorts') {
currentTab = 'videos'
}
if (this.hideLiveStreams && currentTab === 'live') {
currentTab = 'videos'
}
if (this.hideChannelPlaylists && currentTab === 'playlists') {
currentTab = 'videos'
}
if (this.hideChannelCommunity && currentTab === 'community') {
currentTab = 'videos'
}
this.currentTab = currentTab
this.currentTab = this.currentOrFirstTab(this.$route.params.currentTab)
if (this.id === '@@@') {
this.showShareMenu = false
@ -395,25 +378,7 @@ export default defineComponent({
this.id = this.$route.params.id
let currentTab = this.$route.params.currentTab ?? 'videos'
if (this.hideChannelShorts && currentTab === 'shorts') {
currentTab = 'videos'
}
if (this.hideLiveStreams && currentTab === 'live') {
currentTab = 'videos'
}
if (this.hideChannelPlaylists && currentTab === 'playlists') {
currentTab = 'videos'
}
if (this.hideChannelCommunity && currentTab === 'community') {
currentTab = 'videos'
}
this.currentTab = currentTab
this.currentTab = this.currentOrFirstTab(this.$route.params.currentTab)
if (this.id === '@@@') {
this.showShareMenu = false
@ -453,6 +418,14 @@ export default defineComponent({
}
},
currentOrFirstTab: function (currentTab) {
if (this.tabInfoValues.includes(currentTab)) {
return currentTab
}
return this.tabInfoValues[0]
},
getChannelLocal: async function () {
this.apiUsed = 'local'
this.isLoading = true

View File

@ -8,7 +8,6 @@ import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue'
import { MAIN_PROFILE_ID } from '../../../constants'
import { calculatePublishedDate, copyToClipboard, showToast } from '../../helpers/utils'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { getLocalChannelVideos } from '../../helpers/api/local'
@ -30,7 +29,7 @@ export default defineComponent({
dataLimit: 100,
videoList: [],
errorChannels: [],
attemptedFetch: false
attemptedFetch: false,
}
},
computed: {
@ -69,13 +68,27 @@ export default defineComponent({
activeProfile: function () {
return this.$store.getters.getActiveProfile
},
profileSubscriptions: function () {
return this.$store.getters.getProfileSubscriptions
activeProfileId: function () {
return this.activeProfile._id
},
allSubscriptionsList: function () {
return this.$store.getters.getAllSubscriptionsList
cacheEntriesForAllActiveProfileChannels() {
const entries = []
this.activeSubscriptionList.forEach((channel) => {
const cacheEntry = this.$store.getters.getSubscriptionsCacheEntriesForOneChannel(channel.id)
if (cacheEntry == null) { return }
entries.push(cacheEntry)
})
return entries
},
videoCacheForAllActiveProfileChannelsPresent() {
if (this.cacheEntriesForAllActiveProfileChannels.length === 0) { return false }
if (this.cacheEntriesForAllActiveProfileChannels.length < this.activeSubscriptionList.length) { return false }
return this.cacheEntriesForAllActiveProfileChannels.every((cacheEntry) => {
return cacheEntry.videos != null
})
},
historyCache: function () {
@ -96,12 +109,13 @@ export default defineComponent({
fetchSubscriptionsAutomatically: function() {
return this.$store.getters.getFetchSubscriptionsAutomatically
}
},
},
watch: {
activeProfile: async function (_) {
this.getProfileSubscriptions()
}
this.isLoading = true
this.loadVideosFromCacheSometimes()
},
},
mounted: async function () {
document.addEventListener('keydown', this.keyboardShortcutHandler)
@ -112,66 +126,63 @@ export default defineComponent({
this.dataLimit = dataLimit
}
if (this.profileSubscriptions.videoList.length !== 0) {
if (this.profileSubscriptions.activeProfile === this.activeProfile._id) {
const subscriptionList = JSON.parse(JSON.stringify(this.profileSubscriptions))
if (this.hideWatchedSubs) {
this.videoList = await Promise.all(subscriptionList.videoList.filter((video) => {
const historyIndex = this.historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return historyIndex === -1
}))
} else {
this.videoList = subscriptionList.videoList
this.errorChannels = subscriptionList.errorChannels
}
} else {
this.getProfileSubscriptions()
}
this.isLoading = false
} else if (this.fetchSubscriptionsAutomatically) {
setTimeout(async () => {
this.getSubscriptions()
}, 300)
} else {
this.isLoading = false
}
this.loadVideosFromCacheSometimes()
},
beforeDestroy: function () {
document.removeEventListener('keydown', this.keyboardShortcutHandler)
},
methods: {
loadVideosFromCacheSometimes() {
// This method is called on view visible
if (this.videoCacheForAllActiveProfileChannelsPresent) {
this.loadVideosFromCacheForAllActiveProfileChannels()
return
}
this.maybeLoadVideosForSubscriptionsFromRemote()
},
async loadVideosFromCacheForAllActiveProfileChannels() {
const videoList = []
this.activeSubscriptionList.forEach((channel) => {
const channelCacheEntry = this.$store.getters.getSubscriptionsCacheEntriesForOneChannel(channel.id)
videoList.push(...channelCacheEntry.videos)
})
this.updateVideoListAfterProcessing(videoList)
this.isLoading = false
},
goToChannel: function (id) {
this.$router.push({ path: `/channel/${id}` })
},
getSubscriptions: function () {
loadVideosForSubscriptionsFromRemote: async function () {
if (this.activeSubscriptionList.length === 0) {
this.isLoading = false
this.videoList = []
return
}
const channelsToLoadFromRemote = this.activeSubscriptionList
const videoList = []
let channelCount = 0
this.isLoading = true
let useRss = this.useRssFeeds
if (this.activeSubscriptionList.length >= 125 && !useRss) {
if (channelsToLoadFromRemote.length >= 125 && !useRss) {
showToast(
this.$t('Subscriptions["This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting"]'),
10000
)
useRss = true
}
this.isLoading = true
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
this.attemptedFetch = true
let videoList = []
let channelCount = 0
this.errorChannels = []
this.activeSubscriptionList.forEach(async (channel) => {
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
if (useRss) {
@ -187,86 +198,66 @@ export default defineComponent({
}
}
videoList = videoList.concat(videos)
channelCount++
const percentageComplete = (channelCount / this.activeSubscriptionList.length) * 100
const percentageComplete = (channelCount / channelsToLoadFromRemote.length) * 100
this.setProgressBarPercentage(percentageComplete)
this.updateSubscriptionsCacheForOneChannel({
channelId: channel.id,
videos: videos,
})
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
if (channelCount === this.activeSubscriptionList.length) {
videoList = await Promise.all(videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
}))
if (this.hideLiveStreams) {
videoList = videoList.filter(item => {
return (!item.liveNow && !item.isUpcoming)
})
}
if (this.hideUpcomingPremieres) {
videoList = videoList.filter(item => {
if (item.isRSS) {
// viewCount is our only method of detecting premieres in RSS
// data without sending an additional request.
// If we ever get a better flag, use it here instead.
return item.viewCount !== '0'
}
// Observed for premieres in Local API Subscriptions.
return item.durationText !== 'PREMIERE'
})
}
const profileSubscriptions = {
activeProfile: this.activeProfile._id,
videoList: videoList,
errorChannels: this.errorChannels
}
this.updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
},
this.videoList = await Promise.all(videoList.filter((video) => {
if (this.hideWatchedSubs) {
const historyIndex = this.historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return historyIndex === -1
} else {
return true
}
}))
this.updateProfileSubscriptions(profileSubscriptions)
this.isLoading = false
this.updateShowProgressBar(false)
if (this.activeProfile === MAIN_PROFILE_ID) {
this.updateAllSubscriptionsList(profileSubscriptions.videoList)
updateVideoListAfterProcessing(videoList) {
// Filtering and sorting based in preference
videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
})
if (this.hideLiveStreams) {
videoList = videoList.filter(item => {
return (!item.liveNow && !item.isUpcoming)
})
}
if (this.hideUpcomingPremieres) {
videoList = videoList.filter(item => {
if (item.isRSS) {
// viewCount is our only method of detecting premieres in RSS
// data without sending an additional request.
// If we ever get a better flag, use it here instead.
return item.viewCount !== '0'
}
// Observed for premieres in Local API Subscriptions.
return item.premiereDate == null
})
}
this.videoList = videoList.filter((video) => {
if (this.hideWatchedSubs) {
const historyIndex = this.historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return historyIndex === -1
} else {
return true
}
})
},
getProfileSubscriptions: async function () {
if (this.allSubscriptionsList.length !== 0) {
this.isLoading = true
this.videoList = await Promise.all(this.allSubscriptionsList.filter((video) => {
const channelIndex = this.activeSubscriptionList.findIndex((x) => {
return x.id === video.authorId
})
if (this.hideWatchedSubs) {
const historyIndex = this.historyCache.findIndex((x) => {
return x.videoId === video.videoId
})
return channelIndex !== -1 && historyIndex === -1
} else {
return channelIndex !== -1
}
}))
this.isLoading = false
} else if (this.fetchSubscriptionsAutomatically) {
this.getSubscriptions()
} else if (this.activeProfile._id === this.profileSubscriptions.activeProfile) {
this.videoList = this.profileSubscriptions.videoList
maybeLoadVideosForSubscriptionsFromRemote: async function () {
if (this.fetchSubscriptionsAutomatically) {
// `this.isLoading = false` is called inside `loadVideosForSubscriptionsFromRemote` when needed
await this.loadVideosForSubscriptionsFromRemote()
} else {
this.videoList = []
this.attemptedFetch = false
this.isLoading = false
}
},
@ -487,7 +478,7 @@ export default defineComponent({
case 'r':
case 'R':
if (!this.isLoading) {
this.getSubscriptions()
this.loadVideosForSubscriptionsFromRemote()
}
break
}
@ -495,8 +486,7 @@ export default defineComponent({
...mapActions([
'updateShowProgressBar',
'updateProfileSubscriptions',
'updateAllSubscriptionsList'
'updateSubscriptionsCacheForOneChannel',
]),
...mapMutations([

View File

@ -68,7 +68,7 @@
:title="$t('Subscriptions.Refresh Subscriptions')"
:size="12"
theme="primary"
@click="getSubscriptions"
@click="loadVideosForSubscriptionsFromRemote"
/>
</div>
</template>

View File

@ -433,6 +433,7 @@ Settings:
Skip Option: تخطي الخِيار
Show In Seek Bar: إظهار في الشريط
Do Nothing: لا تفعل شيئا
UseDeArrowTitles: استخدام عناوين فيديو DeArrow
External Player Settings:
External Player: المشغل الخارجي
Custom External Player Arguments: وسيطات المشغل الخارجي المخصصة
@ -926,6 +927,8 @@ Tooltips:
Hide Channels: أدخل اسم القناة أو رقم تعريف القناة لإخفاء كل الفيديوهات وقوائم
التشغيل والقناة نفسها من الظهور في "بحث Google" أو "المحتوى الرائج". يجب أن
يكون اسم القناة الذي تم إدخاله مطابقا تماما وحساسا لحالة الأحرف.
SponsorBlock Settings:
UseDeArrowTitles: استبدل عناوين الفيديو بالعناوين التي أرسلها المستخدم من DeArrow.
This video is unavailable because of missing formats. This can happen due to country unavailability.: هذا
الفيديو غير متاح الآن لعدم وجود ملفات فيديو . هذا قد يكون بسبب أن الفيديو غير متاح
في بلدك.

View File

@ -414,6 +414,7 @@ Settings:
Enable SponsorBlock: Enable SponsorBlock
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock API Url (Default is https://sponsor.ajay.app)
Notify when sponsor segment is skipped: Notify when sponsor segment is skipped
UseDeArrowTitles: Use DeArrow Video Titles
Skip Options:
Skip Option: Skip Option
Auto Skip: Auto Skip
@ -848,6 +849,8 @@ Tooltips:
when the watch page is closed.
Experimental Settings:
Replace HTTP Cache: Disables Electron's disk based HTTP cache and enables a custom in-memory image cache. Will lead to increased RAM usage.
SponsorBlock Settings:
UseDeArrowTitles: Replace video titles with user-submitted titles from DeArrow.
# Toast Messages
Local API Error (Click to copy): Local API Error (Click to copy)

View File

@ -440,6 +440,7 @@ Settings:
Skip Option: Omitir opción
Auto Skip: Salto automático
Prompt To Skip: Solicitar que se omita
UseDeArrowTitles: Utilizar los títulos para el vídeo de DeArrow
External Player Settings:
Custom External Player Arguments: Argumentos adicionales del reproductor
Custom External Player Executable: Ruta alternativa del ejecutable del reproductor
@ -958,6 +959,9 @@ Tooltips:
listas de reproducción y el propio canal y evitar que aparezcan en las búsquedas
o en las tendencias. El nombre del canal introducido debe coincidir completamente
y distingue entre mayúsculas y minúsculas.
SponsorBlock Settings:
UseDeArrowTitles: Sustituye los títulos de los vídeos por títulos enviados por
los usuarios desde DeArrow.
More: Más
Unknown YouTube url type, cannot be opened in app: Tipo de URL desconocido. No se
puede abrir en la aplicación

View File

@ -461,6 +461,7 @@ Settings:
Prompt To Skip: Inviter à ignorer
Do Nothing: Ne rien faire
Category Color: Couleur de la catégorie
UseDeArrowTitles: Utiliser les titres vidéo de DeArrow
External Player Settings:
Ignore Unsupported Action Warnings: Ignorer les avertissements concernant les
actions non prises en charge
@ -1000,6 +1001,9 @@ Tooltips:
toutes les vidéos, les listes de lecture et la chaîne elle-même d'apparaître
dans les recherches ou les tendances. Le nom du canal entré doit être une correspondance
complète et est sensible à la casse.
SponsorBlock Settings:
UseDeArrowTitles: Remplacez les titres des vidéos par des titres proposés par
les utilisateurs de DeArrow.
More: Plus
Playing Next Video Interval: Lecture de la prochaine vidéo en un rien de temps. Cliquez
pour annuler. | Lecture de la prochaine vidéo dans {nextVideoInterval} seconde.

View File

@ -438,6 +438,7 @@ Settings:
Do Nothing: לא לעשות כלום
Prompt To Skip: לשאול אם לדלג
Category Color: צבע קטגוריה
UseDeArrowTitles: להשתמש בכותרות סרטונים דרך DeArrow
Download Settings:
Download Settings: הגדרות הורדה
Ask Download Path: לשאול מה נתיב ההורדות
@ -720,7 +721,7 @@ Video:
recap: סיכום
External Player:
video: סרטון
playlist: פלייליסט
playlist: רשימת נגינה
OpenInTemplate: פתיחה בתוך {externalPlayer}
OpeningTemplate: '{videoOrPlaylist} נפתח ב־{externalPlayer}…'
UnsupportedActionTemplate: 'ב־{externalPlayer} אין תמיכה בפעולה: {action}'
@ -778,7 +779,7 @@ Playlist:
# On Video Watch Page
#* Published
#& Views
Playlist: פלייליסט
Playlist: רשימת נגינה
Toggle Theatre Mode: 'הפעלו / בטלו מצב קולנוע'
Change Format:
Change Media Formats: 'שינוי תצורות הסרטונים'
@ -865,6 +866,8 @@ Tooltips:
External Link Handling: "נא לבחור את התנהגות ברירת המחדל כשנלחץ קישור שלא ניתן
לפתוח ב־FreeTube.\nכברירת מחדל FreeTube יפתח את הקישור שנלחץ בדפדפן ברירת המחדל
שלך.\n"
Region for Trending: מגמות אזוריות מאפשרות לך לבחור סרטונים חמים של איזו מדינה
מעניין אותך לראות.
Player Settings:
Force Local Backend for Legacy Formats: עובד רק כאשר ה־API של Invidious הוא ברירת
המחדל שלך. כאשר האפשרות פעילה, ה־API המקומי יופעל וישתמש בתצורות המיושנות שהוחזרו
@ -912,6 +915,8 @@ Tooltips:
Hide Channels: יש למלא את שם או מזהה הערוץ כדי להסתיר את כל הסרטונים, רשימות הנגינה
ואת הערוץ עצמו כך שלא יופיע בחיפוש או במובילים. שם הערוץ שמילאת צריך להיות תואם
במלואו ותואם מבחינת רישיות (אותיות גדולות/קטנות).
SponsorBlock Settings:
UseDeArrowTitles: החלפת כותרות הסרטונים עם כותרות ששלחו משתמשים ב־DeArrow.
More: עוד
Open New Window: פתיחת חלון חדש
Search Bar:

View File

@ -448,6 +448,7 @@ Settings:
Do Nothing: Nincs művelet
Prompt To Skip: Kihagyás kérése
Category Color: Kategória színe
UseDeArrowTitles: DeArrow-videocímek használata
External Player Settings:
Ignore Unsupported Action Warnings: Nem támogatott műveleti figyelmeztetések figyelmen
kívül hagyva
@ -951,6 +952,9 @@ Tooltips:
az összes videót, lejátszási listát és magát a csatornát, hogy ne jelenjen meg
a keresésben vagy a népszerűségben. A megadott csatornanévnek teljes egyezésnek
kell lennie, és megkülönbözteti a kis- és nagybetűket.
SponsorBlock Settings:
UseDeArrowTitles: Cserélje le a videocímeket a DeArrow által beküldött, felhasználó
által beküldött címekre.
Playing Next Video Interval: A következő videó lejátszása folyamatban van. Kattintson
a törléshez. | A következő videó lejátszása {nextVideoInterval} másodperc múlva
történik. Kattintson a törléshez. | A következő videó lejátszása {nextVideoInterval}

View File

@ -451,6 +451,7 @@ Settings:
Prompt To Skip: Chiedi di saltare
Do Nothing: Non fare nulla
Category Color: Colore della categoria
UseDeArrowTitles: Usa i titoli dei video DeArrow
External Player Settings:
Custom External Player Arguments: Argomenti del lettore esterno personalizzato
Custom External Player Executable: File eseguibile del lettore esterno personalizzato
@ -968,6 +969,9 @@ Tooltips:
le playlist e il canale stesso dalla visualizzazione nelle ricerche o nelle
tendenze. Il nome del canale inserito deve essere una corrispondenza completa
e fa distinzione tra maiuscole e minuscole.
SponsorBlock Settings:
UseDeArrowTitles: Sostituisci i titoli dei video con titoli inviati dagli utenti
da DeArrow.
Playing Next Video Interval: Riproduzione del video successivo tra un attimo. Clicca
per annullare. | Riproduzione del video successivo tra {nextVideoInterval} secondi.
Clicca per annullare. | Riproduzione del video successivo tra {nextVideoInterval}

View File

@ -91,7 +91,7 @@ Playlists: '再生リスト'
User Playlists:
Your Playlists: 'あなたの再生リスト'
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: 保存した動画はありません。一覧に表示させるには、ビデオの角にある保存ボタンをクリックします
Playlist Message:
Playlist Message:
このページは、完全に動作する動画リストではありません。保存またはお気に入りと設定した動画のみが表示されます。操作が完了すると、現在ここにあるすべての動画は「お気に入り」の動画リストに移動します。
Search bar placeholder: 動画リスト内の検索
Empty Search Message: この再生リストに、検索に一致する動画はありません
@ -404,6 +404,7 @@ Settings:
Prompt To Skip: ログイン プロンプトのスキップ
Do Nothing: 何もしない
Category Color: カテゴリの色
UseDeArrowTitles: 動画タイトルに DeArrow を使用する
External Player Settings:
Custom External Player Arguments: カスタム外部プレーヤー引数
Custom External Player Executable: カスタム外部プレーヤーの実行可能ファイル
@ -848,8 +849,10 @@ Tooltips:
Replace HTTP Cache: Electron のディスクに基づく HTTP キャッシュを無効化し、メモリ内で独自の画像キャッシュを使用します。このことにより
RAM の使用率は増加します。
Distraction Free Settings:
Hide Channels: チャンネル名またはチャンネル ID
Hide Channels: チャンネル名またはチャンネル ID
を入力すると、すべてのビデオ、再生リスト、およびチャンネル自体が検索や人気に表示されなくなります。入力するチャンネル名は完全に一致することが必要で、大文字と小文字を区別します。
SponsorBlock Settings:
UseDeArrowTitles: 動画のタイトルを DeArrow からユーザーが投稿したタイトルに置き換えます。
Playing Next Video Interval: すぐに次の動画を再生します。クリックするとキャンセル。|次の動画を {nextVideoInterval}
秒で再生します。クリックするとキャンセル。|次の動画を {nextVideoInterval} 秒で再生します。クリックするとキャンセル。
More: もっと見る

View File

@ -267,6 +267,8 @@ Settings:
Enter Fullscreen on Display Rotate: Fullskjermsvisning når skjermen roteres
Skip by Scrolling Over Video Player: Hopp over ved å rulle over videoavspiller
Allow DASH AV1 formats: Tillat DASH AV1-formater
Comment Auto Load:
Comment Auto Load: Auto-innlasting av kommentarer
Privacy Settings:
Privacy Settings: 'Personvernsinnstillinger'
Remember History: 'Husk historikk'
@ -402,6 +404,15 @@ Settings:
Hide Video Description: Skjul videobeskrivelse
Display Titles Without Excessive Capitalisation: Vis titler uten overdrevet bruk
av store bokstaver
Hide Featured Channels: Skjult framhevede kanaler
Hide Channel Community: Skjul kanalgemenskap
Hide Channel Shorts: Skjul kanal-kortvideoer
Sections:
Side Bar: Sidefelt
Channel Page: Kanalside
General: Generelt
Watch Page: Seerlogg
Hide Channel Playlists: Skjul kanalspilleliste
The app needs to restart for changes to take effect. Restart and apply change?: Start
programmet på ny for å ta i bruk endringene?
SponsorBlock Settings:
@ -418,6 +429,7 @@ Settings:
Auto Skip: Automatisk
Show In Seek Bar: Vis i blafringsfelt
Category Color: Kategorifarge
UseDeArrowTitles: Bruk DeArrow-videonavn
External Player Settings:
Custom External Player Arguments: Egendefinerte argumenter for ekstern avspiller
Custom External Player Executable: Egendefinert kjørbar fil for ekstern avspiller
@ -531,6 +543,13 @@ Channel:
Community:
Community: Gemenskap
This channel currently does not have any posts: Denne kanalen har ingen oppføringer
Shorts:
This channel does not currently have any shorts: Denne kanalen har ingen kortvideoer
Shorts: Kortvideoer
Live:
Live: Direkte
This channel does not currently have any live streams: Denne kanalen har ikke
noen direktestrømmer
Video:
Mark As Watched: 'Marker som sett'
Remove From History: 'Fjern fra historikk'
@ -663,6 +682,8 @@ Video:
Premieres on: Har première
Premieres: Première
Upcoming: Kommende
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Sanntidssludring
er ikke tilgjengelig for denne strømmen. Opplasteren kan ha skrudd det av.
Videos:
#& Sort By
Sort By:
@ -818,8 +839,8 @@ Tooltips:
Preferred API Backend: Velg metoden FreeTube bruker til å hente data. Det lokale
API-et er en innebygd utpakker. Invidious-API-et krever en Invidious-tjener
å koble til.
External Link Handling: "Velg oppførsel når en link som ikke kan åpnes i FreeTube\
\ klikkes.\nSom forvalg vil FreeTube åpne lenken i nettleseren din.\n"
External Link Handling: "Velg oppførsel når en link som ikke kan åpnes i FreeTube
klikkes.\nSom forvalg vil FreeTube åpne lenken i nettleseren din.\n"
Subscription Settings:
Fetch Feeds from RSS: Bruk RSS istedenfor FreeTube sin forvalgte metode for innhenting
av din abonnementsstrøm. RSS er raskere og forhindrer IP-blokkering, men mangler
@ -869,6 +890,8 @@ Tooltips:
spillelister og selve kanalen fra å vises i søk eller på vei opp. Kanalnavnet
som skrives inn må være et nøyaktig treff, og gjør forskjell på små og store
bokstaver.
SponsorBlock Settings:
UseDeArrowTitles: Erstatt videonavn med brukerinnsendte navn fra DeArrow.
A new blog is now available, {blogTitle}. Click to view more: Et nytt blogginnlegg
er tilgjengelig, {blogTitle}. Klikk her for å se mer
The playlist has been reversed: Spillelisten har blitt snudd
@ -924,3 +947,7 @@ Clipboard:
uten sikker forbindelse
Copy failed: Kunne ikke kopiere til utklippstavlen
Downloading has completed: «{videoTitle}» er nedlastet
Hashtag:
Hashtag: Emneknagg
This hashtag does not currently have any videos: Denne emneknaggen har ingen videoer
enda

View File

@ -442,6 +442,7 @@ Settings:
Prompt To Skip: Atlamak İçin Sor
Do Nothing: Hiçbir Şey Yapma
Category Color: Kategori Rengi
UseDeArrowTitles: DeArrow Video Başlıklarını Kullan
External Player Settings:
Custom External Player Arguments: Özel Harici Oynatıcı Argümanları
Custom External Player Executable: Özel Harici Oynatıcı Çalıştırılabilir Dosyası
@ -950,6 +951,9 @@ Tooltips:
Hide Channels: Tüm videoların, oynatma listelerinin ve kanalın kendisinin arama
veya öne çıkanlarda görünmesini engellemek için bir kanal adı veya kanal kimliği
girin. Girilen kanal adı tam olarak eşleşmelidir ve büyük/küçük harfe duyarlıdır.
SponsorBlock Settings:
UseDeArrowTitles: Video başlıklarını DeArrow'dan kullanıcıların gönderdiği başlıklarla
değiştir.
Playing Next Video Interval: Sonraki video hemen oynatılıyor. İptal etmek için tıklayın.
| Sonraki video {nextVideoInterval} saniye içinde oynatılıyor. İptal etmek için
tıklayın. | Sonraki video {nextVideoInterval} saniye içinde oynatılıyor. İptal etmek

View File

@ -413,6 +413,7 @@ Settings:
Prompt To Skip: Запит на пропуск
Do Nothing: Нічого не робити
Category Color: Колір категорії
UseDeArrowTitles: Використовувати назви для відео DeArrow
External Player Settings:
Custom External Player Arguments: Власні аргументи зовнішнього програвача
Custom External Player Executable: Власний виконуваний зовнішній програвач
@ -866,6 +867,8 @@ Tooltips:
Hide Channels: Введіть назву або ID каналу, щоб сховати всі відео, списки відтворення
та сам канал від появи в пошуку або в тренді. Введена назва каналу повинна повністю
збігатися і чутлива до регістру.
SponsorBlock Settings:
UseDeArrowTitles: Замінити назви відео на надіслані користувачем назви з DeArrow.
Local API Error (Click to copy): 'Помилка локального API (натисніть, щоб скопіювати)'
Invidious API Error (Click to copy): 'Помилка Invidious API (натисніть, щоб скопіювати)'
Falling back to Invidious API: 'Повернення до API Invidious'

View File

@ -401,6 +401,7 @@ Settings:
Do Nothing: 什么也不做
Skip Option: 跳过选项
Category Color: 类别颜色
UseDeArrowTitles: 使用 DeArrow 视频标题
External Player Settings:
Custom External Player Arguments: 定制外部播放器的参数
Custom External Player Executable: 自定义外部播放器的可执行文件
@ -837,6 +838,8 @@ Tooltips:
Replace HTTP Cache: 禁用 Electron 基于磁盘的 HTTP 缓存,启用自定义内存中图像缓存。会增加内存的使用。
Distraction Free Settings:
Hide Channels: 输入频道名称或频道 ID 以隐藏所有视频、播放列表和频道本身,使其不出现在搜索结果或热门中。 输入的频道名称必须完全匹配,并且区分大小写。
SponsorBlock Settings:
UseDeArrowTitles: 使用来自 DeArrow 的用户提交的标题替换原始视频标题。
More: 更多
Open New Window: 打开新窗口
Search Bar:

View File

@ -403,6 +403,7 @@ Settings:
Prompt To Skip: 提示略過
Do Nothing: 不要做任何事
Category Color: 分類色彩
UseDeArrowTitles: 使用 DeArrow 影片標題
External Player Settings:
Custom External Player Arguments: 自訂外部播放程式參數
Custom External Player Executable: 自訂外部播放程式可執行檔
@ -850,6 +851,8 @@ Tooltips:
Replace HTTP Cache: 停用 Electron 以磁碟為基礎的 HTTP 快取並啟用自訂的記憶體圖片快取。會導致記憶體使用量增加。
Distraction Free Settings:
Hide Channels: 輸入頻道名稱或頻道 ID 以隱藏所有影片、播放清單與頻道本身,使其完全不出現在搜尋或趨勢中。輸入的頻道名稱必須完全符合,且區分大小寫。
SponsorBlock Settings:
UseDeArrowTitles: 將影片標題取代為 DeArrow 使用者遞交的標題。
Playing Next Video Interval: 馬上播放下一個影片。點擊取消。| 播放下一個影片的時間為{nextVideoInterval}秒。點擊取消。|
播放下一個影片的時間為{nextVideoInterval}秒。點擊取消。
More: 更多

View File

@ -7,6 +7,11 @@
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876"
integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==
"@aashutoshrathi/word-wrap@^1.2.3":
version "1.2.6"
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
"@ampproject/remapping@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
@ -1117,14 +1122,14 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884"
integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==
"@eslint/eslintrc@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331"
integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==
"@eslint/eslintrc@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d"
integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.5.2"
espree "^9.6.0"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@ -1132,10 +1137,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.43.0":
version "8.43.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0"
integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==
"@eslint/js@8.44.0":
version "8.44.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==
"@fortawesome/fontawesome-common-types@6.4.0":
version "6.4.0"
@ -1890,6 +1895,11 @@ acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
acorn@^8.9.0:
version "8.9.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59"
integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==
aes-decrypter@3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/aes-decrypter/-/aes-decrypter-3.1.3.tgz#65ff5f2175324d80c41083b0e135d1464b12ac35"
@ -1936,17 +1946,7 @@ ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.0.1:
version "8.11.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ajv@^8.9.0:
ajv@^8.0.0, ajv@^8.0.1, ajv@^8.9.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
@ -3344,10 +3344,10 @@ electron-to-chromium@^1.4.411:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.411.tgz#8cb7787f0442fcb4209590e9951bdb482caa93b2"
integrity sha512-5VXLW4Qw89vM2WTICHua/y8v7fKGDRVa2VPOtBB9IpLvW316B+xd8yD1wTmLPY2ot/00P/qt87xdolj4aG/Lzg==
electron@^22.3.14:
version "22.3.14"
resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.14.tgz#539fc7d7b6df37483aaa351856a28e43092d550e"
integrity sha512-WxVcLnC4DrkBLN1/BwpxNkGvVq8iq1hM7lae5nvjnSYg/bwVbuo1Cwc80Keft4MIWKlYCXNiKKqs3qCXV4Aiaw==
electron@^22.3.15:
version "22.3.15"
resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.15.tgz#8da94b6f98529792aae6b9f54c9abbff0a2cd7f6"
integrity sha512-KhxJkx2tfB8Q1moUI3sI/x48lehTk3wUEwwaKKkfzSKT3m7nK/g1YSYiYe4c8WuqODAcJKhB1MOvRv3WmhBYBw==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^16.11.26"
@ -3762,15 +3762,15 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint@^8.43.0:
version "8.43.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094"
integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==
eslint@^8.44.0:
version "8.44.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500"
integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0"
"@eslint/eslintrc" "^2.0.3"
"@eslint/js" "8.43.0"
"@eslint/eslintrc" "^2.1.0"
"@eslint/js" "8.44.0"
"@humanwhocodes/config-array" "^0.11.10"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@ -3782,7 +3782,7 @@ eslint@^8.43.0:
escape-string-regexp "^4.0.0"
eslint-scope "^7.2.0"
eslint-visitor-keys "^3.4.1"
espree "^9.5.2"
espree "^9.6.0"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
@ -3802,17 +3802,17 @@ eslint@^8.43.0:
lodash.merge "^4.6.2"
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.1"
optionator "^0.9.3"
strip-ansi "^6.0.1"
strip-json-comments "^3.1.0"
text-table "^0.2.0"
espree@^9.0.0, espree@^9.3.1, espree@^9.5.2:
version "9.5.2"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b"
integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==
espree@^9.0.0, espree@^9.3.1, espree@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.0.tgz#80869754b1c6560f32e3b6929194a3fe07c5b82f"
integrity sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==
dependencies:
acorn "^8.8.0"
acorn "^8.9.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
@ -6055,17 +6055,17 @@ open@^8.0.9:
is-docker "^2.1.1"
is-wsl "^2.2.0"
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
optionator@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==
dependencies:
"@aashutoshrathi/word-wrap" "^1.2.3"
deep-is "^0.1.3"
fast-levenshtein "^2.0.6"
levn "^0.4.1"
prelude-ls "^1.2.1"
type-check "^0.4.0"
word-wrap "^1.2.3"
p-cancelable@^2.0.0:
version "2.1.1"
@ -8346,10 +8346,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.88.0:
version "5.88.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.0.tgz#a07aa2f8e7a64a8f1cec0c6c2e180e3cb34440c8"
integrity sha512-O3jDhG5e44qIBSi/P6KpcCcH7HD+nYIHVBhdWFxcLOcIGN8zGo5nqF3BjyNCxIh4p1vFdNnreZv2h2KkoAw3lw==
webpack@^5.88.1:
version "5.88.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.1.tgz#21eba01e81bd5edff1968aea726e2fbfd557d3f8"
integrity sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.0"
@ -8454,11 +8454,6 @@ wildcard@^2.0.0:
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
word-wrap@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"