Make copyToClipboard and openExternalLink helpers (#2722)

This commit is contained in:
absidue 2022-10-18 10:15:28 +02:00 committed by GitHub
parent 26e40c51e7
commit f970936d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 127 additions and 148 deletions

View File

@ -12,7 +12,7 @@ import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
import { marked } from 'marked' import { marked } from 'marked'
import { IpcChannels } from '../constants' import { IpcChannels } from '../constants'
import packageDetails from '../../package.json' import packageDetails from '../../package.json'
import { showToast } from './helpers/utils' import { openExternalLink, showToast } from './helpers/utils'
let ipcRenderer = null let ipcRenderer = null
@ -272,7 +272,7 @@ export default Vue.extend({
handleNewBlogBannerClick: function (response) { handleNewBlogBannerClick: function (response) {
if (response) { if (response) {
this.openExternalLink(this.latestBlogUrl) openExternalLink(this.latestBlogUrl)
} }
this.showBlogBanner = false this.showBlogBanner = false
@ -280,7 +280,7 @@ export default Vue.extend({
openDownloadsPage: function () { openDownloadsPage: function () {
const url = 'https://freetubeapp.io#download' const url = 'https://freetubeapp.io#download'
this.openExternalLink(url) openExternalLink(url)
this.showReleaseNotes = false this.showReleaseNotes = false
this.showUpdatesBanner = false this.showUpdatesBanner = false
}, },
@ -364,7 +364,7 @@ export default Vue.extend({
this.showExternalLinkOpeningPrompt = true this.showExternalLinkOpeningPrompt = true
} else { } else {
// Open links externally // Open links externally
this.openExternalLink(el.href) openExternalLink(el.href)
} }
}, },
@ -515,7 +515,7 @@ export default Vue.extend({
// if `lastExternalLinkToBeOpened` is empty // if `lastExternalLinkToBeOpened` is empty
// Open links externally // Open links externally
this.openExternalLink(this.lastExternalLinkToBeOpened) openExternalLink(this.lastExternalLinkToBeOpened)
} }
}, },
@ -530,7 +530,6 @@ export default Vue.extend({
]), ]),
...mapActions([ ...mapActions([
'openExternalLink',
'grabUserSettings', 'grabUserSettings',
'grabAllProfiles', 'grabAllProfiles',
'grabHistory', 'grabHistory',

View File

@ -11,7 +11,7 @@ import { MAIN_PROFILE_ID } from '../../../constants'
import fs from 'fs' import fs from 'fs'
import { opmlToJSON } from 'opml-to-json' import { opmlToJSON } from 'opml-to-json'
import ytch from 'yt-channel-info' import ytch from 'yt-channel-info'
import { calculateColorLuminance, getRandomColor, showToast } from '../../helpers/utils' import { calculateColorLuminance, copyToClipboard, getRandomColor, showToast } from '../../helpers/utils'
// FIXME: Missing web logic branching // FIXME: Missing web logic branching
@ -1042,7 +1042,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => { showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => {
this.copyToClipboard({ content: err.responseJSON.error }) copyToClipboard(err.responseJSON.error)
}) })
if (this.backendFallback && this.backendPreference === 'invidious') { if (this.backendFallback && this.backendPreference === 'invidious') {
@ -1063,7 +1063,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendFallback && this.backendPreference === 'local') { if (this.backendFallback && this.backendPreference === 'local') {
@ -1145,8 +1145,7 @@ export default Vue.extend({
'showSaveDialog', 'showSaveDialog',
'getUserDataPath', 'getUserDataPath',
'addPlaylist', 'addPlaylist',
'addVideo', 'addVideo'
'copyToClipboard'
]), ]),
...mapMutations([ ...mapMutations([

View File

@ -2,7 +2,7 @@ import Vue from 'vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue' import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { showToast } from '../../helpers/utils' import { copyToClipboard, openExternalLink, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'FtListVideo', name: 'FtListVideo',
@ -300,34 +300,34 @@ export default Vue.extend({
} }
break break
case 'copyYoutube': case 'copyYoutube':
this.copyToClipboard({ content: this.youtubeShareUrl, messageOnSuccess: this.$t('Share.YouTube URL copied to clipboard') }) copyToClipboard(this.youtubeShareUrl, { messageOnSuccess: this.$t('Share.YouTube URL copied to clipboard') })
break break
case 'openYoutube': case 'openYoutube':
this.openExternalLink(this.youtubeUrl) openExternalLink(this.youtubeUrl)
break break
case 'copyYoutubeEmbed': case 'copyYoutubeEmbed':
this.copyToClipboard({ content: this.youtubeEmbedUrl, messageOnSuccess: this.$t('Share.YouTube Embed URL copied to clipboard') }) copyToClipboard(this.youtubeEmbedUrl, { messageOnSuccess: this.$t('Share.YouTube Embed URL copied to clipboard') })
break break
case 'openYoutubeEmbed': case 'openYoutubeEmbed':
this.openExternalLink(this.youtubeEmbedUrl) openExternalLink(this.youtubeEmbedUrl)
break break
case 'copyInvidious': case 'copyInvidious':
this.copyToClipboard({ content: this.invidiousUrl, messageOnSuccess: this.$t('Share.Invidious URL copied to clipboard') }) copyToClipboard(this.invidiousUrl, { messageOnSuccess: this.$t('Share.Invidious URL copied to clipboard') })
break break
case 'openInvidious': case 'openInvidious':
this.openExternalLink(this.invidiousUrl) openExternalLink(this.invidiousUrl)
break break
case 'copyYoutubeChannel': case 'copyYoutubeChannel':
this.copyToClipboard({ content: this.youtubeChannelUrl, messageOnSuccess: this.$t('Share.YouTube Channel URL copied to clipboard') }) copyToClipboard(this.youtubeChannelUrl, { messageOnSuccess: this.$t('Share.YouTube Channel URL copied to clipboard') })
break break
case 'openYoutubeChannel': case 'openYoutubeChannel':
this.openExternalLink(this.youtubeChannelUrl) openExternalLink(this.youtubeChannelUrl)
break break
case 'copyInvidiousChannel': case 'copyInvidiousChannel':
this.copyToClipboard({ content: this.invidiousChannelUrl, messageOnSuccess: this.$t('Share.Invidious Channel URL copied to clipboard') }) copyToClipboard(this.invidiousChannelUrl, { messageOnSuccess: this.$t('Share.Invidious Channel URL copied to clipboard') })
break break
case 'openInvidiousChannel': case 'openInvidiousChannel':
this.openExternalLink(this.invidiousChannelUrl) openExternalLink(this.invidiousChannelUrl)
break break
} }
}, },
@ -508,9 +508,7 @@ export default Vue.extend({
'updateHistory', 'updateHistory',
'removeFromHistory', 'removeFromHistory',
'addVideo', 'addVideo',
'removeVideo', 'removeVideo'
'openExternalLink',
'copyToClipboard'
]) ])
} }
}) })

