mirror of https://github.com/FreeTubeApp/FreeTube
Compare commits
12 Commits
2643ba6fd8
...
1e25f0991d
Author | SHA1 | Date |
---|---|---|
Jason | 1e25f0991d | |
NEXI | 43a7fbdcb1 | |
absidue | 88bed9eaf6 | |
Sergio Marques | 7f3925d0c5 | |
Jason Henriquez | 748fb35c3d | |
Jason Henriquez | 96bb71e837 | |
Jason Henriquez | b6d961dd5e | |
Jason Henriquez | 7337fe1d1b | |
Jason Henriquez | fa1cd3632e | |
Jason Henriquez | 5089cb3278 | |
Jason Henriquez | d32ebdc49c | |
Jason Henriquez | 62ae16d92c |
|
@ -10,6 +10,7 @@ import FtSelect from '../../components/ft-select/ft-select.vue'
|
|||
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
|
||||
import {
|
||||
showToast,
|
||||
ctrlFHandler,
|
||||
getIconForSortPreference
|
||||
} from '../../helpers/utils'
|
||||
|
||||
|
@ -199,12 +200,13 @@ export default defineComponent({
|
|||
},
|
||||
mounted: function () {
|
||||
this.lastActiveElement = document.activeElement
|
||||
|
||||
this.updateQueryDebounce = debounce(this.updateQuery, 500)
|
||||
// User might want to search first if they have many playlists
|
||||
this.$refs.searchBar.focus()
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
this.lastActiveElement?.focus()
|
||||
},
|
||||
methods: {
|
||||
|
@ -270,6 +272,10 @@ export default defineComponent({
|
|||
this.query = query
|
||||
},
|
||||
|
||||
keyboardShortcutHandler: function (event) {
|
||||
ctrlFHandler(event, this.$refs.searchBar)
|
||||
},
|
||||
|
||||
getIconForSortPreference: (s) => getIconForSortPreference(s),
|
||||
|
||||
...mapActions([
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { sanitizeForHtmlId } from '../../helpers/accessibility'
|
||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||
import { getFirstCharacter } from '../../helpers/strings'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtProfileBubble',
|
||||
|
@ -24,6 +25,9 @@ export default defineComponent({
|
|||
},
|
||||
emits: ['click'],
|
||||
computed: {
|
||||
locale: function () {
|
||||
return this.$i18n.locale.replace('_', '-')
|
||||
},
|
||||
isMainProfile: function () {
|
||||
return this.profileId === MAIN_PROFILE_ID
|
||||
},
|
||||
|
@ -31,7 +35,9 @@ export default defineComponent({
|
|||
return 'profileBubble' + sanitizeForHtmlId(this.profileId)
|
||||
},
|
||||
profileInitial: function () {
|
||||
return this?.profileName?.length > 0 ? Array.from(this.translatedProfileName)[0].toUpperCase() : ''
|
||||
return this.profileName
|
||||
? getFirstCharacter(this.translatedProfileName, this.locale).toUpperCase()
|
||||
: ''
|
||||
},
|
||||
translatedProfileName: function () {
|
||||
return this.isMainProfile ? this.$t('Profile.All Channels') : this.profileName
|
||||
|
|
|
@ -8,6 +8,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
|
|||
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||
import { calculateColorLuminance, colors } from '../../helpers/colors'
|
||||
import { showToast } from '../../helpers/utils'
|
||||
import { getFirstCharacter } from '../../helpers/strings'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtProfileEdit',
|
||||
|
@ -47,11 +48,16 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
locale: function () {
|
||||
return this.$i18n.locale.replace('_', '-')
|
||||
},
|
||||
colorValues: function () {
|
||||
return colors.map(color => color.value)
|
||||
},
|
||||
profileInitial: function () {
|
||||
return this?.profileName?.length > 0 ? Array.from(this.translatedProfileName)[0].toUpperCase() : ''
|
||||
return this.profileName
|
||||
? getFirstCharacter(this.translatedProfileName, this.locale).toUpperCase()
|
||||
: ''
|
||||
},
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
:value="translatedProfileName"
|
||||
:show-action-button="false"
|
||||
@input="e => profileName = e"
|
||||
@keydown.enter.native="saveProfile"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -5,6 +5,7 @@ import FtCard from '../../components/ft-card/ft-card.vue'
|
|||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||
import { showToast } from '../../helpers/utils'
|
||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||
import { getFirstCharacter } from '../../helpers/strings'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtProfileSelector',
|
||||
|
@ -19,6 +20,9 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
locale: function () {
|
||||
return this.$i18n.locale.replace('_', '-')
|
||||
},
|
||||
profileList: function () {
|
||||
return this.$store.getters.getProfileList
|
||||
},
|
||||
|
@ -26,12 +30,15 @@ export default defineComponent({
|
|||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
activeProfileInitial: function () {
|
||||
// use Array.from, so that emojis don't get split up into individual character codes
|
||||
return this.activeProfile?.name?.length > 0 ? Array.from(this.translatedProfileName(this.activeProfile))[0].toUpperCase() : ''
|
||||
return this.activeProfile?.name
|
||||
? getFirstCharacter(this.translatedProfileName(this.activeProfile), this.locale).toUpperCase()
|
||||
: ''
|
||||
},
|
||||
profileInitials: function () {
|
||||
return this.profileList.map((profile) => {
|
||||
return profile?.name?.length > 0 ? Array.from(this.translatedProfileName(profile))[0].toUpperCase() : ''
|
||||
return profile?.name
|
||||
? getFirstCharacter(this.translatedProfileName(profile), this.locale).toUpperCase()
|
||||
: ''
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
<span class="select-highlight" />
|
||||
<span class="select-bar" />
|
||||
<label
|
||||
v-if="!disabled"
|
||||
class="select-label"
|
||||
:for="sanitizedId ?? sanitizedPlaceholder"
|
||||
:hidden="disabled"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="icon"
|
||||
|
|
|
@ -6,6 +6,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
|
|||
|
||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||
import { deepCopy, showToast } from '../../helpers/utils'
|
||||
import { getFirstCharacter } from '../../helpers/strings'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtSubscribeButton',
|
||||
|
@ -45,9 +46,14 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
locale: function () {
|
||||
return this.$i18n.locale.replace('_', '-')
|
||||
},
|
||||
profileInitials: function () {
|
||||
return this.profileDisplayList.map((profile) => {
|
||||
return profile?.name?.length > 0 ? Array.from(profile.name)[0].toUpperCase() : ''
|
||||
return profile.name
|
||||
? getFirstCharacter(profile.name, this.locale).toUpperCase()
|
||||
: ''
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
input-type="password"
|
||||
:value="password"
|
||||
@input="e => password = e"
|
||||
@keydown.enter.native="handleSetPassword"
|
||||
/>
|
||||
<ft-button
|
||||
class="centerButton"
|
||||
|
|
|
@ -7,6 +7,7 @@ import FtInput from '../ft-input/ft-input.vue'
|
|||
import FtPrompt from '../ft-prompt/ft-prompt.vue'
|
||||
import FtButton from '../ft-button/ft-button.vue'
|
||||
import {
|
||||
ctrlFHandler,
|
||||
formatNumber,
|
||||
showToast,
|
||||
} from '../../helpers/utils'
|
||||
|
@ -411,19 +412,9 @@ export default defineComponent({
|
|||
this.$emit('search-video-query-change', query)
|
||||
},
|
||||
|
||||
keyboardShortcutHandler(event) {
|
||||
switch (event.key) {
|
||||
case 'F':
|
||||
case 'f':
|
||||
if (this.searchVideoModeAllowed && ((process.platform !== 'darwin' && event.ctrlKey) || (process.platform === 'darwin' && event.metaKey))) {
|
||||
nextTick(() => {
|
||||
// Some elements only present after rendering update
|
||||
this.$refs.searchInput.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
keyboardShortcutHandler: function (event) {
|
||||
ctrlFHandler(event, this.$refs.searchInput)
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'showAddToPlaylistPromptForManyVideos',
|
||||
'updatePlaylist',
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
:show-label="false"
|
||||
:value="newTitle"
|
||||
@input="(input) => (newTitle = input)"
|
||||
@keydown.enter.native="savePlaylistInfo"
|
||||
/>
|
||||
<h2
|
||||
v-else
|
||||
|
@ -66,6 +67,7 @@
|
|||
:show-label="false"
|
||||
:value="newDescription"
|
||||
@input="(input) => newDescription = input"
|
||||
@keydown.enter.native="savePlaylistInfo"
|
||||
/>
|
||||
<p
|
||||
v-else
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
:show-label="true"
|
||||
:value="proxyHostname"
|
||||
@input="handleUpdateProxyHostname"
|
||||
@keydown.enter.native="testProxy"
|
||||
/>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.Proxy Settings.Proxy Port Number')"
|
||||
|
@ -37,6 +38,7 @@
|
|||
:show-label="true"
|
||||
:value="proxyPort"
|
||||
@input="handleUpdateProxyPort"
|
||||
@keydown.enter.native="testProxy"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<p
|
||||
|
|
|
@ -52,3 +52,31 @@ export function translateWindowTitle(title, i18n) {
|
|||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first user-perceived character,
|
||||
* respecting language specific rules and
|
||||
* emojis made up of multiple codepoints
|
||||
* like flags, families and skin tone modifiers.
|
||||
* @param {string} text
|
||||
* @param {string} locale
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getFirstCharacter(text, locale) {
|
||||
if (text.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Firefox only received support for Intl.Segmenter support in version 125 (2024-04-16)
|
||||
// so fallback to Array.from just in case.
|
||||
// TODO: Remove fallback in the future
|
||||
if (Intl.Segmenter) {
|
||||
const segmenter = new Intl.Segmenter([locale, 'en'], { granularity: 'grapheme' })
|
||||
|
||||
// Use iterator directly as we only need the first segment
|
||||
const firstSegment = segmenter.segment(text)[Symbol.iterator]().next().value
|
||||
return firstSegment.segment
|
||||
} else {
|
||||
return Array.from(text)[0]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { IpcChannels } from '../../constants'
|
|||
import FtToastEvents from '../components/ft-toast/ft-toast-events'
|
||||
import i18n from '../i18n/index'
|
||||
import router from '../router/index'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
// allowed characters in channel handle: A-Z, a-z, 0-9, -, _, .
|
||||
// https://support.google.com/youtube/answer/11585688#change_handle
|
||||
|
@ -862,3 +863,13 @@ export async function fetchWithTimeout(timeoutMs, input, init) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ctrlFHandler(event, inputElement) {
|
||||
switch (event.key) {
|
||||
case 'F':
|
||||
case 'f':
|
||||
if (((process.platform !== 'darwin' && event.ctrlKey) || (process.platform === 'darwin' && event.metaKey))) {
|
||||
nextTick(() => inputElement?.focus())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import autolinker from 'autolinker'
|
|||
import {
|
||||
setPublishedTimestampsInvidious,
|
||||
copyToClipboard,
|
||||
ctrlFHandler,
|
||||
extractNumberFromString,
|
||||
formatNumber,
|
||||
showToast,
|
||||
|
@ -435,6 +436,7 @@ export default defineComponent({
|
|||
},
|
||||
mounted: function () {
|
||||
this.isLoading = true
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
|
||||
if (this.$route.query.url) {
|
||||
this.resolveChannelUrl(this.$route.query.url, this.$route.params.currentTab)
|
||||
|
@ -461,6 +463,9 @@ export default defineComponent({
|
|||
})
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
methods: {
|
||||
resolveChannelUrl: async function (url, tab = undefined) {
|
||||
let id
|
||||
|
@ -1951,6 +1956,10 @@ export default defineComponent({
|
|||
})
|
||||
},
|
||||
|
||||
keyboardShortcutHandler: function (event) {
|
||||
ctrlFHandler(event, this.$refs.channelSearchBar)
|
||||
},
|
||||
|
||||
getIconForSortPreference: (s) => getIconForSortPreference(s),
|
||||
|
||||
...mapActions([
|
||||
|
|
|
@ -206,6 +206,7 @@
|
|||
|
||||
<ft-input
|
||||
v-if="showSearchBar"
|
||||
ref="channelSearchBar"
|
||||
:placeholder="$t('Channel.Search Channel')"
|
||||
:show-clear-text-button="true"
|
||||
class="channelSearch"
|
||||
|
|
|
@ -7,6 +7,7 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
|||
import FtButton from '../../components/ft-button/ft-button.vue'
|
||||
import FtInput from '../../components/ft-input/ft-input.vue'
|
||||
import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue'
|
||||
import { ctrlFHandler } from '../../helpers/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'History',
|
||||
|
@ -53,6 +54,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
mounted: function () {
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
const limit = sessionStorage.getItem('historyLimit')
|
||||
|
||||
if (limit !== null) {
|
||||
|
@ -69,6 +71,9 @@ export default defineComponent({
|
|||
|
||||
this.filterHistoryDebounce = debounce(this.filterHistory, 500)
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
methods: {
|
||||
increaseLimit: function () {
|
||||
if (this.query !== '') {
|
||||
|
@ -113,5 +118,8 @@ export default defineComponent({
|
|||
this.activeData = filteredQuery.length < this.searchDataLimit ? filteredQuery : filteredQuery.slice(0, this.searchDataLimit)
|
||||
}
|
||||
},
|
||||
keyboardShortcutHandler: function (event) {
|
||||
ctrlFHandler(event, this.$refs.searchBar)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import FtInput from '../../components/ft-input/ft-input.vue'
|
|||
import FtSubscribeButton from '../../components/ft-subscribe-button/ft-subscribe-button.vue'
|
||||
import { invidiousGetChannelInfo, youtubeImageUrlToInvidious, invidiousImageUrlToInvidious } from '../../helpers/api/invidious'
|
||||
import { getLocalChannel } from '../../helpers/api/local'
|
||||
import { ctrlFHandler } from '../../helpers/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubscribedChannels',
|
||||
|
@ -26,7 +27,7 @@ export default defineComponent({
|
|||
},
|
||||
thumbnailSize: 176,
|
||||
ytBaseURL: 'https://yt3.ggpht.com',
|
||||
errorCount: 0
|
||||
errorCount: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -78,8 +79,12 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
mounted: function () {
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
this.getSubscription()
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
methods: {
|
||||
getSubscription: function () {
|
||||
this.subscribedChannels = this.activeSubscriptionList.slice().sort((a, b) => {
|
||||
|
@ -155,6 +160,10 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
keyboardShortcutHandler: function (event) {
|
||||
ctrlFHandler(event, this.$refs.searchBarChannels)
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateSubscriptionDetails'
|
||||
])
|
||||
|
|
|
@ -12,7 +12,7 @@ import FtInput from '../../components/ft-input/ft-input.vue'
|
|||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
|
||||
import FtAutoLoadNextPageWrapper from '../../components/ft-auto-load-next-page-wrapper/ft-auto-load-next-page-wrapper.vue'
|
||||
import { getIconForSortPreference } from '../../helpers/utils'
|
||||
import { ctrlFHandler, getIconForSortPreference } from '../../helpers/utils'
|
||||
|
||||
const SORT_BY_VALUES = {
|
||||
NameAscending: 'name_ascending',
|
||||
|
@ -184,6 +184,7 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
mounted: function () {
|
||||
document.addEventListener('keydown', this.keyboardShortcutHandler)
|
||||
const limit = sessionStorage.getItem('favoritesLimit')
|
||||
if (limit !== null) {
|
||||
this.dataLimit = limit
|
||||
|
@ -200,6 +201,9 @@ export default defineComponent({
|
|||
|
||||
this.filterPlaylistDebounce = debounce(this.filterPlaylist, 500)
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
document.removeEventListener('keydown', this.keyboardShortcutHandler)
|
||||
},
|
||||
methods: {
|
||||
increaseLimit: function () {
|
||||
if (this.query !== '') {
|
||||
|
@ -243,6 +247,10 @@ export default defineComponent({
|
|||
})
|
||||
},
|
||||
|
||||
keyboardShortcutHandler: function (event) {
|
||||
ctrlFHandler(event, this.$refs.searchBar)
|
||||
},
|
||||
|
||||
getIconForSortPreference: (s) => getIconForSortPreference(s),
|
||||
|
||||
...mapActions([
|
||||
|
|
|
@ -276,6 +276,9 @@ Settings:
|
|||
Open Link: Abrir ligação
|
||||
Ask Before Opening Link: Perguntar antes de abrir a ligação
|
||||
No Action: Nenhuma ação
|
||||
Auto Load Next Page:
|
||||
Label: Carregar seguinte automaticamente
|
||||
Tooltip: Carrega as páginas e comentários automaticamente.
|
||||
Theme Settings:
|
||||
Theme Settings: 'Configurações de tema'
|
||||
Match Top Bar with Main Color: 'Utilizar cor principal na barra superior'
|
||||
|
@ -633,6 +636,7 @@ Settings:
|
|||
Set Password: Definir palavra-passe
|
||||
Remove Password: Remover palavra-passe
|
||||
Expand All Settings Sections: Expandir todas as secções de configurações
|
||||
Sort Settings Sections (A-Z): Ordenar definições (A-Z)
|
||||
About:
|
||||
#On About page
|
||||
About: 'Acerca'
|
||||
|
@ -974,6 +978,13 @@ Playlist:
|
|||
Playlist: Lista de reprodução
|
||||
Sort By:
|
||||
Sort By: Ordenar por
|
||||
AuthorAscending: Autor (A-Z)
|
||||
DateAddedNewest: Últimas adições primeiro
|
||||
DateAddedOldest: Primeiras adições primeiro
|
||||
AuthorDescending: Autor (Z-A)
|
||||
VideoTitleAscending: Título (A-Z)
|
||||
VideoTitleDescending: Título (Z-A)
|
||||
Custom: Personalizado
|
||||
Toggle Theatre Mode: 'Alternar modo cinema'
|
||||
Change Format:
|
||||
Change Media Formats: 'Alterar formatos multimédia'
|
||||
|
@ -1192,3 +1203,6 @@ Age Restricted:
|
|||
Feed:
|
||||
Feed Last Updated: 'Última atualização de {feedName}: {date}'
|
||||
Refresh Feed: Recarregar {subscriptionName}
|
||||
Display Label: '{label}: {value}'
|
||||
Moments Ago: há momentos
|
||||
checkmark: ✓
|
||||
|
|
|
@ -608,6 +608,7 @@ Settings:
|
|||
Enter Password To Unlock: Унесите лозинку да бисте откључали подешавања
|
||||
Unlock: Откључај
|
||||
Expand All Settings Sections: Прошири све одељке подешавања
|
||||
Sort Settings Sections (A-Z): Сортирање одељка подешавања (A-Z)
|
||||
About:
|
||||
#On About page
|
||||
About: 'О апликацији'
|
||||
|
|
Loading…
Reference in New Issue