From a660a5b20878684dc172a5a9144a8191f5592486 Mon Sep 17 00:00:00 2001 From: absidue <48293849+absidue@users.noreply.github.com> Date: Sat, 25 Jun 2022 12:56:02 +0200 Subject: [PATCH] Use native web browser features instead of jquery --- package.json | 1 - src/renderer/App.js | 96 ++++++++-------- .../ft-icon-button/ft-icon-button.js | 5 +- .../ft-profile-selector.js | 17 ++- .../ft-video-player/ft-video-player.js | 108 ++++++++++-------- .../proxy-settings/proxy-settings.js | 41 ++++--- src/renderer/components/top-nav/top-nav.js | 27 +++-- .../watch-video-description.js | 3 +- .../watch-video-live-chat.css | 6 + .../watch-video-live-chat.js | 16 ++- src/renderer/store/modules/invidious.js | 31 ++--- src/renderer/store/modules/sponsorblock.js | 29 ++--- src/renderer/views/Trending/Trending.js | 23 ++-- src/renderer/views/Watch/Watch.js | 62 +++++----- yarn.lock | 5 - 15 files changed, 241 insertions(+), 229 deletions(-) diff --git a/package.json b/package.json index 63e793ade31cc..f471864078153 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "electron-context-menu": "^3.1.2", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", - "jquery": "^3.6.0", "js-yaml": "^4.1.0", "lodash.debounce": "^4.0.8", "lodash.isequal": "^4.5.0", diff --git a/src/renderer/App.js b/src/renderer/App.js index fee872301e9a7..dcfd9131fab24 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -9,7 +9,6 @@ import FtPrompt from './components/ft-prompt/ft-prompt.vue' import FtButton from './components/ft-button/ft-button.vue' import FtToast from './components/ft-toast/ft-toast.vue' import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue' -import $ from 'jquery' import { marked } from 'marked' import Parser from 'rss-parser' import { IpcChannels } from '../constants' @@ -57,7 +56,7 @@ export default Vue.extend({ isOpen: function () { return this.$store.getters.getIsSideNavOpen }, - usingElectron: function() { + usingElectron: function () { return this.$store.getters.getUsingElectron }, showProgressBar: function () { @@ -81,9 +80,9 @@ export default Vue.extend({ windowTitle: function () { if (this.$route.meta.title !== 'Channel' && this.$route.meta.title !== 'Watch') { let title = - this.$route.meta.path === '/home' - ? process.env.PRODUCT_NAME - : `${this.$t(this.$route.meta.title)} - ${process.env.PRODUCT_NAME}` + this.$route.meta.path === '/home' + ? process.env.PRODUCT_NAME + : `${this.$t(this.$route.meta.title)} - ${process.env.PRODUCT_NAME}` if (!title) { title = process.env.PRODUCT_NAME } @@ -138,13 +137,13 @@ export default Vue.extend({ secColor: 'checkThemeSettings', - $route () { + $route() { // react to route changes... // Hide top nav filter panel on page change this.$refs.topNav.hideFilters() } }, - created () { + created() { this.checkThemeSettings() this.setWindowTitle() }, @@ -209,31 +208,34 @@ export default Vue.extend({ const { version } = require('../../package.json') const requestUrl = 'https://api.github.com/repos/freetubeapp/freetube/releases?per_page=1' - $.getJSON(requestUrl, (response) => { - const tagName = response[0].tag_name - const versionNumber = tagName.replace('v', '').replace('-beta', '') - this.updateChangelog = marked.parse(response[0].body) - this.changeLogTitle = response[0].name - - const message = this.$t('Version $ is now available! Click for more details') - this.updateBannerMessage = message.replace('$', versionNumber) - - const appVersion = version.split('.') - const latestVersion = versionNumber.split('.') - - if (parseInt(appVersion[0]) < parseInt(latestVersion[0])) { - this.showUpdatesBanner = true - } else if (parseInt(appVersion[1]) < parseInt(latestVersion[1])) { - this.showUpdatesBanner = true - } else if (parseInt(appVersion[2]) < parseInt(latestVersion[2]) && parseInt(appVersion[1]) <= parseInt(latestVersion[1])) { - this.showUpdatesBanner = true - } - }).fail((xhr, textStatus, error) => { - console.log(xhr) - console.log(textStatus) - console.log(requestUrl) - console.log(error) - }) + fetch(requestUrl) + .then((response) => response.json()) + .then((json) => { + const tagName = json[0].tag_name + const versionNumber = tagName.replace('v', '').replace('-beta', '') + this.updateChangelog = marked.parse(json[0].body) + this.changeLogTitle = json[0].name + + const message = this.$t('Version $ is now available! Click for more details') + this.updateBannerMessage = message.replace('$', versionNumber) + + const appVersion = version.split('.') + const latestVersion = versionNumber.split('.') + + if (parseInt(appVersion[0]) < parseInt(latestVersion[0])) { + this.showUpdatesBanner = true + } else if (parseInt(appVersion[1]) < parseInt(latestVersion[1])) { + this.showUpdatesBanner = true + } else if (parseInt(appVersion[2]) < parseInt(latestVersion[2]) && parseInt(appVersion[1]) <= parseInt(latestVersion[1])) { + this.showUpdatesBanner = true + } + }) + .catch((error) => { + console.group('checkForNewUpdates error') + console.error(requestUrl) + console.error(error) + console.groupEnd('checkForNewUpdates error') + }) } }, @@ -295,8 +297,8 @@ export default Vue.extend({ }, activateKeyboardShortcuts: function () { - $(document).on('keydown', this.handleKeyboardShortcuts) - $(document).on('mousedown', () => { + document.addEventListener('keydown', this.handleKeyboardShortcuts) + document.addEventListener('mousedown', () => { this.hideOutlines = true }) }, @@ -329,16 +331,20 @@ export default Vue.extend({ }, openAllLinksExternally: function () { - $(document).on('click', 'a[href^="http"]', (event) => { - this.handleLinkClick(event) + document.addEventListener('click', (event) => { + if (event.target.tagName.toLowerCase() === 'a' && event.target.href.startsWith('http')) { + this.handleLinkClick(event) + } }) - $(document).on('auxclick', 'a[href^="http"]', (event) => { - // auxclick fires for all clicks not performed with the primary button - // only handle the link click if it was the middle button, - // otherwise the context menu breaks - if (event.button === 1) { - this.handleLinkClick(event) + document.addEventListener('auxclick', (event) => { + if (event.target.tagName.toLowerCase() === 'a' && event.target.href.startsWith('http')) { + if (event.button === 1) { + // auxclick fires for all clicks not performed with the primary button + // only handle the link click if it was the middle button, + // otherwise the context menu breaks + this.handleLinkClick(event) + } } }) }, @@ -373,7 +379,7 @@ export default Vue.extend({ } }, - handleYoutubeLink: function (href, { doCreateNewWindow = false } = { }) { + handleYoutubeLink: function (href, { doCreateNewWindow = false } = {}) { this.getYoutubeUrlInfo(href).then((result) => { switch (result.urlType) { case 'video': { @@ -473,7 +479,7 @@ export default Vue.extend({ }) }, - openInternalPath: function({ path, doCreateNewWindow, query = {} }) { + openInternalPath: function ({ path, doCreateNewWindow, query = {} }) { if (this.usingElectron && doCreateNewWindow) { const { ipcRenderer } = require('electron') @@ -516,7 +522,7 @@ export default Vue.extend({ } }, - setWindowTitle: function() { + setWindowTitle: function () { if (this.windowTitle !== null) { document.title = this.windowTitle } diff --git a/src/renderer/components/ft-icon-button/ft-icon-button.js b/src/renderer/components/ft-icon-button/ft-icon-button.js index c324021bc5349..a6e9f26c44e20 100644 --- a/src/renderer/components/ft-icon-button/ft-icon-button.js +++ b/src/renderer/components/ft-icon-button/ft-icon-button.js @@ -1,5 +1,4 @@ import Vue from 'vue' -import $ from 'jquery' export default Vue.extend({ name: 'FtIconButton', @@ -64,7 +63,7 @@ export default Vue.extend({ }, methods: { toggleDropdown: function () { - const dropdownBox = $(`#${this.id}`) + const dropdownBox = document.getElementById(this.id) if (this.dropdownShown) { dropdownBox.get(0).style.display = 'none' @@ -99,7 +98,7 @@ export default Vue.extend({ }, focusOut: function () { - const dropdownBox = $(`#${this.id}`) + const dropdownBox = document.getElementById(this.id) dropdownBox.focusout() dropdownBox.get(0).style.display = 'none' diff --git a/src/renderer/components/ft-profile-selector/ft-profile-selector.js b/src/renderer/components/ft-profile-selector/ft-profile-selector.js index 6d038f6810e44..875fdfd47ad95 100644 --- a/src/renderer/components/ft-profile-selector/ft-profile-selector.js +++ b/src/renderer/components/ft-profile-selector/ft-profile-selector.js @@ -1,6 +1,5 @@ import Vue from 'vue' import { mapActions } from 'vuex' -import $ from 'jquery' import FtCard from '../../components/ft-card/ft-card.vue' import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue' @@ -36,8 +35,8 @@ export default Vue.extend({ } }, mounted: function () { - $('#profileList').focusout(() => { - $('#profileList')[0].style.display = 'none' + document.getElementById('profileList').addEventListener('focusout', (event) => { + event.currentTarget.style.display = 'none' // When pressing the profile button // It will make the menu reappear if we set `profileListShown` immediately setTimeout(() => { @@ -47,14 +46,14 @@ export default Vue.extend({ }, methods: { toggleProfileList: function () { - const profileList = $('#profileList') + const profileList = document.getElementById('profileList') if (this.profileListShown) { - profileList.get(0).style.display = 'none' + profileList.style.display = 'none' this.profileListShown = false } else { - profileList.get(0).style.display = 'inline' - profileList.get(0).focus() + profileList.style.display = 'inline' + profileList.focus() this.profileListShown = true } }, @@ -63,7 +62,7 @@ export default Vue.extend({ this.$router.push({ path: '/settings/profile/' }) - $('#profileList').focusout() + document.getElementById('profileList').dispatchEvent(new FocusEvent('focusout')) }, setActiveProfile: function (profile) { @@ -80,7 +79,7 @@ export default Vue.extend({ } } - $('#profileList').trigger('focusout') + document.getElementById('profileList').dispatchEvent(new FocusEvent('focusout')) }, ...mapActions([ diff --git a/src/renderer/components/ft-video-player/ft-video-player.js b/src/renderer/components/ft-video-player/ft-video-player.js index 2152a343fb32f..67bd1cfbb2019 100644 --- a/src/renderer/components/ft-video-player/ft-video-player.js +++ b/src/renderer/components/ft-video-player/ft-video-player.js @@ -2,7 +2,6 @@ import Vue from 'vue' import { mapActions } from 'vuex' import FtCard from '../ft-card/ft-card.vue' -import $ from 'jquery' import videojs from 'video.js' import qualitySelector from '@silvermine/videojs-quality-selector' import fs from 'fs' @@ -434,7 +433,7 @@ export default Vue.extend({ this.initializeSponsorBlock() } - $(document).on('keydown', this.keyboardShortcutHandler) + document.addEventListener('keydown', this.keyboardShortcutHandler) this.player.on('mousemove', this.hideMouseTimeout) this.player.on('mouseleave', this.removeMouseTimeout) @@ -941,16 +940,18 @@ export default Vue.extend({ this.selectedMimeType = 'auto' } - const qualityItems = $('.quality-item').get() - - $('.quality-item').removeClass('quality-selected') + document.querySelectorAll('.quality-item.quality-selected') + .forEach((selected) => { + selected.classList.remove('quality-selected') + }) - qualityItems.forEach((item) => { - const qualityText = $(item).find('.vjs-menu-item-text').get(0) - if (qualityText.innerText === selectedQuality.toLowerCase()) { - $(item).addClass('quality-selected') - } - }) + document.querySelectorAll('.quality-item') + .forEach((item) => { + const qualityText = item.querySelector('.vjs-menu-item-text') + if (qualityText.innerText === selectedQuality.toLowerCase()) { + item.classList.add('quality-selected') + } + }) /* if (this.selectedQuality === qualityLevel && this.using60Fps === is60Fps) { return @@ -1158,8 +1159,11 @@ export default Vue.extend({ this.toggleVideoLoop() }, createControlTextEl: function (button) { - return $(button).html($('
') - .attr('title', 'Toggle Loop')) + const contents = document.createElement('div') + contents.id = 'loopButton' + contents.className = 'vjs-icon-loop loop-white vjs-button loopWhite' + contents.title = 'Toggle Loop' + button.appendChild(contents) } }) videojs.registerComponent('loopButton', loopButton) @@ -1177,18 +1181,20 @@ export default Vue.extend({ const themeTextColor = await this.calculateColorLuminance(colorValues[nameIndex]) - $('#loopButton').addClass('vjs-icon-loop-active') + const loopButton = document.getElementById('loopButton') + loopButton.classList.add('vjs-icon-loop-active') if (themeTextColor === '#000000') { - $('#loopButton').addClass('loop-black') - $('#loopButton').removeClass('loop-white') + loopButton.classList.add('loop-black') + loopButton.classList.remove('loop-white') } this.player.loop(true) } else { - $('#loopButton').removeClass('vjs-icon-loop-active') - $('#loopButton').removeClass('loop-black') - $('#loopButton').addClass('loop-white') + const loopButton = document.getElementById('loopButton') + loopButton.classList.remove('vjs-icon-loop-active') + loopButton.classList.remove('loop-black') + loopButton.classList.add('loop-white') this.player.loop(false) } }, @@ -1204,10 +1210,13 @@ export default Vue.extend({ }, createControlTextEl: function (button) { // Add class name to button to be able to target it with CSS selector - return $(button) - .addClass('vjs-button-fullwindow') - .html($('
') - .attr('title', 'Full Window')) + button.classList.add('vjs-button-fullwindow') + + const contents = document.createElement('div') + contents.id = 'fullwindow' + contents.className = 'vjs-icon-fullwindow-enter vjs-button' + contents.title = 'Full Window' + button.appendChild(contents) } }) videojs.registerComponent('fullWindowButton', fullWindowButton) @@ -1229,10 +1238,13 @@ export default Vue.extend({ this.toggleTheatreMode() }, createControlTextEl: function (button) { - return $(button) - .addClass('vjs-button-theatre') - .html($(`
`)) - .attr('title', 'Toggle Theatre Mode') + button.classList.add('vjs-button-theatre') + + const contents = document.createElement('div') + contents.id = 'toggleTheatreModeButton' + contents.className = `vjs-icon-theatre-inactive${theatreModeActive} vjs-button` + contents.title = 'Toggle Theatre Mode' + button.appendChild(contents) } }) @@ -1241,11 +1253,11 @@ export default Vue.extend({ toggleTheatreMode: function() { if (!this.player.isFullscreen_) { - const toggleTheatreModeButton = $('#toggleTheatreModeButton') + const toggleTheatreModeButton = document.getElementById('toggleTheatreModeButton') if (!this.$parent.useTheatreMode) { - toggleTheatreModeButton.addClass('vjs-icon-theatre-active') + toggleTheatreModeButton.classList.add('vjs-icon-theatre-active') } else { - toggleTheatreModeButton.removeClass('vjs-icon-theatre-active') + toggleTheatreModeButton.classList.remove('vjs-icon-theatre-active') } } @@ -1265,9 +1277,11 @@ export default Vue.extend({ video.blur() }, createControlTextEl: function (button) { - return $(button) - .html('
') - .attr('title', 'Screenshot') + const contents = document.createElement('div') + contents.id = 'screenshotButton' + contents.className = 'vjs-icon-screenshot vjs-button vjs-hidden' + contents.title = 'Screenshot' + button.appendChild(contents) } }) @@ -1494,11 +1508,8 @@ export default Vue.extend({ ` */ }) - return $(button).html( - $(beginningHtml + qualityHtml + endingHtml).attr( - 'title', - 'Select Quality' - )) + button.innerHTML = beginningHtml + qualityHtml + endingHtml + button.childNodes[0].title = 'Select Quality' } }) videojs.registerComponent('dashQualitySelector', dashQualitySelector) @@ -1573,8 +1584,8 @@ export default Vue.extend({ this.player.removeClass('vjs-full-screen') this.player.isFullWindow = false document.documentElement.style.overflow = this.player.docOrigOverflow - $('body').removeClass('vjs-full-window') - $('#fullwindow').removeClass('vjs-icon-fullwindow-exit') + document.body.classList.remove('vjs-full-window') + document.getElementById('fullwindow').classList.remove('vjs-icon-fullwindow-exit') this.player.trigger('exitFullWindow') } else { this.player.addClass('vjs-full-screen') @@ -1582,8 +1593,8 @@ export default Vue.extend({ this.player.isFullWindow = true this.player.docOrigOverflow = document.documentElement.style.overflow document.documentElement.style.overflow = 'hidden' - $('body').addClass('vjs-full-window') - $('#fullwindow').addClass('vjs-icon-fullwindow-exit') + document.body.classList.add('vjs-full-window') + document.getElementById('fullwindow').classList.add('vjs-icon-fullwindow-exit') this.player.trigger('enterFullWindow') } } @@ -1594,8 +1605,8 @@ export default Vue.extend({ this.player.isFullWindow = false document.documentElement.style.overflow = this.player.docOrigOverflow this.player.removeClass('vjs-full-screen') - $('body').removeClass('vjs-full-window') - $('#fullwindow').removeClass('vjs-icon-fullwindow-exit') + document.body.classList.remove('vjs-full-window') + document.getElementById('fullwindow').classList.remove('vjs-icon-fullwindow-exit') this.player.trigger('exitFullWindow') } }, @@ -1613,7 +1624,8 @@ export default Vue.extend({ return } - const videoPlayer = $(`#${this.id} video`).get(0) + // css doesn't like numerical ids so we need to escape the id here + const videoPlayer = document.querySelector(`#${CSS.escape(this.id)} video`) if (typeof (videoPlayer) !== 'undefined') { videoPlayer.style.cursor = 'default' clearTimeout(this.mouseTimeout) @@ -1659,9 +1671,9 @@ export default Vue.extend({ toggleFullscreenClass: function () { if (this.player.isFullscreen()) { - $('body').addClass('vjs--full-screen-enabled') + document.body.classList.add('vjs--full-screen-enabled') } else { - $('body').removeClass('vjs--full-screen-enabled') + document.body.classList.remove('vjs--full-screen-enabled') } }, @@ -1748,7 +1760,7 @@ export default Vue.extend({ // This function should always be at the bottom of this file keyboardShortcutHandler: function (event) { - const activeInputs = $('.ft-input') + const activeInputs = document.querySelectorAll('.ft-input') for (let i = 0; i < activeInputs.length; i++) { if (activeInputs[i] === document.activeElement) { diff --git a/src/renderer/components/proxy-settings/proxy-settings.js b/src/renderer/components/proxy-settings/proxy-settings.js index 1f536e17a49bf..f616d4611be13 100644 --- a/src/renderer/components/proxy-settings/proxy-settings.js +++ b/src/renderer/components/proxy-settings/proxy-settings.js @@ -1,5 +1,4 @@ import Vue from 'vue' -import $ from 'jquery' import { mapActions } from 'vuex' import FtCard from '../ft-card/ft-card.vue' import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' @@ -125,27 +124,27 @@ export default Vue.extend({ if (!this.useProxy) { this.enableProxy() } - $.getJSON(this.proxyTestUrl1, (response) => { - console.log(response) - this.proxyIp = response.ip - this.proxyCountry = response.country_name - this.proxyRegion = response.region_name - this.proxyCity = response.city - this.dataAvailable = true - }).fail((xhr, textStatus, error) => { - console.log(xhr) - console.log(textStatus) - console.log(error) - this.showToast({ - message: this.$t('Settings.Proxy Settings["Error getting network information. Is your proxy configured properly?"]') + fetch(this.proxyTestUrl1) + .then((response) => response.json()) + .then((json) => { + console.log(json) + this.proxyIp = json.ip + this.proxyCountry = json.country_name + this.proxyRegion = json.region_name + this.proxyCity = json.city + this.dataAvailable = true + }).catch((error) => { + console.log(error) + this.showToast({ + message: this.$t('Settings.Proxy Settings["Error getting network information. Is your proxy configured properly?"]') + }) + this.dataAvailable = false + }).finally(() => { + if (!this.useProxy) { + this.disableProxy() + } + this.isLoading = false }) - this.dataAvailable = false - }).always(() => { - if (!this.useProxy) { - this.disableProxy() - } - this.isLoading = false - }) }, ...mapActions([ diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js index 15e67e7197bae..cd2269c0b23ba 100644 --- a/src/renderer/components/top-nav/top-nav.js +++ b/src/renderer/components/top-nav/top-nav.js @@ -3,7 +3,6 @@ import { mapActions } from 'vuex' import FtInput from '../ft-input/ft-input.vue' import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue' import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue' -import $ from 'jquery' import debounce from 'lodash.debounce' import ytSuggest from 'youtube-suggest' @@ -81,10 +80,10 @@ export default Vue.extend({ } }, mounted: function () { - const appWidth = $(window).width() + const appWidth = window.innerWidth if (appWidth <= 680) { - const searchContainer = $('.searchContainer').get(0) + const searchContainer = document.querySelector('.searchContainer') searchContainer.style.display = 'none' } @@ -97,7 +96,7 @@ export default Vue.extend({ window.addEventListener('resize', function (event) { const width = event.srcElement.innerWidth - const searchContainer = $('.searchContainer').get(0) + const searchContainer = document.querySelector('.searchContainer') if (width > 680) { searchContainer.style.display = '' @@ -110,14 +109,14 @@ export default Vue.extend({ }, methods: { goToSearch: async function (query) { - const appWidth = $(window).width() + const appWidth = window.innerWidth if (appWidth <= 680) { - const searchContainer = $('.searchContainer').get(0) + const searchContainer = document.querySelector('.searchContainer') searchContainer.blur() searchContainer.style.display = 'none' } else { - const searchInput = $('.searchInput input').get(0) + const searchInput = document.querySelector('.searchInput input') searchInput.blur() } @@ -261,7 +260,7 @@ export default Vue.extend({ }, toggleSearchContainer: function () { - const searchContainer = $('.searchContainer').get(0) + const searchContainer = document.querySelector('.searchContainer') if (searchContainer.style.display === 'none') { searchContainer.style.display = '' @@ -279,8 +278,8 @@ export default Vue.extend({ navigateHistory: function() { if (!this.isForwardOrBack) { this.historyIndex = window.history.length - $('#historyArrowBack').removeClass('fa-arrow-left') - $('#historyArrowForward').addClass('fa-arrow-right') + document.getElementById('historyArrowBack').classList.remove('fa-arrow-left') + document.getElementById('historyArrowForward').classList.add('fa-arrow-right') } else { this.isForwardOrBack = false } @@ -292,9 +291,9 @@ export default Vue.extend({ if (this.historyIndex > 1) { this.historyIndex-- - $('#historyArrowForward').removeClass('fa-arrow-right') + document.getElementById('historyArrowForward').classList.remove('fa-arrow-right') if (this.historyIndex === 1) { - $('#historyArrowBack').addClass('fa-arrow-left') + document.getElementById('historyArrowBack').classList.add('fa-arrow-left') } } }, @@ -305,10 +304,10 @@ export default Vue.extend({ if (this.historyIndex < window.history.length) { this.historyIndex++ - $('#historyArrowBack').removeClass('fa-arrow-left') + document.getElementById('historyArrowBack').classList.remove('fa-arrow-left') if (this.historyIndex === window.history.length) { - $('#historyArrowForward').addClass('fa-arrow-right') + document.getElementById('historyArrowForward').classList.add('fa-arrow-right') } } }, diff --git a/src/renderer/components/watch-video-description/watch-video-description.js b/src/renderer/components/watch-video-description/watch-video-description.js index f028e8c266996..4ebd6bafbf09f 100644 --- a/src/renderer/components/watch-video-description/watch-video-description.js +++ b/src/renderer/components/watch-video-description/watch-video-description.js @@ -2,7 +2,6 @@ import Vue from 'vue' import FtCard from '../ft-card/ft-card.vue' import FtTimestampCatcher from '../ft-timestamp-catcher/ft-timestamp-catcher.vue' import autolinker from 'autolinker' -import $ from 'jquery' export default Vue.extend({ name: 'WatchVideoDescription', @@ -37,7 +36,7 @@ export default Vue.extend({ } if (/^\s*$/.test(this.shownDescription)) { - $('.videoDescription')[0].style.display = 'none' + document.querySelector('.videoDescription').style.display = 'none' } }, methods: { diff --git a/src/renderer/components/watch-video-live-chat/watch-video-live-chat.css b/src/renderer/components/watch-video-live-chat/watch-video-live-chat.css index bc2923d308ecd..7f93683bb5095 100644 --- a/src/renderer/components/watch-video-live-chat/watch-video-live-chat.css +++ b/src/renderer/components/watch-video-live-chat/watch-video-live-chat.css @@ -130,6 +130,12 @@ overflow-y: auto; } +@media (prefers-reduced-motion: no-preference) { + .liveChatComments { + scroll-behavior: smooth; + } +} + .comment .superChatMessage { padding: 5px; } diff --git a/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js b/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js index ea72b7f501584..5cff779188793 100644 --- a/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js +++ b/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js @@ -5,7 +5,6 @@ import FtCard from '../ft-card/ft-card.vue' import FtButton from '../ft-button/ft-button.vue' import FtListVideo from '../ft-list-video/ft-list-video.vue' -import $ from 'jquery' import autolinker from 'autolinker' import { LiveChat } from '@freetube/youtube-chat' @@ -171,10 +170,10 @@ export default Vue.extend({ comment.messageHtml = autolinker.link(comment.messageHtml) - const liveChatComments = $('.liveChatComments') - const liveChatMessage = $('.liveChatMessage') + const liveChatComments = document.querySelector('.liveChatComments') + const liveChatMessage = document.querySelector('.liveChatMessage') - if (typeof (liveChatComments.get(0)) === 'undefined' && typeof (liveChatMessage.get(0)) === 'undefined') { + if (liveChatComments === null && liveChatMessage === null) { console.log("Can't find chat object. Stopping chat connection") this.liveChat.stop() return @@ -211,7 +210,7 @@ export default Vue.extend({ } if (this.stayAtBottom) { - liveChatComments.animate({ scrollTop: liveChatComments.prop('scrollHeight') }) + liveChatComments.scrollTo({ top: liveChatComments.scrollHeight }) } if (this.comments.length > 150 && this.stayAtBottom) { @@ -236,9 +235,8 @@ export default Vue.extend({ }, onScroll: function (event) { - const liveChatComments = $('.liveChatComments').get(0) + const liveChatComments = document.querySelector('.liveChatComments') if (event.wheelDelta >= 0 && this.stayAtBottom) { - $('.liveChatComments').data('animating', 0) this.stayAtBottom = false if (liveChatComments.scrollHeight > liveChatComments.clientHeight) { @@ -252,8 +250,8 @@ export default Vue.extend({ }, scrollToBottom: function () { - const liveChatComments = $('.liveChatComments') - liveChatComments.animate({ scrollTop: liveChatComments.prop('scrollHeight') }) + const liveChatComments = document.querySelector('.liveChatComments') + liveChatComments.scrollTo({ top: liveChatComments.scrollHeight }) this.stayAtBottom = true this.showScrollToBottom = false }, diff --git a/src/renderer/store/modules/invidious.js b/src/renderer/store/modules/invidious.js index e4f16a399c744..36d33a3d5aff6 100644 --- a/src/renderer/store/modules/invidious.js +++ b/src/renderer/store/modules/invidious.js @@ -1,4 +1,3 @@ -import $ from 'jquery' import fs from 'fs' const state = { @@ -25,11 +24,12 @@ const actions = { async fetchInvidiousInstances({ commit }, payload) { const requestUrl = 'https://api.invidious.io/instances.json' - let response let instances = [] try { - response = await $.getJSON(requestUrl) - instances = response.filter((instance) => { + const response = await fetch(requestUrl) + const json = await response.json() + + instances = json.filter((instance) => { if (instance[0].includes('.onion') || instance[0].includes('.i2p')) { return false } else { @@ -71,17 +71,18 @@ const actions = { invidiousAPICall({ state }, payload) { return new Promise((resolve, reject) => { - const requestUrl = state.currentInvidiousInstance + '/api/v1/' + payload.resource + '/' + payload.id + '?' + $.param(payload.params) - - $.getJSON(requestUrl, (response) => { - resolve(response) - }).fail((xhr, textStatus, error) => { - console.log(xhr) - console.log(textStatus) - console.log(requestUrl) - console.log(error) - reject(xhr) - }) + const requestUrl = state.currentInvidiousInstance + '/api/v1/' + payload.resource + '/' + payload.id + '?' + new URLSearchParams(payload.params).toString() + + fetch(requestUrl) + .then((response) => response.json()) + .then((json) => resolve(json)) + .catch((error) => { + console.group('Invidious API error') + console.error(requestUrl) + console.error(error) + console.groupEnd('Invidious API error') + reject(error) + }) }) }, diff --git a/src/renderer/store/modules/sponsorblock.js b/src/renderer/store/modules/sponsorblock.js index 241830789daad..ca3e51d3767cb 100644 --- a/src/renderer/store/modules/sponsorblock.js +++ b/src/renderer/store/modules/sponsorblock.js @@ -1,5 +1,3 @@ -import $ from 'jquery' - const state = {} const getters = {} @@ -18,18 +16,21 @@ const actions = { const requestUrl = `${rootState.settings.sponsorBlockUrl}/api/skipSegments/${videoIdHashPrefix}?categories=${JSON.stringify(categories)}` - $.getJSON(requestUrl, (response) => { - const segments = response - .filter((result) => result.videoID === videoId) - .flatMap((result) => result.segments) - resolve(segments) - }).fail((xhr, textStatus, error) => { - console.log(xhr) - console.log(textStatus) - console.log(requestUrl) - console.log(error) - reject(xhr) - }) + fetch(requestUrl) + .then((response) => response.json()) + .then((json) => { + const segments = json + .filter((result) => result.videoID === videoId) + .flatMap((result) => result.segments) + resolve(segments) + }) + .catch((error) => { + console.group('SponsorBlock error') + console.error(requestUrl) + console.error(error) + console.groupEnd('SponsorBlock error') + reject(error) + }) }) }) } diff --git a/src/renderer/views/Trending/Trending.js b/src/renderer/views/Trending/Trending.js index cf03f44f808c5..16718ef90a03e 100644 --- a/src/renderer/views/Trending/Trending.js +++ b/src/renderer/views/Trending/Trending.js @@ -6,7 +6,6 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue' import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' -import $ from 'jquery' import ytrend from 'yt-trending-scraper' export default Vue.extend({ @@ -71,10 +70,10 @@ export default Vue.extend({ ? this.tabInfoValues[(index > 0 ? index : this.tabInfoValues.length) - 1] : this.tabInfoValues[(index + 1) % this.tabInfoValues.length] - const tabNode = $(`#${tab}Tab`) + const tabNode = document.getElementById(`${tab}Tab`) event.target.setAttribute('tabindex', '-1') - tabNode.attr('tabindex', '0') - tabNode[0].focus() + tabNode.setAttribute('tabindex', '0') + tabNode.focus() } event.preventDefault() @@ -82,15 +81,15 @@ export default Vue.extend({ return } } - const currentTabNode = $('.trendingInfoTabs > .tab[aria-selected="true"]') - const newTabNode = $(`#${tab}Tab`) + const currentTabNode = document.querySelector('.trendingInfoTabs > .tab[aria-selected="true"]') + const newTabNode = document.getElementById(`${tab}Tab`) // switch selectability from currently focused tab to new tab - $('.trendingInfoTabs > .tab[tabindex="0"]').attr('tabindex', '-1') - newTabNode.attr('tabindex', '0') + document.querySelector('.trendingInfoTabs > .tab[tabindex="0"]').setAttribute('tabindex', '-1') + newTabNode.setAttribute('tabindex', '0') - currentTabNode.attr('aria-selected', 'false') - newTabNode.attr('aria-selected', 'true') + currentTabNode.setAttribute('aria-selected', 'false') + newTabNode.setAttribute('aria-selected', 'true') this.currentTab = tab if (this.trendingCache[this.currentTab] && this.trendingCache[this.currentTab].length > 0) { this.getTrendingInfoCache() @@ -134,7 +133,7 @@ export default Vue.extend({ const currentTab = this.currentTab this.$store.commit('setTrendingCache', { value: returnData, page: currentTab }) }).then(() => { - document.querySelector(`#${this.currentTab}Tab`).focus() + document.getElementById(`${this.currentTab}Tab`).focus() }).catch((err) => { console.log(err) const errorMessage = this.$t('Local API Error (Click to copy)') @@ -193,7 +192,7 @@ export default Vue.extend({ const currentTab = this.currentTab this.$store.commit('setTrendingCache', { value: returnData, page: currentTab }) }).then(() => { - document.querySelector(`#${this.currentTab}Tab`).focus() + document.getElementById(`${this.currentTab}Tab`).focus() }).catch((err) => { console.log(err) const errorMessage = this.$t('Invidious API Error (Click to copy)') diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js index 69ae3189eab33..c441c9bb4f272 100644 --- a/src/renderer/views/Watch/Watch.js +++ b/src/renderer/views/Watch/Watch.js @@ -1,6 +1,5 @@ import Vue from 'vue' import { mapActions } from 'vuex' -import $ from 'jquery' import fs from 'fs' import ytDashGen from 'yt-dash-manifest-generator' import FtLoader from '../../components/ft-loader/ft-loader.vue' @@ -143,7 +142,7 @@ export default Vue.extend({ hideVideoDescription: function () { return this.$store.getters.getHideVideoDescription }, - showFamilyFriendlyOnly: function() { + showFamilyFriendlyOnly: function () { return this.$store.getters.getShowFamilyFriendlyOnly }, @@ -1002,7 +1001,7 @@ export default Vue.extend({ if (this.$route.fullPath.includes('/watch')) { const routeId = this.$route.params.id if (routeId === videoId) { - const activePlayer = $('.ftVideoPlayer video').get(0) + const activePlayer = document.querySelector('.ftVideoPlayer video') activePlayer.currentTime = watchTime } } @@ -1220,35 +1219,36 @@ export default Vue.extend({ const url = new URL(caption.baseUrl) url.searchParams.set('fmt', 'vtt') - $.get(url.toString(), response => { - // The character '#' needs to be percent-encoded in a (data) URI - // because it signals an identifier, which means anything after it - // is automatically removed when the URI is used as a source - let vtt = response.replace(/#/g, '%23') - - // A lot of videos have messed up caption positions that need to be removed - // This can be either because this format isn't really used by YouTube - // or because it's expected for the player to be able to somehow - // wrap the captions so that they won't step outside its boundaries - // - // Auto-generated captions are also all aligned to the start - // so those instances must also be removed - // In addition, all aligns seem to be fixed to "start" when they do pop up in normal captions - // If it's prominent enough that people start to notice, it can be removed then - if (caption.kind === 'asr') { - vtt = vtt.replace(/ align:start| position:\d{1,3}%/g, '') - } else { - vtt = vtt.replace(/ position:\d{1,3}%/g, '') - } + fetch(url.toString()) + .then((response) => response.text()) + .then((text) => { + // The character '#' needs to be percent-encoded in a (data) URI + // because it signals an identifier, which means anything after it + // is automatically removed when the URI is used as a source + let vtt = text.replace(/#/g, '%23') + + // A lot of videos have messed up caption positions that need to be removed + // This can be either because this format isn't really used by YouTube + // or because it's expected for the player to be able to somehow + // wrap the captions so that they won't step outside its boundaries + // + // Auto-generated captions are also all aligned to the start + // so those instances must also be removed + // In addition, all aligns seem to be fixed to "start" when they do pop up in normal captions + // If it's prominent enough that people start to notice, it can be removed then + if (caption.kind === 'asr') { + vtt = vtt.replace(/ align:start| position:\d{1,3}%/g, '') + } else { + vtt = vtt.replace(/ position:\d{1,3}%/g, '') + } - caption.baseUrl = `data:${caption.type};${caption.charset},${vtt}` - resolve(caption) - }).fail((xhr, textStatus, error) => { - console.log(xhr) - console.log(textStatus) - console.log(error) - reject(error) - }) + caption.baseUrl = `data:${caption.type};${caption.charset},${vtt}` + resolve(caption) + }).catch((error) => { + console.log(url.toString()) + console.log(error) + reject(error) + }) })) }, diff --git a/yarn.lock b/yarn.lock index 42ae3b71bd3f1..0077a11af1b95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4611,11 +4611,6 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jquery@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" - integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"