diff --git a/README.md b/README.md index dddf9479a008a..8c538e9df15a0 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ These builds are maintained by the community. While they should be safe, downloa * Scoop (Windows Only): [Usage](https://github.com/ScoopInstaller/Scoop) -* Snap: [Download](https://snapcraft.io/freetube-snap) and [Source Code](https://github.com/CapeCrusader321/Freetube-Snap) +* Snap: [Download](https://snapcraft.io/freetube-snap) and [Source Code](https://launchpad.net/freetube) * Windows Package Manager (winget): [Usage](https://docs.microsoft.com/en-us/windows/package-manager/winget/) diff --git a/package.json b/package.json index 792338d708d7e..02e6b398259ef 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "vue-router": "^3.6.5", "vue-tiny-slider": "^0.1.39", "vuex": "^3.6.2", - "youtubei.js": "^5.6.0" + "youtubei.js": "^5.8.0" }, "devDependencies": { "@babel/core": "^7.22.9", @@ -91,23 +91,23 @@ "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "electron": "^22.3.18", - "electron-builder": "^23.6.0", - "eslint": "^8.45.0", - "eslint-config-prettier": "^8.8.0", + "electron-builder": "^24.6.3", + "eslint": "^8.46.0", + "eslint-config-prettier": "^8.9.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.28.0", "eslint-plugin-jsonc": "^2.9.0", "eslint-plugin-n": "^16.0.1", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-unicorn": "^48.0.0", - "eslint-plugin-vue": "^9.15.1", + "eslint-plugin-unicorn": "^48.0.1", + "eslint-plugin-vue": "^9.16.1", "eslint-plugin-vuejs-accessibility": "^2.1.0", "eslint-plugin-yml": "^1.8.0", "html-webpack-plugin": "^5.5.3", "js-yaml": "^4.1.0", "json-minimizer-webpack-plugin": "^4.0.0", - "lefthook": "^1.4.6", + "lefthook": "^1.4.7", "mini-css-extract-plugin": "^2.7.6", "npm-run-all": "^4.1.5", "postcss": "^8.4.26", diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js index 271325872f1ca..48ede5c6247d6 100644 --- a/src/renderer/components/data-settings/data-settings.js +++ b/src/renderer/components/data-settings/data-settings.js @@ -421,7 +421,7 @@ export default defineComponent({ } const newPipeSubscriptions = newPipeData.subscriptions.filter((channel, index) => { - return channel.service_id === 0 + return new URL(channel.url).hostname === 'www.youtube.com' }) const subscriptions = [] @@ -1060,10 +1060,9 @@ export default defineComponent({ invidiousAPICall(subscriptionsPayload).then((response) => { resolve(response) }).catch((err) => { - console.error(err) const errorMessage = this.$t('Invidious API Error (Click to copy)') - showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => { - copyToClipboard(err.responseJSON.error) + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) }) if (process.env.IS_ELECTRON && this.backendFallback && this.backendPreference === 'invidious') { @@ -1081,7 +1080,7 @@ export default defineComponent({ const channel = await getLocalChannel(channelId) if (channel.alert) { - return undefined + return [] } return { diff --git a/src/renderer/components/ft-input/ft-input.js b/src/renderer/components/ft-input/ft-input.js index 2f97cf6132471..45e0e73a2ce94 100644 --- a/src/renderer/components/ft-input/ft-input.js +++ b/src/renderer/components/ft-input/ft-input.js @@ -1,6 +1,7 @@ import { defineComponent } from 'vue' import FtTooltip from '../ft-tooltip/ft-tooltip.vue' import { mapActions } from 'vuex' +import { isKeyboardEventKeyPrintableChar, isNullOrEmpty } from '../../helpers/strings' export default defineComponent({ name: 'FtInput', @@ -78,7 +79,8 @@ export default defineComponent({ searchState: { showOptions: false, selectedOption: -1, - isPointerInList: false + isPointerInList: false, + keyboardSelectedOptionIndex: -1, }, visibleDataList: this.dataList, // This button should be invisible on app start @@ -103,7 +105,23 @@ export default defineComponent({ inputDataPresent: function () { return this.inputData.length > 0 - } + }, + inputDataDisplayed() { + if (!this.isSearch) { return this.inputData } + + const selectedOptionValue = this.searchStateKeyboardSelectedOptionValue + if (selectedOptionValue != null && selectedOptionValue !== '') { + return selectedOptionValue + } + + return this.inputData + }, + + searchStateKeyboardSelectedOptionValue() { + if (this.searchState.keyboardSelectedOptionIndex === -1) { return null } + + return this.visibleDataList[this.searchState.keyboardSelectedOptionIndex] + }, }, watch: { dataList(val, oldVal) { @@ -133,11 +151,15 @@ export default defineComponent({ if (!this.inputDataPresent) { return } this.searchState.showOptions = false + this.searchState.selectedOption = -1 + this.searchState.keyboardSelectedOptionIndex = -1 this.$emit('input', this.inputData) this.$emit('click', this.inputData, { event: e }) }, handleInput: function (val) { + this.inputData = val + if (this.isSearch && this.searchState.selectedOption !== -1 && this.inputData === this.visibleDataList[this.searchState.selectedOption]) { return } @@ -217,6 +239,9 @@ export default defineComponent({ this.handleClick() }, + /** + * @param {KeyboardEvent} event + */ handleKeyDown: function (event) { if (event.key === 'Enter') { // Update Input box value if enter key was pressed and option selected @@ -234,25 +259,32 @@ export default defineComponent({ this.searchState.showOptions = true const isArrow = event.key === 'ArrowDown' || event.key === 'ArrowUp' - if (isArrow) { - event.preventDefault() - if (event.key === 'ArrowDown') { - this.searchState.selectedOption = (this.searchState.selectedOption + 1) % this.visibleDataList.length - } else if (event.key === 'ArrowUp') { - if (this.searchState.selectedOption < 1) { - this.searchState.selectedOption = this.visibleDataList.length - 1 - } else { - this.searchState.selectedOption-- - } - } - if (this.searchState.selectedOption < 0) { - this.searchState.selectedOption = this.visibleDataList.length - } else if (this.searchState.selectedOption > this.visibleDataList.length - 1) { - this.searchState.selectedOption = 0 + if (!isArrow) { + const selectedOptionValue = this.searchStateKeyboardSelectedOptionValue + // Keyboard selected & is char + if (!isNullOrEmpty(selectedOptionValue) && isKeyboardEventKeyPrintableChar(event.key)) { + // Update input based on KB selected suggestion value instead of current input value + event.preventDefault() + this.handleInput(`${selectedOptionValue}${event.key}`) + return } - } else { + return + } + + event.preventDefault() + if (event.key === 'ArrowDown') { + this.searchState.selectedOption++ + } else if (event.key === 'ArrowUp') { + this.searchState.selectedOption-- + } + // Allow deselecting suggestion + if (this.searchState.selectedOption < -1) { + this.searchState.selectedOption = this.visibleDataList.length - 1 + } else if (this.searchState.selectedOption > this.visibleDataList.length - 1) { this.searchState.selectedOption = -1 } + // Update displayed value + this.searchState.keyboardSelectedOptionIndex = this.searchState.selectedOption }, handleInputBlur: function () { @@ -265,21 +297,19 @@ export default defineComponent({ updateVisibleDataList: function () { if (this.dataList.length === 0) { return } + // Reset selected option before it's updated + this.searchState.selectedOption = -1 + this.searchState.keyboardSelectedOptionIndex = -1 if (this.inputData === '') { this.visibleDataList = this.dataList return } // get list of items that match input const lowerCaseInputData = this.inputData.toLowerCase() - const visList = this.dataList.filter(x => { - if (x.toLowerCase().indexOf(lowerCaseInputData) !== -1) { - return true - } else { - return false - } - }) - this.visibleDataList = visList + this.visibleDataList = this.dataList.filter(x => { + return x.toLowerCase().indexOf(lowerCaseInputData) !== -1 + }) }, updateInputData: function(text) { diff --git a/src/renderer/components/ft-input/ft-input.vue b/src/renderer/components/ft-input/ft-input.vue index 4f6e9904f5cbb..3ffff6a8c1568 100644 --- a/src/renderer/components/ft-input/ft-input.vue +++ b/src/renderer/components/ft-input/ft-input.vue @@ -42,7 +42,7 @@ {{ list }} diff --git a/src/renderer/components/ft-list-video/ft-list-video.js b/src/renderer/components/ft-list-video/ft-list-video.js index b63f69980e4f7..27e7dc8759654 100644 --- a/src/renderer/components/ft-list-video/ft-list-video.js +++ b/src/renderer/components/ft-list-video/ft-list-video.js @@ -313,10 +313,17 @@ export default defineComponent({ }, displayTitle: function () { + let title + if (this.useDeArrowTitles && this.deArrowCache?.title) { + title = this.deArrowCache.title + } else { + title = this.title + } + if (this.showDistractionFreeTitles) { - return toDistractionFreeTitle(this.title) + return toDistractionFreeTitle(title) } else { - return this.title + return title } }, @@ -376,7 +383,7 @@ export default defineComponent({ }, deArrowCache: function () { - return this.$store.getters.getDeArrowCache(this.id) + return this.$store.getters.getDeArrowCache[this.id] }, }, watch: { @@ -394,15 +401,13 @@ export default defineComponent({ created: function () { this.parseVideoData() this.checkIfWatched() + + if (this.useDeArrowTitles && !this.deArrowCache) { + this.fetchDeArrowData() + } }, 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 } - + fetchDeArrowData: async function() { const videoId = this.id const data = await deArrowData(this.id) const cacheData = { videoId, title: null } @@ -412,7 +417,6 @@ export default defineComponent({ // Save data to cache whether data available or not to prevent duplicate requests this.$store.commit('addVideoToDeArrowCache', cacheData) - return cacheData }, handleExternalPlayer: function () { @@ -477,14 +481,20 @@ export default defineComponent({ } }, - parseVideoData: async function () { + parseVideoData: function () { this.id = this.data.videoId - this.title = (await this.getDeArrowDataEntry())?.title ?? this.data.title + this.title = this.data.title // this.thumbnail = this.data.videoThumbnails[4].url this.channelName = this.data.author ?? null this.channelId = this.data.authorId ?? null - this.duration = formatDurationAsTimestamp(this.data.lengthSeconds) + + if (this.data.isRSS && this.historyIndex !== -1) { + this.duration = formatDurationAsTimestamp(this.historyCache[this.historyIndex].lengthSeconds) + } else { + this.duration = formatDurationAsTimestamp(this.data.lengthSeconds) + } + this.description = this.data.description this.isLive = this.data.liveNow || this.data.lengthSeconds === 'undefined' this.isUpcoming = this.data.isUpcoming || this.data.premiere diff --git a/src/renderer/components/ft-subscribe-button/ft-subscribe-button.scss b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.css similarity index 100% rename from src/renderer/components/ft-subscribe-button/ft-subscribe-button.scss rename to src/renderer/components/ft-subscribe-button/ft-subscribe-button.css diff --git a/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue index bf6d90d9732a0..11bf54e7e66c6 100644 --- a/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue +++ b/src/renderer/components/ft-subscribe-button/ft-subscribe-button.vue @@ -9,4 +9,4 @@