mirror of
https://github.com/FreeTubeApp/FreeTube
synced 2024-11-22 01:45:40 +01:00
Merge branch 'development' of github.com:FreeTubeApp/FreeTube into feat/add-select-mode
This commit is contained in:
commit
74f14ea273
@ -11,17 +11,12 @@
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
-webkit-transition: background 0.2s ease-out;
|
||||
-moz-transition: background 0.2s ease-out;
|
||||
-o-transition: background 0.2s ease-out;
|
||||
transition: background 0.2s ease-out;
|
||||
}
|
||||
|
||||
.bubblePadding:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
}"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-expanded="dropdownShown"
|
||||
@click.stop="handleIconClick"
|
||||
@mousedown.stop="handleIconMouseDown"
|
||||
@keydown.enter.stop.prevent="handleIconClick"
|
||||
@ -34,7 +35,6 @@
|
||||
v-if="dropdownOptions.length > 0"
|
||||
class="list"
|
||||
role="listbox"
|
||||
:aria-expanded="dropdownShown"
|
||||
>
|
||||
<li
|
||||
v-for="(option, index) in dropdownOptions"
|
||||
@ -72,7 +72,6 @@
|
||||
v-if="dropdownOptions.length > 0"
|
||||
class="list"
|
||||
role="listbox"
|
||||
:aria-expanded="dropdownShown"
|
||||
>
|
||||
<li
|
||||
v-for="(option, index) in dropdownOptions"
|
||||
|
@ -57,8 +57,6 @@
|
||||
border-radius: 100%;
|
||||
color: var(--primary-text-color);
|
||||
opacity: 0;
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
@ -74,8 +72,6 @@
|
||||
.clearInputTextButton.visible:active {
|
||||
background-color: var(--tertiary-text-color);
|
||||
color: var(--side-nav-active-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
@ -172,8 +168,6 @@
|
||||
.inputAction.enabled:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
@ -184,8 +178,6 @@
|
||||
.inputAction.enabled:active {
|
||||
background-color: var(--tertiary-text-color);
|
||||
color: var(--side-nav-active-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
|
@ -4,17 +4,12 @@
|
||||
padding-block: 10px 30px;
|
||||
padding-inline: 10px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: background 0.2s ease-out;
|
||||
-moz-transition: background 0.2s ease-out;
|
||||
-o-transition: background 0.2s ease-out;
|
||||
transition: background 0.2s ease-out;
|
||||
}
|
||||
|
||||
.bubblePadding:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
inset-block-start: 60px;
|
||||
inset-inline-end: 10px;
|
||||
min-inline-size: 250px;
|
||||
block-size: 400px;
|
||||
block-size: auto;
|
||||
padding: 5px;
|
||||
background-color: var(--card-bg-color);
|
||||
box-shadow: 0 0 4px var(--scrollbar-color-hover);
|
||||
@ -35,24 +35,33 @@
|
||||
|
||||
.profileWrapper {
|
||||
margin-block-start: 60px;
|
||||
block-size: 340px;
|
||||
block-size: auto;
|
||||
overflow-y: auto;
|
||||
/*
|
||||
profile list max height: 90% of window size - 100 px. It's scaled to be 340px on 800x600 resolution.
|
||||
Offset of 100px is to compensate for the fixed size of elements above the list, which takes more screen space on lower resolutions
|
||||
*/
|
||||
max-block-size: calc(90vh - 100px);
|
||||
min-block-size: 340px;
|
||||
}
|
||||
|
||||
/* Navbar changes position to horizontal with this media rule.
|
||||
Height adjust for profile list so it won't cover navbar. */
|
||||
@media only screen and (max-width: 680px){
|
||||
.profileWrapper {
|
||||
max-block-size: calc(95vh - 180px);
|
||||
}
|
||||
}
|
||||
|
||||
.profile {
|
||||
cursor: pointer;
|
||||
block-size: 50px;
|
||||
-webkit-transition: background 0.2s ease-out;
|
||||
-moz-transition: background 0.2s ease-out;
|
||||
-o-transition: background 0.2s ease-out;
|
||||
transition: background 0.2s ease-out;
|
||||
}
|
||||
|
||||
.profile:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,10 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isActiveProfile: function (profile) {
|
||||
return profile._id === this.activeProfile._id
|
||||
},
|
||||
|
||||
toggleProfileList: function () {
|
||||
this.profileListShown = !this.profileListShown
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
:style="{ background: activeProfile.bgColor, color: activeProfile.textColor }"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-expanded="profileListShown"
|
||||
aria-controls="profileSelectorList"
|
||||
@click="toggleProfileList"
|
||||
@mousedown="handleIconMouseDown"
|
||||
@keydown.space.prevent="toggleProfileList"
|
||||
@ -19,6 +21,7 @@
|
||||
</div>
|
||||
<ft-card
|
||||
v-show="profileListShown"
|
||||
id="profileSelectorList"
|
||||
ref="profileList"
|
||||
class="profileList"
|
||||
tabindex="-1"
|
||||
@ -46,7 +49,7 @@
|
||||
:key="index"
|
||||
class="profile"
|
||||
:aria-labelledby="'profile-' + index + '-name'"
|
||||
aria-selected="false"
|
||||
:aria-selected="isActiveProfile(profile)"
|
||||
tabindex="0"
|
||||
role="option"
|
||||
@click="setActiveProfile(profile)"
|
||||
|
@ -1,8 +0,0 @@
|
||||
/* Ensures style here overrides style of .btn */
|
||||
.subscribeButton.btn {
|
||||
align-self: center;
|
||||
block-size: 50%;
|
||||
margin-block-end: 10px;
|
||||
min-inline-size: 150px;
|
||||
white-space: initial;
|
||||
}
|
@ -25,29 +25,54 @@ export default defineComponent({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
hideProfileDropdownToggle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
subscriptionCountText: {
|
||||
default: '',
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isProfileDropdownOpen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
profileInitials: function () {
|
||||
return this.profileDisplayList.map((profile) => {
|
||||
return profile?.name?.length > 0 ? Array.from(profile.name)[0].toUpperCase() : ''
|
||||
})
|
||||
},
|
||||
|
||||
profileList: function () {
|
||||
return this.$store.getters.getProfileList
|
||||
},
|
||||
|
||||
/* sort by 'All Channels' -> active profile -> unsubscribed channels -> subscribed channels */
|
||||
profileDisplayList: function () {
|
||||
const mainProfileAndActiveProfile = [this.profileList[0]]
|
||||
if (this.activeProfile._id !== MAIN_PROFILE_ID) {
|
||||
mainProfileAndActiveProfile.push(this.activeProfile)
|
||||
}
|
||||
|
||||
return [
|
||||
...mainProfileAndActiveProfile,
|
||||
...this.profileList.filter((profile, i) =>
|
||||
i !== 0 && !this.isActiveProfile(profile) && !this.isProfileSubscribed(profile)),
|
||||
...this.profileList.filter((profile, i) =>
|
||||
i !== 0 && !this.isActiveProfile(profile) && this.isProfileSubscribed(profile))
|
||||
]
|
||||
},
|
||||
|
||||
activeProfile: function () {
|
||||
return this.$store.getters.getActiveProfile
|
||||
},
|
||||
|
||||
subscriptionInfo: function () {
|
||||
return this.activeProfile.subscriptions.find((channel) => {
|
||||
return channel.id === this.channelId
|
||||
}) ?? null
|
||||
},
|
||||
|
||||
isSubscribed: function () {
|
||||
return this.subscriptionInfo !== null
|
||||
return this.subscriptionInfoForProfile(this.activeProfile)
|
||||
},
|
||||
|
||||
hideChannelSubscriptions: function () {
|
||||
@ -55,23 +80,27 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
subscribedText: function () {
|
||||
let subscribedValue = (this.isSubscribed ? this.$t('Channel.Unsubscribe') : this.$t('Channel.Subscribe')).toUpperCase()
|
||||
let subscribedValue = (this.isProfileSubscribed(this.activeProfile) ? this.$t('Channel.Unsubscribe') : this.$t('Channel.Subscribe')).toUpperCase()
|
||||
if (this.subscriptionCountText !== '' && !this.hideChannelSubscriptions) {
|
||||
subscribedValue += ' ' + this.subscriptionCountText
|
||||
}
|
||||
return subscribedValue
|
||||
},
|
||||
|
||||
isProfileDropdownEnabled: function () {
|
||||
return !this.hideProfileDropdownToggle && this.profileList.length > 1
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
handleSubscription: function () {
|
||||
handleSubscription: function (profile = this.activeProfile) {
|
||||
if (this.channelId === '') {
|
||||
return
|
||||
}
|
||||
|
||||
const currentProfile = deepCopy(this.activeProfile)
|
||||
const currentProfile = deepCopy(profile)
|
||||
|
||||
if (this.isSubscribed) {
|
||||
if (this.isProfileSubscribed(profile)) {
|
||||
currentProfile.subscriptions = currentProfile.subscriptions.filter((channel) => {
|
||||
return channel.id !== this.channelId
|
||||
})
|
||||
@ -79,16 +108,16 @@ export default defineComponent({
|
||||
this.updateProfile(currentProfile)
|
||||
showToast(this.$t('Channel.Channel has been removed from your subscriptions'))
|
||||
|
||||
if (this.activeProfile._id === MAIN_PROFILE_ID) {
|
||||
if (profile._id === MAIN_PROFILE_ID) {
|
||||
// Check if a subscription exists in a different profile.
|
||||
// Remove from there as well.
|
||||
let duplicateSubscriptions = 0
|
||||
|
||||
this.profileList.forEach((profile) => {
|
||||
if (profile._id === MAIN_PROFILE_ID) {
|
||||
this.profileList.forEach((profileInList) => {
|
||||
if (profileInList._id === MAIN_PROFILE_ID) {
|
||||
return
|
||||
}
|
||||
duplicateSubscriptions += this.unsubscribe(profile, this.channelId)
|
||||
duplicateSubscriptions += this.unsubscribe(profileInList, this.channelId)
|
||||
})
|
||||
|
||||
if (duplicateSubscriptions > 0) {
|
||||
@ -107,7 +136,7 @@ export default defineComponent({
|
||||
this.updateProfile(currentProfile)
|
||||
showToast(this.$t('Channel.Added channel to your subscriptions'))
|
||||
|
||||
if (this.activeProfile._id !== MAIN_PROFILE_ID) {
|
||||
if (profile._id !== MAIN_PROFILE_ID) {
|
||||
const primaryProfile = deepCopy(this.profileList.find(prof => {
|
||||
return prof._id === MAIN_PROFILE_ID
|
||||
}))
|
||||
@ -122,6 +151,34 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isProfileDropdownEnabled && !this.isProfileDropdownOpen) {
|
||||
this.toggleProfileDropdown()
|
||||
}
|
||||
},
|
||||
|
||||
handleProfileDropdownFocusOut: function () {
|
||||
if (!this.$refs.subscribeButton.matches(':focus-within')) {
|
||||
this.isProfileDropdownOpen = false
|
||||
}
|
||||
},
|
||||
|
||||
toggleProfileDropdown: function() {
|
||||
this.isProfileDropdownOpen = !this.isProfileDropdownOpen
|
||||
},
|
||||
|
||||
isActiveProfile: function (profile) {
|
||||
return profile._id === this.activeProfile._id
|
||||
},
|
||||
|
||||
subscriptionInfoForProfile: function (profile) {
|
||||
return profile.subscriptions.find((channel) => {
|
||||
return channel.id === this.channelId
|
||||
}) ?? null
|
||||
},
|
||||
|
||||
isProfileSubscribed: function (profile) {
|
||||
return this.subscriptionInfoForProfile(profile) !== null
|
||||
},
|
||||
|
||||
unsubscribe: function(profile, channelId) {
|
||||
|
@ -0,0 +1,141 @@
|
||||
.buttonList {
|
||||
margin: 5px;
|
||||
margin-block-end: 10px;
|
||||
border-radius: 4px;
|
||||
block-size: fit-content;
|
||||
box-shadow: 0px 1px 2px rgb(0 0 0 / 50%);
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
/* addresses odd clipping behavior when adjusting window size */
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.ftSubscribeButton {
|
||||
position: relative;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
/* Ensures style here overrides style of .btn */
|
||||
.subscribeButton.btn {
|
||||
min-inline-size: 150px;
|
||||
white-space: initial;
|
||||
}
|
||||
|
||||
.subscribeButton.btn, .profileDropdownToggle.btn {
|
||||
align-self: center;
|
||||
margin-block: 0;
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
.dropdownOpened {
|
||||
.subscribeButton, .profileDropdownToggle {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.profileDropdownToggle.btn {
|
||||
border-inline-start: none !important;
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
display: inline-block;
|
||||
min-inline-size: 1em;
|
||||
padding-inline: 10px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.hasProfileDropdownToggle {
|
||||
.subscribeButton.btn {
|
||||
min-inline-size: 100px;
|
||||
padding-inline: 5px;
|
||||
border-inline-end: 2px solid var(--primary-color-active) !important;
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
}
|
||||
|
||||
.hasProfileDropdownToggle > .subscribeButton.btn, .profileDropdownToggle.btn {
|
||||
padding-block: 5px;
|
||||
padding-inline: 6px;
|
||||
box-shadow: none;
|
||||
flex: auto;
|
||||
block-size: 2em;
|
||||
}
|
||||
|
||||
.profileDropdown {
|
||||
background-color: var(--side-nav-color);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 50%);
|
||||
color: var(--secondary-text-color);
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
max-block-size: 200px;
|
||||
margin-block: -10px 0;
|
||||
margin-inline: 5px 0;
|
||||
overflow-y: scroll;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
z-index: 3;
|
||||
// accounts for parent's left and right margins
|
||||
inline-size: calc(100% - 10px);
|
||||
|
||||
.profileList {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.profile {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
padding-inline-start: 0.5em;
|
||||
block-size: 50px;
|
||||
align-items: center;
|
||||
transition: background 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
.colorOption {
|
||||
inline-size: 40px;
|
||||
block-size: 40px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
-webkit-border-radius: 50%;
|
||||
}
|
||||
|
||||
.initial {
|
||||
font-size: 20px;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.profileName {
|
||||
padding-inline-end: 1em;
|
||||
text-align: start;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.subscribed {
|
||||
background-color: var(--primary-color);
|
||||
|
||||
.profileName {
|
||||
color: var(--text-with-main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,83 @@
|
||||
<template>
|
||||
<ft-button
|
||||
:label="subscribedText"
|
||||
class="subscribeButton"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="handleSubscription"
|
||||
/>
|
||||
<div
|
||||
ref="subscribeButton"
|
||||
class="ftSubscribeButton"
|
||||
:class="{ dropdownOpened: isProfileDropdownOpen}"
|
||||
@focusout="handleProfileDropdownFocusOut"
|
||||
>
|
||||
<div
|
||||
class="buttonList"
|
||||
:class="{ hasProfileDropdownToggle: isProfileDropdownEnabled}"
|
||||
>
|
||||
<ft-button
|
||||
:label="subscribedText"
|
||||
:no-border="true"
|
||||
class="subscribeButton"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="handleSubscription"
|
||||
/>
|
||||
<ft-button
|
||||
v-if="isProfileDropdownEnabled"
|
||||
:no-border="true"
|
||||
:title="isProfileDropdownOpen ? $t('Profile.Close Profile Dropdown') : $t('Profile.Open Profile Dropdown')"
|
||||
class="profileDropdownToggle"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
:aria-expanded="isProfileDropdownOpen"
|
||||
@click="toggleProfileDropdown"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="isProfileDropdownOpen ? ['fas', 'angle-up'] : ['fas', 'angle-down']"
|
||||
/>
|
||||
</ft-button>
|
||||
</div>
|
||||
<template v-if="isProfileDropdownOpen">
|
||||
<div
|
||||
tabindex="-1"
|
||||
class="profileDropdown"
|
||||
>
|
||||
<ul
|
||||
class="profileList"
|
||||
>
|
||||
<li
|
||||
v-for="(profile, index) in profileDisplayList"
|
||||
:id="'subscription-profile-' + index"
|
||||
:key="index"
|
||||
class="profile"
|
||||
:class="{
|
||||
subscribed: isProfileSubscribed(profile)
|
||||
}"
|
||||
:aria-labelledby="'subscription-profile-' + index + '-name'"
|
||||
:aria-selected="isActiveProfile(profile)"
|
||||
:aria-checked="isProfileSubscribed(profile)"
|
||||
tabindex="0"
|
||||
role="checkbox"
|
||||
@click.stop.prevent="handleSubscription(profile)"
|
||||
@keydown.space.stop.prevent="handleSubscription(profile)"
|
||||
>
|
||||
<div
|
||||
class="colorOption"
|
||||
:style="{ background: profile.bgColor, color: profile.textColor }"
|
||||
>
|
||||
<div
|
||||
class="initial"
|
||||
>
|
||||
{{ isProfileSubscribed(profile) ? '✓' : profileInitials[index] }}
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
:id="'subscription-profile-' + index + '-name'"
|
||||
class="profileName"
|
||||
>
|
||||
{{ profile.name }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./ft-subscribe-button.js" />
|
||||
<style src="./ft-subscribe-button.css" />
|
||||
<style lang="scss" src="./ft-subscribe-button.scss" />
|
||||
|
@ -14,16 +14,12 @@
|
||||
.navOption:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
.navOption:active {
|
||||
background-color: var(--side-nav-active-color);
|
||||
color: var(--side-nav-active-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
|
@ -53,16 +53,12 @@
|
||||
.navOption:hover, .navChannel:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
color: var(--side-nav-hover-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
.navOption:active, .navChannel:active {
|
||||
background-color: var(--side-nav-active-color);
|
||||
color: var(--side-nav-active-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,13 @@
|
||||
row-gap: 16px;
|
||||
}
|
||||
|
||||
.watchingCount {
|
||||
font-weight: normal;
|
||||
margin-inline-start: 5px;
|
||||
font-size: 15px;
|
||||
color: var(--tertiary-text-color);
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 18px;
|
||||
color: var(--tertiary-text-color);
|
||||
|
@ -6,6 +6,7 @@ import FtButton from '../ft-button/ft-button.vue'
|
||||
import autolinker from 'autolinker'
|
||||
import { getRandomColorClass } from '../../helpers/colors'
|
||||
import { getLocalVideoInfo, parseLocalTextRuns } from '../../helpers/api/local'
|
||||
import { formatNumber } from '../../helpers/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WatchVideoLiveChat',
|
||||
@ -30,6 +31,7 @@ export default defineComponent({
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
/** @type {import('youtubei.js').YT.LiveChat|null} */
|
||||
liveChatInstance: null,
|
||||
isLoading: true,
|
||||
hasError: false,
|
||||
@ -52,7 +54,9 @@ export default defineComponent({
|
||||
amount: '',
|
||||
colorClass: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
/** @type {number|null} */
|
||||
watchingCount: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -74,6 +78,14 @@ export default defineComponent({
|
||||
|
||||
scrollingBehaviour: function () {
|
||||
return this.$store.getters.getDisableSmoothScrolling ? 'auto' : 'smooth'
|
||||
},
|
||||
|
||||
hideVideoViews: function () {
|
||||
return this.$store.getters.getHideVideoViews
|
||||
},
|
||||
|
||||
formattedWatchingCount: function () {
|
||||
return this.watchingCount !== null ? formatNumber(this.watchingCount) : '0'
|
||||
}
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
@ -181,6 +193,12 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
this.liveChatInstance.on('metadata-update', metadata => {
|
||||
if (!this.hideVideoViews && metadata.views && !isNaN(metadata.views.original_view_count)) {
|
||||
this.watchingCount = metadata.views.original_view_count
|
||||
}
|
||||
})
|
||||
|
||||
this.liveChatInstance.once('end', () => {
|
||||
this.hasEnded = true
|
||||
this.liveChatInstance = null
|
||||
|
@ -42,7 +42,15 @@
|
||||
v-else
|
||||
class="relative"
|
||||
>
|
||||
<h4>{{ $t("Video.Live Chat") }}</h4>
|
||||
<h4>
|
||||
{{ $t("Video.Live Chat") }}
|
||||
<span
|
||||
v-if="!hideVideoViews && watchingCount !== null"
|
||||
class="watchingCount"
|
||||
>
|
||||
{{ $tc('Global.Counts.Watching Count', watchingCount, { count: formattedWatchingCount }) }}
|
||||
</span>
|
||||
</h4>
|
||||
<div
|
||||
v-if="superChatComments.length > 0"
|
||||
class="superChatComments"
|
||||
|
@ -9,6 +9,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
// to avoid code conflict and duplicate entries
|
||||
import {
|
||||
faAngleDown,
|
||||
faAngleUp,
|
||||
faArrowDown,
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
@ -82,6 +83,7 @@ Vue.config.productionTip = process.env.NODE_ENV === 'development'
|
||||
library.add(
|
||||
// solid icons
|
||||
faAngleDown,
|
||||
faAngleUp,
|
||||
faArrowDown,
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
|
@ -69,6 +69,7 @@
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.subscribeButton {
|
||||
|
@ -26,7 +26,7 @@
|
||||
<ft-flex-box class="channels">
|
||||
<div
|
||||
v-for="channel in channelList"
|
||||
:key="channel.key"
|
||||
:key="channel.id"
|
||||
class="channel"
|
||||
>
|
||||
<router-link
|
||||
@ -53,7 +53,6 @@
|
||||
class="unsubscribeContainer"
|
||||
>
|
||||
<ft-subscribe-button
|
||||
class="btn"
|
||||
:channel-id="channel.id"
|
||||
:channel-name="channel.name"
|
||||
:channel-thumbnail="channel.thumbnail"
|
||||
|
@ -811,6 +811,8 @@ Video:
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': الدردشة
|
||||
المباشرة غير متاحة لهذا البث. ربما تم تعطيلها من قبل القائم بالتحميل.
|
||||
Pause on Current Video: توقف مؤقتًا على الفيديو الحالي
|
||||
Unhide Channel: عرض القناة
|
||||
Hide Channel: إخفاء القناة
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -1037,3 +1039,6 @@ Playlist will pause when current video is finished: ستتوقف قائمة ال
|
||||
انتهاء الفيديو الحالي
|
||||
Playlist will not pause when current video is finished: لن تتوقف قائمة التشغيل مؤقتًا
|
||||
عند انتهاء الفيديو الحالي
|
||||
Channel Hidden: تم إضافة {channel} إلى مرشح القناة
|
||||
Go to page: إذهب إلى {page}
|
||||
Channel Unhidden: تمت إزالة {channel} من مرشح القناة
|
||||
|
@ -3,7 +3,7 @@ Locale Name: 'ئیگلیزی (وڵاتە یەکگرتووەکانی ئەمریک
|
||||
FreeTube: 'فریتیوب'
|
||||
# Currently on Subscriptions, Playlists, and History
|
||||
'This part of the app is not ready yet. Come back later when progress has been made.': >-
|
||||
بەشێک لە بەرنامۆچکە هێشتا ئامادە نییە. کە ڕەوتەکە درووست کرا دووبارە وەرەوە.
|
||||
بەشێک لە نەرمەواڵەکە هێشتا ئامادە نییە. کە ڕەوتەکە درووست کرا دووبارە وەرەوە.
|
||||
|
||||
# Webkit Menu Bar
|
||||
File: 'پەڕگە'
|
||||
@ -32,9 +32,9 @@ Back: 'دواوە'
|
||||
Forward: 'پێشەوە'
|
||||
Open New Window: 'کردنەوەی پەنجەرەیەکی نوێ'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'وەشانی {versionNumber}
|
||||
ئێستا بەردەستە! بۆ زانیاری زۆرتر کرتە بکە'
|
||||
Download From Site: 'داگرتن لە وێبگە'
|
||||
Version {versionNumber} is now available! Click for more details: 'ئێستا وەشانی {versionNumber}
|
||||
بەردەستە..بۆ زانیاری زۆرتر کرتە بکە'
|
||||
Download From Site: 'لە وێبگەوە دایگرە'
|
||||
A new blog is now available, {blogTitle}. Click to view more: ''
|
||||
Are you sure you want to open this link?: 'دڵنیایت دەتەوێت ئەم بەستەرە بکەیتەوە؟'
|
||||
|
||||
@ -44,13 +44,13 @@ Global:
|
||||
Videos: 'ڤیدیۆکان'
|
||||
Shorts: ''
|
||||
Live: 'ڕاستەوخۆ'
|
||||
Community: ''
|
||||
Community: 'کۆمەڵگە'
|
||||
Counts:
|
||||
Video Count: '١ ڤیدیۆ | {count} ڤیدیۆ'
|
||||
Channel Count: '١ کەناڵ | {count} کەناڵ'
|
||||
Subscriber Count: '١ بەشداربوو | {count} بەشداربوو'
|
||||
View Count: 'بینینەک | {count} بینین'
|
||||
Watching Count: ''
|
||||
Watching Count: '١ تەمەشاکردن | {count} تەمەشاکردن'
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: ''
|
||||
@ -58,7 +58,7 @@ Search Bar:
|
||||
Clear Input: ''
|
||||
# In Filter Button
|
||||
Search Filters:
|
||||
Search Filters: ''
|
||||
Search Filters: 'پاڵفتەکردنی گەڕان'
|
||||
Sort By:
|
||||
Sort By: 'ڕیزکردن بە'
|
||||
Most Relevant: ''
|
||||
@ -87,10 +87,10 @@ Search Filters:
|
||||
Medium (4 - 20 minutes): 'ناوەند (٤ - ٢٠ خولەک)'
|
||||
Long (> 20 minutes): 'درێژ (> ٢٠ خولەک)'
|
||||
# On Search Page
|
||||
Search Results: ''
|
||||
Search Results: 'ئەنجامەکانی گەڕان'
|
||||
Fetching results. Please wait: ''
|
||||
Fetch more results: ''
|
||||
There are no more results for this search: ''
|
||||
There are no more results for this search: 'ئەنجامەکی تر نییە بۆ ئەم گەڕانە'
|
||||
# Sidebar
|
||||
Subscriptions:
|
||||
# On Subscriptions Page
|
||||
@ -121,26 +121,27 @@ Channels:
|
||||
Unsubscribe Prompt: ''
|
||||
Trending:
|
||||
Trending: ''
|
||||
Default: ''
|
||||
Default: 'بنەڕەت'
|
||||
Music: 'مۆسیقا'
|
||||
Gaming: 'یاری'
|
||||
Movies: 'فیلم'
|
||||
Trending Tabs: ''
|
||||
Most Popular: 'باوترین'
|
||||
Playlists: ''
|
||||
Playlists: 'پێڕستی لێدانەکان'
|
||||
User Playlists:
|
||||
Your Playlists: ''
|
||||
Your Playlists: 'پێڕستی لێدانەکانت'
|
||||
Playlist Message: ''
|
||||
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: ''
|
||||
Empty Search Message: ''
|
||||
Search bar placeholder: ''
|
||||
Search bar placeholder: 'لەناو پێڕستی لێدان بگەڕێ'
|
||||
History:
|
||||
# On History Page
|
||||
History: 'مێژوو'
|
||||
Watch History: 'مێژووی تەمەشاکردن'
|
||||
Your history list is currently empty.: ''
|
||||
Empty Search Message: ''
|
||||
Search bar placeholder: ""
|
||||
Your history list is currently empty.: 'ئێستا لیستەی مێژووت بەتاڵە.'
|
||||
Empty Search Message: 'هیچ ڤیدیۆیەک لە مێژووت نەدۆزرایەوە کە بەرانبەری گەڕانەکەت
|
||||
بێت'
|
||||
Search bar placeholder: "لەناو مێژوو بگەڕێ"
|
||||
Settings:
|
||||
# On Settings Page
|
||||
Settings: 'ڕێکخستنەکان'
|
||||
@ -153,7 +154,7 @@ Settings:
|
||||
Enable Search Suggestions: ''
|
||||
Default Landing Page: ''
|
||||
Locale Preference: ''
|
||||
System Default: ''
|
||||
System Default: 'بنەڕەتی سیستەم'
|
||||
Preferred API Backend:
|
||||
Preferred API Backend: ''
|
||||
Local API: ''
|
||||
@ -164,8 +165,8 @@ Settings:
|
||||
List: 'پێڕست'
|
||||
Thumbnail Preference:
|
||||
Thumbnail Preference: ''
|
||||
Default: ''
|
||||
Beginning: ''
|
||||
Default: 'بنەڕەت'
|
||||
Beginning: 'سەرەتا'
|
||||
Middle: 'ناوەڕاست'
|
||||
End: 'کۆتایی'
|
||||
Hidden: 'شاراوە'
|
||||
@ -193,10 +194,10 @@ Settings:
|
||||
Hide Side Bar Labels: ''
|
||||
Hide FreeTube Header Logo: ''
|
||||
Base Theme:
|
||||
Base Theme: ''
|
||||
Base Theme: 'ڕووکاری بنچینە'
|
||||
Black: 'ڕەش'
|
||||
Dark: 'تاریک'
|
||||
System Default: ''
|
||||
System Default: 'بنەڕەتی سیستەم'
|
||||
Light: 'ڕووناک'
|
||||
Dracula: ''
|
||||
Catppuccin Mocha: ''
|
||||
@ -208,11 +209,11 @@ Settings:
|
||||
Pink: 'پەمبە'
|
||||
Purple: 'وەنەوشەیی'
|
||||
Deep Purple: 'وەنەوشەیی تۆخ'
|
||||
Indigo: ''
|
||||
Indigo: 'نیلی'
|
||||
Blue: 'شین'
|
||||
Light Blue: 'شینی ئاڵ'
|
||||
Cyan: ''
|
||||
Teal: ''
|
||||
Cyan: 'شینی تۆخ'
|
||||
Teal: 'شەدری'
|
||||
Green: 'کەسک'
|
||||
Light Green: 'کەسکی ئاڵ'
|
||||
Lime: ''
|
||||
@ -856,3 +857,4 @@ Hashtag:
|
||||
Yes: ''
|
||||
No: ''
|
||||
Ok: ''
|
||||
Go to page: بڕۆ بۆ {page}
|
||||
|
@ -806,6 +806,8 @@ Video:
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Živý
|
||||
chat není pro tento stream k dispozici. Je možné, že byl vypnut nahrávajícím.
|
||||
Pause on Current Video: Pozastavit na současném videu
|
||||
Unhide Channel: Zobrazit kanál
|
||||
Hide Channel: Skrýt kanál
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -1036,3 +1038,6 @@ Playlist will pause when current video is finished: Po přehrání aktuálního
|
||||
playlist pozastaven
|
||||
Playlist will not pause when current video is finished: Po přehrání aktuálního videa
|
||||
nebude playlist pozastaven
|
||||
Channel Hidden: Kanál {channel} přidán do filtru kanálů
|
||||
Go to page: Přejít na {page}
|
||||
Channel Unhidden: Kanál {channel} odebrán z filtrů kanálů
|
||||
|
@ -554,6 +554,8 @@ Profile:
|
||||
Are you sure you want to delete the selected channels? This will not delete the channel from any other profile.: Are
|
||||
you sure you want to delete the selected channels? This will not delete the channel
|
||||
from any other profile.
|
||||
Close Profile Dropdown: Close Profile Dropdown
|
||||
Open Profile Dropdown: Open Profile Dropdown
|
||||
#On Channel Page
|
||||
Channel:
|
||||
Subscribe: Subscribe
|
||||
|
@ -827,6 +827,8 @@ Video:
|
||||
chat en vivo no está disponible para esta transmisión. Tal vez estaba deshabilitado
|
||||
antes de la retransmisión.
|
||||
Pause on Current Video: Pausa en el vídeo actual
|
||||
Unhide Channel: Mostrar el canal
|
||||
Hide Channel: Ocultar el canal
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -1073,3 +1075,5 @@ Playlist will pause when current video is finished: La lista de reproducción se
|
||||
Playlist will not pause when current video is finished: La lista de reproducción no
|
||||
se detendrá cuando termine el vídeo actual
|
||||
Go to page: Ir a la {page}
|
||||
Channel Hidden: '{channel} añadido al filtro de canales'
|
||||
Channel Unhidden: '{channel} eliminado del filtro de canales'
|
||||
|
@ -764,6 +764,8 @@ Video:
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Otsevestlus
|
||||
pole selle videovoo puhul saadaval. Võib-olla on üleslaadija vestluse keelanud.
|
||||
Pause on Current Video: Peata hetkel esitatav video
|
||||
Unhide Channel: Näita kanalit
|
||||
Hide Channel: Peida kanal
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -991,3 +993,6 @@ Playlist will pause when current video is finished: Hetkel mängiva video lõppe
|
||||
esitusloendi esitamine peatub
|
||||
Playlist will not pause when current video is finished: Hetkel mängiva video lõppemisel
|
||||
esitusloendi esitamine jätkub
|
||||
Channel Hidden: '{channel} on lisatud kanalite filtrisse'
|
||||
Go to page: 'Ava leht: {page}'
|
||||
Channel Unhidden: '{channel} on eemaldatud kanalite filtrist'
|
||||
|
@ -794,6 +794,8 @@ Video:
|
||||
chat dal vivo non è disponibile per questo video. Potrebbe essere stata disattivata
|
||||
dall'autore del caricamento.
|
||||
Pause on Current Video: Pausa sul video attuale
|
||||
Unhide Channel: Mostra canale
|
||||
Hide Channel: Nascondi canale
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -1082,3 +1084,6 @@ Playlist will pause when current video is finished: La playlist verrà messa in
|
||||
al termine del video attuale
|
||||
Playlist will not pause when current video is finished: La playlist non verrà messa
|
||||
in pausa al termine del video attuale
|
||||
Channel Hidden: '{channel} aggiunto al filtro canali'
|
||||
Go to page: Vai a {page}
|
||||
Channel Unhidden: '{channel} rimosso dal filtro canali'
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Put the name of your locale in the same language
|
||||
Locale Name: 'kur-ckb'
|
||||
Locale Name: 'کوردی ناوەڕاست'
|
||||
FreeTube: 'فریتیوب'
|
||||
# Currently on Subscriptions, Playlists, and History
|
||||
'This part of the app is not ready yet. Come back later when progress has been made.': >-
|
||||
@ -7,20 +7,20 @@ FreeTube: 'فریتیوب'
|
||||
رویداوە.
|
||||
|
||||
# Webkit Menu Bar
|
||||
File: 'فایل'
|
||||
Quit: 'چونەدەرەوە'
|
||||
Edit: 'دەستکاریکردن'
|
||||
Undo: 'گەڕانەوە'
|
||||
File: 'پەڕگە'
|
||||
Quit: 'دەرچوون'
|
||||
Edit: 'دەستکاری'
|
||||
Undo: 'پووچکردنەوە'
|
||||
Redo: 'هێنانەوە'
|
||||
Cut: 'بڕین'
|
||||
Copy: 'کۆپی'
|
||||
Paste: 'پەیست'
|
||||
Copy: 'لەبەرگرتنەوە'
|
||||
Paste: 'لکاندن'
|
||||
Delete: 'سڕینەوە'
|
||||
Select all: 'دیاریکردنی هەمووی'
|
||||
Reload: 'دوبارە دابەزاندن'
|
||||
Force Reload: 'دوباری دابەزاندی بەهێز'
|
||||
Toggle Developer Tools: 'ئەدەواتەکانی دیڤیڵۆپەر بەردەست بخە'
|
||||
Actual size: 'گەورەیی راستی'
|
||||
Reload: 'بارکردنەوە'
|
||||
Force Reload: 'بارکردنەوەی بەزۆر'
|
||||
Toggle Developer Tools: 'زامنی ئامرازەکانی گەشەپێدەر'
|
||||
Actual size: 'قەبارەی ڕاستەقینە'
|
||||
Zoom in: 'زووم کردنە ناوەوە'
|
||||
Zoom out: 'زووم کردنە دەرەوە'
|
||||
Toggle fullscreen: 'شاشەکەت پرکەرەوە'
|
||||
@ -30,9 +30,9 @@ Close: 'داخستن'
|
||||
Back: 'گەڕانەوە'
|
||||
Forward: 'چونەپێشەوە'
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: 'ڤێرژنی {versionNumber}
|
||||
ئێستا بەردەستە! کلیک بکە بۆ زانیاری زیاتر'
|
||||
Download From Site: 'دایبەزێنە لە سایتەکەوە'
|
||||
Version {versionNumber} is now available! Click for more details: 'ئێستا وەشانی {versionNumber}
|
||||
بەردەستە..بۆ زانیاری زۆرتر کرتە بکە'
|
||||
Download From Site: 'لە وێبگەوە دایگرە'
|
||||
A new blog is now available, {blogTitle}. Click to view more: 'بڵۆگێکی نوێ بەردەستە،
|
||||
{blogTitle}. کلیک بکە بۆ بینینی زیاتر'
|
||||
|
||||
@ -190,3 +190,8 @@ Profile:
|
||||
Removed {profile} from your profiles: 'سڕاوەتەوە لە پرۆفایلەکانت {profile}'
|
||||
Channel:
|
||||
Playlists: {}
|
||||
New Window: پەنجەرەی نوێ
|
||||
Go to page: بڕۆ بۆ {page}
|
||||
Preferences: هەڵبژاردەکان
|
||||
Are you sure you want to open this link?: دڵنیایت دەتەوێت ئەم بەستەرە بکەیتەوە؟
|
||||
Open New Window: کردنەوەی پەنجەرەیەکی نوێ
|
||||
|
@ -761,6 +761,8 @@ Video:
|
||||
YouTube-ом.
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Ћаскање
|
||||
уживо није доступно за овај стрим. Можда га је онемогућио аутор.
|
||||
Unhide Channel: Прикажи канал
|
||||
Hide Channel: Сакриј канал
|
||||
Tooltips:
|
||||
Subscription Settings:
|
||||
Fetch Feeds from RSS: 'Када је омогућено, FreeTube ће користити RSS уместо свог
|
||||
@ -982,3 +984,6 @@ Screenshot Error: Снимак екрана није успео. {error}
|
||||
Downloading has completed: „{videoTitle}“ је завршио преузимање
|
||||
Loop is now enabled: Понављање је сада омогућено
|
||||
Downloading failed: Дошло је до проблема при преузимању „{videoTitle}“
|
||||
Channel Hidden: '{channel} је додат на филтер канала'
|
||||
Go to page: Иди на {page}
|
||||
Channel Unhidden: '{channel} је уклоњен из филтера канала'
|
||||
|
@ -713,6 +713,8 @@ Video:
|
||||
Upcoming: 即将到来
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': 实时聊天对此音视频流不可用。上传者可能禁用了它。
|
||||
Pause on Current Video: 当前视频播完后不自动播放列表中下一视频
|
||||
Unhide Channel: 显示频道
|
||||
Hide Channel: 隐藏频道
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -936,3 +938,5 @@ Hashtag:
|
||||
Playlist will pause when current video is finished: 当前视频播完后播放列表会暂停
|
||||
Playlist will not pause when current video is finished: 当前视频播完后播放列表不会暂停
|
||||
Go to page: 转到页{page}
|
||||
Channel Hidden: '{channel} 频道已添加到频道过滤器'
|
||||
Channel Unhidden: 从频道过滤器删除了{channel} 频道
|
||||
|
@ -131,7 +131,7 @@ Settings:
|
||||
Preferred API Backend:
|
||||
Preferred API Backend: '偏好API伺服器'
|
||||
Local API: '本機 API'
|
||||
Invidious API: 'Invidious API(應用程式介面)'
|
||||
Invidious API: 'Invidious API'
|
||||
Video View Type:
|
||||
Video View Type: '影片觀看類別'
|
||||
Grid: '網格'
|
||||
@ -141,7 +141,7 @@ Settings:
|
||||
Default: '預設'
|
||||
Beginning: '片頭'
|
||||
Middle: '中間'
|
||||
End: '結尾'
|
||||
End: '片尾'
|
||||
Hidden: 隱藏
|
||||
Blur: 模糊
|
||||
'Invidious Instance (Default is https://invidious.snopyta.org)': 'Invidious實例(預設為
|
||||
@ -410,7 +410,7 @@ Settings:
|
||||
Hide Channels Disabled Message: 某些頻道被使用 ID 封鎖且無法處理。當這些 ID 更新時,功能將會被封鎖
|
||||
Hide Channels Already Exists: 頻道 ID 已存在
|
||||
Hide Channels API Error: 使用提供的 ID 擷取使用者時發生錯誤。請再次檢查 ID 是否正確。
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: 此變更需要重啟讓修改生效。重啟並且套用變更?
|
||||
The app needs to restart for changes to take effect. Restart and apply change?: 必須重新啟動應用程式以生效。重新啟動並套用變更嗎?
|
||||
Proxy Settings:
|
||||
Error getting network information. Is your proxy configured properly?: 取得網路資訊時發生錯誤。您的代理伺服器設定正確嗎?
|
||||
City: 城市
|
||||
@ -590,12 +590,12 @@ Channel:
|
||||
Releases: 發布
|
||||
This channel does not currently have any releases: 此頻道目前沒有任何發布
|
||||
Video:
|
||||
Open in YouTube: '在YouTube中開啟'
|
||||
Copy YouTube Link: '複製YouTube連結'
|
||||
Open YouTube Embedded Player: '開啟YouTube內嵌播放器'
|
||||
Copy YouTube Embedded Player Link: '複製YouTube內嵌播放器連結'
|
||||
Open in Invidious: '在Invidious中開啟'
|
||||
Copy Invidious Link: '複製Invidious連結'
|
||||
Open in YouTube: '在 YouTube 中開啟'
|
||||
Copy YouTube Link: '複製 YouTube 連結'
|
||||
Open YouTube Embedded Player: '開啟 YouTube 內嵌播放器'
|
||||
Copy YouTube Embedded Player Link: '複製 YouTube 內嵌播放器連結'
|
||||
Open in Invidious: '在 Invidious 中開啟'
|
||||
Copy Invidious Link: '複製 Invidious 連結'
|
||||
Views: '觀看'
|
||||
Watched: '已觀看'
|
||||
# As in a Live Video
|
||||
@ -661,10 +661,10 @@ Video:
|
||||
audio only: 僅音訊
|
||||
video only: 僅影片
|
||||
Download Video: 下載影片
|
||||
Copy Invidious Channel Link: 複製Invidious頻道連結
|
||||
Open Channel in Invidious: 在Invidious開啟頻道
|
||||
Copy YouTube Channel Link: 複製YouTube頻道連結
|
||||
Open Channel in YouTube: 在YouTube開啟頻道
|
||||
Copy Invidious Channel Link: 複製 Invidious 頻道連結
|
||||
Open Channel in Invidious: 在 Invidious 開啟頻道
|
||||
Copy YouTube Channel Link: 複製 YouTube 頻道連結
|
||||
Open Channel in YouTube: 在 YouTube 開啟頻道
|
||||
Started streaming on: '開始直播時間'
|
||||
Streamed on: 直播於
|
||||
Video has been removed from your saved list: 影片已從您的播放清單移除
|
||||
@ -722,6 +722,8 @@ Video:
|
||||
Upcoming: 即將到來
|
||||
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': 即時聊天在此串流不可用。其可能被上傳者停用了。
|
||||
Pause on Current Video: 暫停目前影片
|
||||
Unhide Channel: 顯示頻道
|
||||
Hide Channel: 隱藏頻道
|
||||
Videos:
|
||||
#& Sort By
|
||||
Sort By:
|
||||
@ -944,3 +946,6 @@ Hashtag:
|
||||
This hashtag does not currently have any videos: 此標籤目前沒有任何影片
|
||||
Playlist will pause when current video is finished: 當目前影片結束時,播放清單將會暫停
|
||||
Playlist will not pause when current video is finished: 當目前影片結束時,播放清單將不會暫停
|
||||
Channel Hidden: '{channel} 已新增至頻道過濾條件'
|
||||
Go to page: 到 {page}
|
||||
Channel Unhidden: '{channel} 已從頻道過濾條件移除'
|
||||
|
Loading…
Reference in New Issue
Block a user