View File

@ -4,7 +4,7 @@ import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue' import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import FtButton from '../ft-button/ft-button.vue' import FtButton from '../ft-button/ft-button.vue'
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
import { mapActions } from 'vuex' import { copyToClipboard, openExternalLink } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'FtShareButton', name: 'FtShareButton',
@ -78,42 +78,42 @@ export default Vue.extend({
methods: { methods: {
openInvidious() { openInvidious() {
this.openExternalLink(this.getFinalUrl(this.invidiousURL)) openExternalLink(this.getFinalUrl(this.invidiousURL))
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
copyInvidious() { copyInvidious() {
this.copyToClipboard({ content: this.getFinalUrl(this.invidiousURL), messageOnSuccess: this.$t('Share.Invidious URL copied to clipboard') }) copyToClipboard(this.getFinalUrl(this.invidiousURL), { messageOnSuccess: this.$t('Share.Invidious URL copied to clipboard') })
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
openYoutube() { openYoutube() {
this.openExternalLink(this.getFinalUrl(this.youtubeURL)) openExternalLink(this.getFinalUrl(this.youtubeURL))
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
copyYoutube() { copyYoutube() {
this.copyToClipboard({ content: this.getFinalUrl(this.youtubeShareURL), messageOnSuccess: this.$t('Share.YouTube URL copied to clipboard') }) copyToClipboard(this.getFinalUrl(this.youtubeShareURL), { messageOnSuccess: this.$t('Share.YouTube URL copied to clipboard') })
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
openYoutubeEmbed() { openYoutubeEmbed() {
this.openExternalLink(this.getFinalUrl(this.youtubeEmbedURL)) openExternalLink(this.getFinalUrl(this.youtubeEmbedURL))
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
copyYoutubeEmbed() { copyYoutubeEmbed() {
this.copyToClipboard({ content: this.getFinalUrl(this.youtubeEmbedURL), messageOnSuccess: this.$t('Share.YouTube Embed URL copied to clipboard') }) copyToClipboard(this.getFinalUrl(this.youtubeEmbedURL), { messageOnSuccess: this.$t('Share.YouTube Embed URL copied to clipboard') })
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
openInvidiousEmbed() { openInvidiousEmbed() {
this.openExternalLink(this.getFinalUrl(this.invidiousEmbedURL)) openExternalLink(this.getFinalUrl(this.invidiousEmbedURL))
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
copyInvidiousEmbed() { copyInvidiousEmbed() {
this.copyToClipboard({ content: this.getFinalUrl(this.invidiousEmbedURL), messageOnSuccess: this.$t('Share.Invidious Embed URL copied to clipboard') }) copyToClipboard(this.getFinalUrl(this.invidiousEmbedURL), { messageOnSuccess: this.$t('Share.Invidious Embed URL copied to clipboard') })
this.$refs.iconButton.hideDropdown() this.$refs.iconButton.hideDropdown()
}, },
@ -126,11 +126,6 @@ export default Vue.extend({
return this.includeTimestamp ? `${url}?t=${this.getTimestamp()}` : url return this.includeTimestamp ? `${url}?t=${this.getTimestamp()}` : url
} }
return this.includeTimestamp ? `${url}&t=${this.getTimestamp()}` : url return this.includeTimestamp ? `${url}&t=${this.getTimestamp()}` : url
}, }
...mapActions([
'openExternalLink',
'copyToClipboard'
])
} }
}) })

View File

@ -128,7 +128,6 @@ export default Vue.extend({
}, },
...mapActions([ ...mapActions([
'openExternalLink',
'updateSponsorBlockSponsor', 'updateSponsorBlockSponsor',
'updateSponsorBlockSelfPromo', 'updateSponsorBlockSelfPromo',
'updateSponsorBlockInteraction', 'updateSponsorBlockInteraction',

View File

@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import { mapActions } from 'vuex'
import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue' import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { copyToClipboard, openExternalLink } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'PlaylistInfo', name: 'PlaylistInfo',
@ -110,16 +110,16 @@ export default Vue.extend({
switch (method) { switch (method) {
case 'copyYoutube': case 'copyYoutube':
this.copyToClipboard({ content: youtubeUrl, messageOnSuccess: this.$t('Share.YouTube URL copied to clipboard') }) copyToClipboard(youtubeUrl, { messageOnSuccess: this.$t('Share.YouTube URL copied to clipboard') })
break break
case 'openYoutube': case 'openYoutube':
this.openExternalLink(youtubeUrl) openExternalLink(youtubeUrl)
break break
case 'copyInvidious': case 'copyInvidious':
this.copyToClipboard({ content: invidiousUrl, messageOnSuccess: this.$t('Share.Invidious URL copied to clipboard') }) copyToClipboard(invidiousUrl, { messageOnSuccess: this.$t('Share.Invidious URL copied to clipboard') })
break break
case 'openInvidious': case 'openInvidious':
this.openExternalLink(invidiousUrl) openExternalLink(invidiousUrl)
break break
} }
}, },
@ -139,11 +139,6 @@ export default Vue.extend({
goToChannel: function () { goToChannel: function () {
this.$router.push({ path: `/channel/${this.channelId}` }) this.$router.push({ path: `/channel/${this.channelId}` })
}, }
...mapActions([
'openExternalLink',
'copyToClipboard'
])
} }
}) })

View File

@ -6,7 +6,7 @@ import FtSelect from '../../components/ft-select/ft-select.vue'
import FtTimestampCatcher from '../../components/ft-timestamp-catcher/ft-timestamp-catcher.vue' import FtTimestampCatcher from '../../components/ft-timestamp-catcher/ft-timestamp-catcher.vue'
import autolinker from 'autolinker' import autolinker from 'autolinker'
import ytcm from '@freetube/yt-comment-scraper' import ytcm from '@freetube/yt-comment-scraper'
import { showToast } from '../../helpers/utils' import { copyToClipboard, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'WatchVideoComments', name: 'WatchVideoComments',
@ -166,7 +166,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendFallback && this.backendPreference === 'local') { if (this.backendFallback && this.backendPreference === 'local') {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -186,7 +186,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendFallback && this.backendPreference === 'local') { if (this.backendFallback && this.backendPreference === 'local') {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -293,7 +293,7 @@ export default Vue.extend({
console.error(xhr) console.error(xhr)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${xhr.responseText}`, 10000, () => { showToast(`${errorMessage}: ${xhr.responseText}`, 10000, () => {
this.copyToClipboard({ content: xhr.responseText }) copyToClipboard(xhr.responseText)
}) })
if (this.backendFallback && this.backendPreference === 'invidious') { if (this.backendFallback && this.backendPreference === 'invidious') {
showToast(this.$t('Falling back to local API')) showToast(this.$t('Falling back to local API'))
@ -341,7 +341,7 @@ export default Vue.extend({
console.error(xhr) console.error(xhr)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${xhr.responseText}`, 10000, () => { showToast(`${errorMessage}: ${xhr.responseText}`, 10000, () => {
this.copyToClipboard({ content: xhr.responseText }) copyToClipboard(xhr.responseText)
}) })
this.isLoading = false this.isLoading = false
}) })
@ -353,8 +353,7 @@ export default Vue.extend({
...mapActions([ ...mapActions([
'toLocalePublicationString', 'toLocalePublicationString',
'invidiousAPICall', 'invidiousAPICall'
'copyToClipboard'
]) ])
} }
}) })

View File

@ -7,7 +7,7 @@ import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import FtShareButton from '../ft-share-button/ft-share-button.vue' import FtShareButton from '../ft-share-button/ft-share-button.vue'
import { MAIN_PROFILE_ID } from '../../../constants' import { MAIN_PROFILE_ID } from '../../../constants'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { showToast } from '../../helpers/utils' import { openExternalLink, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'WatchVideoInfo', name: 'WatchVideoInfo',
@ -430,7 +430,7 @@ export default Vue.extend({
const extension = this.grabExtensionFromUrl(linkName) const extension = this.grabExtensionFromUrl(linkName)
if (this.downloadBehavior === 'open') { if (this.downloadBehavior === 'open') {
this.openExternalLink(url) openExternalLink(url)
} else { } else {
this.downloadMedia({ this.downloadMedia({
url: url, url: url,
@ -491,7 +491,6 @@ export default Vue.extend({
'updateProfile', 'updateProfile',
'addVideo', 'addVideo',
'removeVideo', 'removeVideo',
'openExternalLink',
'downloadMedia' 'downloadMedia'
]) ])
} }

View File

@ -4,7 +4,7 @@ import FtLoader from '../ft-loader/ft-loader.vue'
import FtCard from '../ft-card/ft-card.vue' import FtCard from '../ft-card/ft-card.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtListVideo from '../ft-list-video/ft-list-video.vue' import FtListVideo from '../ft-list-video/ft-list-video.vue'
import { showToast } from '../../helpers/utils' import { copyToClipboard, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'WatchVideoPlaylist', name: 'WatchVideoPlaylist',
@ -284,7 +284,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -316,7 +316,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'invidious' && this.backendFallback) { if (this.backendPreference === 'invidious' && this.backendFallback) {
showToast(this.$t('Falling back to Local API')) showToast(this.$t('Falling back to Local API'))
@ -350,8 +350,7 @@ export default Vue.extend({
...mapActions([ ...mapActions([
'ytGetPlaylistInfo', 'ytGetPlaylistInfo',
'invidiousGetPlaylistInfo', 'invidiousGetPlaylistInfo'
'copyToClipboard'
]) ])
} }
}) })

View File

@ -1,4 +1,6 @@
import { IpcChannels } from '../../constants'
import FtToastEvents from '../components/ft-toast/ft-toast-events' import FtToastEvents from '../components/ft-toast/ft-toast-events'
import i18n from '../i18n/index'
export const colors = [ export const colors = [
{ name: 'Red', value: '#d50000' }, { name: 'Red', value: '#d50000' },
@ -157,3 +159,40 @@ export function buildVTTFileLocally(storyboard) {
export function showToast(message, time = null, action = null) { export function showToast(message, time = null, action = null) {
FtToastEvents.$emit('toast-open', message, time, action) FtToastEvents.$emit('toast-open', message, time, action)
} }
/**
* This writes to the clipboard. If an error occurs during the copy,
* a toast with the error is shown. If the copy is successful and
* there is a success message, a toast with that message is shown.
* @param {string} content the content to be copied to the clipboard
* @param {string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
* @param {string} messageOnError the message to be displayed as a toast when the copy fails (optional)
*/
export async function copyToClipboard(content, { messageOnSuccess = null, messageOnError = null }) {
if (navigator.clipboard !== undefined && window.isSecureContext) {
try {
await navigator.clipboard.writeText(content)
if (messageOnSuccess !== null) {
showToast(messageOnSuccess)
}
} catch (error) {
console.error(`Failed to copy ${content} to clipboard`, error)
if (messageOnError !== null) {
showToast(`${messageOnError}: ${error}`, 5000)
} else {
showToast(`${i18n.t('Clipboard.Copy failed')}: ${error}`, 5000)
}
}
} else {
showToast(i18n.t('Clipboard.Cannot access clipboard without a secure connection'), 5000)
}
}
export function openExternalLink(url) {
if (process.env.IS_ELECTRON) {
const ipcRenderer = require('electron').ipcRenderer
ipcRenderer.send(IpcChannels.OPEN_EXTERNAL_LINK, url)
} else {
window.open(url, '_blank')
}
}

View File

@ -4,7 +4,7 @@ import path from 'path'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { IpcChannels } from '../../../constants' import { IpcChannels } from '../../../constants'
import { showToast } from '../../helpers/utils' import { openExternalLink, showToast } from '../../helpers/utils'
const state = { const state = {
isSideNavOpen: false, isSideNavOpen: false,
@ -117,15 +117,6 @@ async function invokeIRC(context, IRCtype, webCbk, payload = null) {
} }
const actions = { const actions = {
openExternalLink (_, url) {
if (process.env.IS_ELECTRON) {
const ipcRenderer = require('electron').ipcRenderer
ipcRenderer.send(IpcChannels.OPEN_EXTERNAL_LINK, url)
} else {
window.open(url, '_blank')
}
},
replaceFilenameForbiddenChars(_, filenameOriginal) { replaceFilenameForbiddenChars(_, filenameOriginal) {
let filenameNew = filenameOriginal let filenameNew = filenameOriginal
let forbiddenChars = {} let forbiddenChars = {}
@ -159,44 +150,16 @@ const actions = {
return filenameNew return filenameNew
}, },
/**
* This writes to the clipboard. If an error occurs during the copy,
* a toast with the error is shown. If the copy is successful and
* there is a success message, a toast with that message is shown.
* @param {string} content the content to be copied to the clipboard
* @param {string} messageOnSuccess the message to be displayed as a toast when the copy succeeds (optional)
* @param {string} messageOnError the message to be displayed as a toast when the copy fails (optional)
*/
async copyToClipboard ({ dispatch }, { content, messageOnSuccess, messageOnError }) {
if (navigator.clipboard !== undefined && window.isSecureContext) {
try {
await navigator.clipboard.writeText(content)
if (messageOnSuccess !== undefined) {
showToast(messageOnSuccess)
}
} catch (error) {
console.error(`Failed to copy ${content} to clipboard`, error)
if (messageOnError !== undefined) {
showToast(`${messageOnError}: ${error}`, 5000)
} else {
showToast(`${i18n.t('Clipboard.Copy failed')}: ${error}`, 5000)
}
}
} else {
showToast(i18n.t('Clipboard.Cannot access clipboard without a secure connection'), 5000)
}
},
async downloadMedia({ rootState, dispatch }, { url, title, extension, fallingBackPath }) { async downloadMedia({ rootState, dispatch }, { url, title, extension, fallingBackPath }) {
if (!process.env.IS_ELECTRON) {
openExternalLink(url)
return
}
const fileName = `${await dispatch('replaceFilenameForbiddenChars', title)}.${extension}` const fileName = `${await dispatch('replaceFilenameForbiddenChars', title)}.${extension}`
const errorMessage = i18n.t('Downloading failed', { videoTitle: title }) const errorMessage = i18n.t('Downloading failed', { videoTitle: title })
let folderPath = rootState.settings.downloadFolderPath let folderPath = rootState.settings.downloadFolderPath
if (!process.env.IS_ELECTRON) {
dispatch('openExternalLink', url)
return
}
if (folderPath === '') { if (folderPath === '') {
const options = { const options = {
defaultPath: fileName, defaultPath: fileName,

View File

@ -14,7 +14,7 @@ import ytch from 'yt-channel-info'
import autolinker from 'autolinker' import autolinker from 'autolinker'
import { MAIN_PROFILE_ID } from '../../../constants' import { MAIN_PROFILE_ID } from '../../../constants'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { showToast } from '../../helpers/utils' import { copyToClipboard, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'Search', name: 'Search',
@ -308,7 +308,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -334,7 +334,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -353,7 +353,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
}) })
}, },
@ -403,7 +403,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => { showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => {
this.copyToClipboard({ content: err.responseJSON.error }) copyToClipboard(err.responseJSON.error)
}) })
this.isLoading = false this.isLoading = false
}) })
@ -427,7 +427,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
}) })
}, },
@ -449,7 +449,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -468,7 +468,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
}) })
}, },
@ -490,7 +490,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => { showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => {
this.copyToClipboard({ content: err.responseJSON.error }) copyToClipboard(err.responseJSON.error)
}) })
if (this.backendPreference === 'invidious' && this.backendFallback) { if (this.backendPreference === 'invidious' && this.backendFallback) {
showToast(this.$t('Falling back to Local API')) showToast(this.$t('Falling back to Local API'))
@ -527,7 +527,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => { showToast(`${errorMessage}: ${err.responseJSON.error}`, 10000, () => {
this.copyToClipboard({ content: err.responseJSON.error }) copyToClipboard(err.responseJSON.error)
}) })
if (this.backendPreference === 'invidious' && this.backendFallback) { if (this.backendPreference === 'invidious' && this.backendFallback) {
showToast(this.$t('Falling back to Local API')) showToast(this.$t('Falling back to Local API'))
@ -680,7 +680,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -698,7 +698,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
}) })
} }
@ -722,7 +722,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'invidious' && this.backendFallback) { if (this.backendPreference === 'invidious' && this.backendFallback) {
showToast(this.$t('Falling back to Local API')) showToast(this.$t('Falling back to Local API'))
@ -737,8 +737,7 @@ export default Vue.extend({
'updateProfile', 'updateProfile',
'invidiousGetChannelInfo', 'invidiousGetChannelInfo',
'invidiousAPICall', 'invidiousAPICall',
'updateSubscriptionDetails', 'updateSubscriptionDetails'
'copyToClipboard'
]) ])
} }
}) })

View File

@ -5,7 +5,7 @@ import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtCard from '../../components/ft-card/ft-card.vue' import FtCard from '../../components/ft-card/ft-card.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue' import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import { calculateLengthInSeconds } from '@freetube/yt-trending-scraper/src/HtmlParser' import { calculateLengthInSeconds } from '@freetube/yt-trending-scraper/src/HtmlParser'
import { showToast } from '../../helpers/utils' import { copyToClipboard, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'Search', name: 'Search',
@ -201,7 +201,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -262,7 +262,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'invidious' && this.backendFallback) { if (this.backendPreference === 'invidious' && this.backendFallback) {
showToast(this.$t('Falling back to Local API')) showToast(this.$t('Falling back to Local API'))
@ -316,8 +316,7 @@ export default Vue.extend({
...mapActions([ ...mapActions([
'ytSearch', 'ytSearch',
'invidiousAPICall', 'invidiousAPICall'
'copyToClipboard'
]) ])
} }
}) })

View File

@ -10,7 +10,7 @@ import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubbl
import ytch from 'yt-channel-info' import ytch from 'yt-channel-info'
import { MAIN_PROFILE_ID } from '../../../constants' import { MAIN_PROFILE_ID } from '../../../constants'
import { calculatePublishedDate, showToast } from '../../helpers/utils' import { calculatePublishedDate, copyToClipboard, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'Subscriptions', name: 'Subscriptions',
@ -274,7 +274,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
switch (failedAttempts) { switch (failedAttempts) {
case 0: case 0:
@ -314,7 +314,7 @@ export default Vue.extend({
console.error(error) console.error(error)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => { showToast(`${errorMessage}: ${error}`, 10000, () => {
this.copyToClipboard({ content: error }) copyToClipboard(error)
}) })
switch (failedAttempts) { switch (failedAttempts) {
case 0: case 0:
@ -351,7 +351,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => { showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
this.copyToClipboard({ content: err.responseText }) copyToClipboard(err.responseText)
}) })
switch (failedAttempts) { switch (failedAttempts) {
case 0: case 0:
@ -391,7 +391,7 @@ export default Vue.extend({
console.error(error) console.error(error)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${error}`, 10000, () => { showToast(`${errorMessage}: ${error}`, 10000, () => {
this.copyToClipboard({ content: error }) copyToClipboard(error)
}) })
switch (failedAttempts) { switch (failedAttempts) {
case 0: case 0:
@ -467,8 +467,7 @@ export default Vue.extend({
'invidiousAPICall', 'invidiousAPICall',
'updateShowProgressBar', 'updateShowProgressBar',
'updateProfileSubscriptions', 'updateProfileSubscriptions',
'updateAllSubscriptionsList', 'updateAllSubscriptionsList'
'copyToClipboard'
]), ]),
...mapMutations([ ...mapMutations([

View File

@ -7,7 +7,7 @@ import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import { scrapeTrendingPage } from '@freetube/yt-trending-scraper' import { scrapeTrendingPage } from '@freetube/yt-trending-scraper'
import { showToast } from '../../helpers/utils' import { copyToClipboard, showToast } from '../../helpers/utils'
export default Vue.extend({ export default Vue.extend({
name: 'Trending', name: 'Trending',
@ -101,7 +101,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
if (this.backendPreference === 'local' && this.backendFallback) { if (this.backendPreference === 'local' && this.backendFallback) {
showToast(this.$t('Falling back to Invidious API')) showToast(this.$t('Falling back to Invidious API'))
@ -157,7 +157,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => { showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
this.copyToClipboard({ content: err.responseText }) copyToClipboard(err.responseText)
}) })
if (process.env.IS_ELECTRON && (this.backendPreference === 'invidious' && this.backendFallback)) { if (process.env.IS_ELECTRON && (this.backendPreference === 'invidious' && this.backendFallback)) {
@ -185,8 +185,7 @@ export default Vue.extend({
}, },
...mapActions([ ...mapActions([
'invidiousAPICall', 'invidiousAPICall'
'copyToClipboard'
]) ])
} }
}) })

View File

@ -15,7 +15,7 @@ import WatchVideoPlaylist from '../../components/watch-video-playlist/watch-vide
import WatchVideoRecommendations from '../../components/watch-video-recommendations/watch-video-recommendations.vue' import WatchVideoRecommendations from '../../components/watch-video-recommendations/watch-video-recommendations.vue'
import FtAgeRestricted from '../../components/ft-age-restricted/ft-age-restricted.vue' import FtAgeRestricted from '../../components/ft-age-restricted/ft-age-restricted.vue'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { buildVTTFileLocally, showToast } from '../../helpers/utils' import { buildVTTFileLocally, copyToClipboard, showToast } from '../../helpers/utils'
const isDev = process.env.NODE_ENV === 'development' const isDev = process.env.NODE_ENV === 'development'
@ -628,7 +628,7 @@ export default Vue.extend({
.catch(err => { .catch(err => {
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
console.error(err) console.error(err)
if (this.backendPreference === 'local' && this.backendFallback && !err.toString().includes('private')) { if (this.backendPreference === 'local' && this.backendFallback && !err.toString().includes('private')) {
@ -848,7 +848,7 @@ export default Vue.extend({
console.error(err) console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err.responseText}`, 10000, () => { showToast(`${errorMessage}: ${err.responseText}`, 10000, () => {
this.copyToClipboard({ content: err.responseText }) copyToClipboard(err.responseText)
}) })
console.error(err) console.error(err)
if (this.backendPreference === 'invidious' && this.backendFallback) { if (this.backendPreference === 'invidious' && this.backendFallback) {
@ -1035,7 +1035,7 @@ export default Vue.extend({
.catch(err => { .catch(err => {
const errorMessage = this.$t('Local API Error (Click to copy)') const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => { showToast(`${errorMessage}: ${err}`, 10000, () => {
this.copyToClipboard({ content: err }) copyToClipboard(err)
}) })
console.error(err) console.error(err)
if (!process.env.IS_ELECTRON || (this.backendPreference === 'local' && this.backendFallback)) { if (!process.env.IS_ELECTRON || (this.backendPreference === 'local' && this.backendFallback)) {
@ -1512,8 +1512,7 @@ export default Vue.extend({
'getUserDataPath', 'getUserDataPath',
'ytGetVideoInformation', 'ytGetVideoInformation',
'invidiousGetVideoInformation', 'invidiousGetVideoInformation',
'updateSubscriptionDetails', 'updateSubscriptionDetails'
'copyToClipboard'
]) ])
} }
}) })