Reuse actions.okay string

This commit is contained in:
Bnyro 2023-05-31 09:50:59 +02:00
commit 0300718377
56 changed files with 1034 additions and 844 deletions

105
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,105 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior include but are not limited to:
- The usage of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, threats, and personal or political attacks
- Harassment of any form
- Publishing others' private information, such as a physical or electronic address, without explicit permission from the individual
- Derailling conversations unnecessarily in a way that is not constructive, such as repeatedly posting off-topic comments whilest not in an off-topic channel
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at the contact section of the project README, or alternatively any admin of an official medium community. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private or public, written warning from community leaders, providing
clarity when necessary around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A written warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Kick / Temporary Ban
**Community Impact**: A serious or repeated violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A kick or temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -23,29 +23,29 @@
"stream-browserify": "3.0.0",
"vue": "3.3.4",
"vue-i18n": "9.2.2",
"vue-router": "4.2.1",
"vue-router": "4.2.2",
"xml-js": "1.6.11"
},
"devDependencies": {
"@iconify-json/fa6-brands": "1.1.11",
"@iconify-json/fa6-solid": "1.1.13",
"@intlify/vite-plugin-vue-i18n": "6.0.3",
"@unocss/preset-icons": "0.52.1",
"@unocss/preset-web-fonts": "0.52.1",
"@unocss/transformer-directives": "0.52.1",
"@unocss/transformer-variant-group": "0.52.1",
"@vitejs/plugin-legacy": "4.0.3",
"@unocss/preset-icons": "0.52.5",
"@unocss/preset-web-fonts": "0.52.5",
"@unocss/transformer-directives": "0.52.5",
"@unocss/transformer-variant-group": "0.52.5",
"@vitejs/plugin-legacy": "4.0.4",
"@vitejs/plugin-vue": "4.2.3",
"@vue/compiler-sfc": "3.3.4",
"eslint": "8.41.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-vue": "9.14.0",
"eslint-plugin-vue": "9.14.1",
"prettier": "2.8.8",
"unocss": "0.52.1",
"vite": "4.3.8",
"unocss": "0.52.5",
"vite": "4.3.9",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-pwa": "0.15.0"
"vite-plugin-pwa": "0.15.2"
},
"eslintConfig": {
"root": true,

View File

@ -216,8 +216,10 @@ b {
}
.input:focus {
@apply border-2 border-red-500 outline-none;
box-shadow: 0 0 15px rgba(239, 68, 68, var(--un-border-opacity));
@apply outline-red-500;
outline-style: solid;
outline-width: 2px;
box-shadow: 0 0 15px rgba(239, 68, 68, 1);
}
hr {

View File

@ -42,19 +42,7 @@
</div>
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="channel.description" class="whitespace-pre-wrap py-2 mx-1">
<span v-if="fullDescription" v-html="purifyHTML(rewriteDescription(channel.description))" />
<span v-html="purifyHTML(rewriteDescription(channel.description.slice(0, 100)))" v-else />
<span v-if="channel.description.length > 100 && !fullDescription">...</span>
<button
v-if="channel.description.length > 100"
class="hover:underline font-semibold text-neutral-500 block whitespace-normal"
@click="fullDescription = !fullDescription"
>
[{{ fullDescription ? $t("actions.show_less") : $t("actions.show_more") }}]
</button>
</div>
<CollapsableText :text="channel.description" />
<WatchOnYouTubeButton :link="`https://youtube.com/channel/${this.channel.id}`" />
@ -90,6 +78,7 @@ import ErrorHandler from "./ErrorHandler.vue";
import ContentItem from "./ContentItem.vue";
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
import CollapsableText from "./CollapsableText.vue";
export default {
components: {
@ -97,6 +86,7 @@ export default {
ContentItem,
WatchOnYouTubeButton,
LoadingIndicatorPage,
CollapsableText,
},
data() {
return {
@ -105,7 +95,6 @@ export default {
tabs: [],
selectedTab: 0,
contentItems: [],
fullDescription: false,
};
},
mounted() {

View File

@ -0,0 +1,28 @@
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="text" class="whitespace-pre-wrap py-2 mx-1">
<span v-if="showFullText" v-html="purifyHTML(rewriteDescription(text))" />
<span v-else v-html="purifyHTML(rewriteDescription(text.slice(0, 100)))" />
<span v-if="text.length > 100 && !showFullText">...</span>
<button
v-if="text.length > 100"
class="hover:underline font-semibold text-neutral-500 block whitespace-normal"
@click="showFullText = !showFullText"
>
[{{ showFullText ? $t("actions.show_less") : $t("actions.show_more") }}]
</button>
</div>
</template>
<script>
export default {
props: {
text: String,
},
data() {
return {
showFullText: false,
};
},
};
</script>

View File

@ -4,7 +4,7 @@
<h3 class="text-xl" v-text="message" />
<div class="ml-auto mt-8 flex gap-2 w-min">
<button class="btn" v-t="'actions.cancel'" @click="$emit('close')" />
<button class="btn" v-t="'actions.confirm'" @click="$emit('confirm')" />
<button class="btn" v-t="'actions.okay'" @click="$emit('confirm')" />
</div>
</div>
</ModalComponent>

View File

@ -1,27 +1,27 @@
<template>
<h1 v-t="'titles.feed'" class="font-bold text-center my-4" />
<button class="btn mr-2" @click="exportHandler">
<router-link to="/subscriptions">Subscriptions</router-link>
</button>
<div class="flex flex-wrap md:items-center flex-col md:flex-row gap-2 children:(flex gap-1 items-center)">
<span>
<a :href="getRssUrl">
<font-awesome-icon icon="rss" />
</a>
</span>
<label for="filters" class="ml-10 mr-2">
<label for="filters">
<strong v-text="`${$t('actions.filter')}:`" />
</label>
<select id="filters" v-model="selectedFilter" default="all" class="select w-auto" @change="onFilterChange()">
<select
id="filters"
v-model="selectedFilter"
default="all"
class="select flex-grow"
@change="onFilterChange()"
>
<option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`video.${filter}`" />
</select>
</span>
<label for="group-selector" class="ml-10 mr-2">
<span>
<label for="group-selector">
<strong v-text="`${$t('titles.channel_groups')}:`" />
</label>
<select id="group-selector" v-model="selectedGroupName" default="" class="select w-auto">
<select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
<option value="" v-t="`video.all`" />
<option
v-for="group in channelGroups"
@ -30,11 +30,20 @@
v-text="group.groupName"
/>
</select>
<span class="md:float-right">
<SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
</span>
<span class="md:ml-auto">
<SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
</span>
</div>
<hr />
<span class="flex gap-2">
<router-link class="btn" to="/subscriptions">Subscriptions</router-link>
<a :href="getRssUrl" class="btn">
<font-awesome-icon icon="rss" />
</a>
</span>
<hr />
<LoadingIndicatorPage :show-content="videosStore != null" class="video-grid">

View File

@ -1,14 +1,12 @@
<template>
<h1 class="font-bold text-center" v-t="'titles.history'" />
<div class="flex">
<div>
<div class="flex md:items-center gap-2 flex-col md:flex-row">
<button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
<button class="btn mx-3" v-t="'actions.export_to_json'" @click="exportHistory" />
</div>
<button class="btn" v-t="'actions.export_to_json'" @click="exportHistory" />
<div class="right-1">
<div class="ml-auto flex gap-1 items-center">
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
</div>
</div>

View File

@ -45,13 +45,13 @@ export default {
}
.modal-container {
@apply w-min m-auto px-8 bg-white p-6 rounded-xl min-w-[20vw] relative;
@apply w-min m-auto bg-white p-5 rounded-xl min-w-[20vw] relative;
}
.dark .modal-container {
@apply bg-dark-700;
}
.modal-container > button {
@apply absolute right-8 top-6;
@apply absolute right-2.5 top-1;
}
</style>

View File

@ -59,35 +59,41 @@
</ul>
</nav>
<!-- navigation bar for mobile devices -->
<ul
<div
v-if="showTopNav"
class="flex flex-col justify-center items-end mb-4 children:(my-0.5 mr-5)"
@click="showTopNav = false"
class="mobile-nav flex flex-col mb-4 children:(p-1 w-full border-b border-dark-100 flex items-center gap-1)"
>
<li v-if="shouldShowTrending">
<router-link v-t="'titles.trending'" to="/trending" />
</li>
<li>
<router-link v-t="'titles.preferences'" to="/preferences" />
</li>
<li v-if="shouldShowLogin">
<router-link v-t="'titles.login'" to="/login" />
</li>
<li v-if="shouldShowLogin">
<router-link v-t="'titles.register'" to="/register" />
</li>
<li v-if="shouldShowHistory">
<router-link v-t="'titles.history'" to="/history" />
</li>
<li>
<router-link v-t="'titles.playlists'" to="/playlists" />
</li>
<li v-if="!shouldShowTrending">
<router-link v-t="'titles.feed'" to="/feed" />
</li>
</ul>
<router-link v-if="shouldShowTrending" to="/trending">
<div class="i-fa6-solid:fire"></div>
<i18n-t keypath="titles.trending"></i18n-t>
</router-link>
<router-link to="/preferences">
<div class="i-fa6-solid:gear"></div>
<i18n-t keypath="titles.preferences"></i18n-t>
</router-link>
<router-link v-if="shouldShowLogin" to="/login">
<div class="i-fa6-solid:user"></div>
<i18n-t keypath="titles.login"></i18n-t>
</router-link>
<router-link v-if="shouldShowLogin" to="/register">
<div class="i-fa6-solid:user-plus"></div>
<i18n-t keypath="titles.register"></i18n-t>
</router-link>
<router-link v-if="shouldShowHistory" to="/history">
<div class="i-fa6-solid:clock-rotate-left"></div>
<i18n-t keypath="titles.history"></i18n-t>
</router-link>
<router-link to="/playlists">
<div class="i-fa6-solid:list"></div>
<i18n-t keypath="titles.playlists"></i18n-t>
</router-link>
<router-link v-if="!shouldShowTrending" to="/feed">
<div class="i-fa6-solid:play"></div>
<i18n-t keypath="titles.feed"></i18n-t>
</router-link>
</div>
<!-- search suggestions for mobile devices -->
<div class="mobile-search md:hidden mx-2 search-container">
<div class="w-full mb-2 md:hidden search-container">
<input
v-model="searchText"
class="input h-10 w-full"
@ -189,8 +195,7 @@ export default {
@apply absolute right-3 cursor-pointer rounded-full bg-[#ccc] w-4 h-4 text-center text-black opacity-50 hover:(opacity-70) text-size-[13px];
line-height: 1.05;
}
.mobile-search {
width: calc(100% - 1rem);
@apply mx-2;
.mobile-nav div {
@apply mx-1;
}
</style>

View File

@ -1,20 +1,21 @@
<template>
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
<LoadingIndicatorPage :show-content="playlist" v-show="!playlist.error">
<h1 class="text-center my-4" v-text="playlist.name" />
<LoadingIndicatorPage :show-content="playlist" v-show="!playlist?.error">
<h1 class="ml-1 mb-1 mt-4 text-3xl!" v-text="playlist.name" />
<div class="flex justify-between items-center">
<CollapsableText :text="playlist.description" />
<div class="flex justify-between items-center mt-1">
<div>
<router-link class="link" :to="playlist.uploaderUrl || '/'">
<router-link class="link flex items-center gap-3" :to="playlist.uploaderUrl || '/'">
<img :src="playlist.uploaderAvatar" loading="lazy" class="rounded-full" />
<strong v-text="playlist.uploader" />
</router-link>
</div>
<div>
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
<br />
<button class="btn mr-1" v-if="!isPipedPlaylist" @click="bookmarkPlaylist">
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" class="mr-2" />
<button class="btn mx-1" v-if="!isPipedPlaylist" @click="bookmarkPlaylist">
{{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
}}<font-awesome-icon class="ml-3" icon="bookmark" />
</button>
@ -52,6 +53,7 @@
<script>
import ErrorHandler from "./ErrorHandler.vue";
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
import CollapsableText from "./CollapsableText.vue";
import VideoItem from "./VideoItem.vue";
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
@ -61,6 +63,7 @@ export default {
VideoItem,
WatchOnYouTubeButton,
LoadingIndicatorPage,
CollapsableText,
},
data() {
return {

View File

@ -39,8 +39,26 @@
v-text="playlist.name"
/>
</router-link>
<button class="btn h-auto" @click="renamePlaylist(playlist.id)" v-t="'actions.rename_playlist'" />
<button class="btn h-auto" @click="showPlaylistEditModal(playlist)" v-t="'actions.edit_playlist'" />
<button class="btn h-auto ml-2" @click="playlistToDelete = playlist.id" v-t="'actions.delete_playlist'" />
<ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
<div class="flex flex-col gap-2">
<h2 v-t="'actions.edit_playlist'" />
<input
class="input"
type="text"
v-model="newPlaylistName"
:placeholder="$t('actions.playlist_name')"
/>
<input
class="input"
type="text"
v-model="newPlaylistDescription"
:placeholder="$t('actions.playlist_description')"
/>
<button class="btn ml-auto" @click="editPlaylist(playlist)" v-t="'actions.okay'" />
</div>
</ModalComponent>
<ConfirmModal
v-if="playlistToDelete == playlist.id"
:message="$t('actions.delete_playlist_confirm')"
@ -83,6 +101,7 @@
<script>
import ConfirmModal from "./ConfirmModal.vue";
import ModalComponent from "./ModalComponent.vue";
export default {
data() {
@ -90,6 +109,9 @@ export default {
playlists: [],
bookmarks: [],
playlistToDelete: null,
playlistToEdit: null,
newPlaylistName: "",
newPlaylistDescription: "",
};
},
mounted() {
@ -109,13 +131,20 @@ export default {
this.playlists = json;
});
},
renamePlaylist(id) {
const newName = prompt(this.$t("actions.new_playlist_name"));
if (!newName) return;
showPlaylistEditModal(playlist) {
this.newPlaylistName = playlist.name;
this.newPlaylistDescription = playlist.description;
this.playlistToEdit = playlist.id;
},
editPlaylist(selectedPlaylist) {
// save the new name and description since they could be overwritten during the http request
const newName = this.newPlaylistName;
const newDescription = this.newPlaylistDescription;
if (newName != selectedPlaylist.name) {
this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
method: "POST",
body: JSON.stringify({
playlistId: id,
playlistId: selectedPlaylist.id,
newName: newName,
}),
headers: {
@ -124,15 +153,26 @@ export default {
},
}).then(json => {
if (json.error) alert(json.error);
else {
this.playlists.forEach((playlist, index) => {
if (playlist.id == id) {
this.playlists[index].name = newName;
return;
}
else selectedPlaylist.name = newName;
});
}
if (newDescription != selectedPlaylist.description) {
this.fetchJson(this.authApiUrl() + "/user/playlists/description", null, {
method: "PATCH",
body: JSON.stringify({
playlistId: selectedPlaylist.id,
description: newDescription,
}),
headers: {
Authorization: this.getAuthToken(),
"Content-Type": "application/json",
},
}).then(json => {
if (json.error) alert(json.error);
else selectedPlaylist.description = newDescription;
});
}
this.playlistToEdit = null;
},
deletePlaylist(id) {
this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
@ -271,6 +311,6 @@ export default {
this.bookmarks.splice(index, 1);
},
},
components: { ConfirmModal },
components: { ConfirmModal, ModalComponent },
};
</script>

View File

@ -82,7 +82,7 @@ export default {
<style>
.suggestions-container {
@apply left-1/2 translate-x-[-50%] transform-gpu max-w-3xl w-full box-border p-y-1.25 z-10 lt-md:max-w-[calc(100%-0.5rem)] bg-gray-300;
@apply left-1/2 translate-x-[-50%] transform-gpu max-w-3xl w-full box-border z-10 lt-md:max-w-[calc(100%-0.5rem)] bg-gray-300;
}
.dark .suggestions-container {
@ -98,6 +98,6 @@ export default {
}
.suggestion {
@apply p-y-1;
@apply p-1;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<label for="ddlSortBy" v-t="'actions.sort_by'" />
<select id="ddlSortBy" v-model="selectedSort" class="select w-auto">
<select id="ddlSortBy" v-model="selectedSort" class="select flex-grow">
<option v-for="(value, key) in options" v-t="`actions.${key}`" :key="key" :value="value" />
</select>
</template>

View File

@ -43,13 +43,13 @@
<div class="font-bold mt-2 text-2xl break-words" v-text="video.title" />
<div class="flex flex-wrap mt-3 mb-3">
<!-- views / date -->
<div class="flex flex-auto children:ml-2">
<div class="flex flex-auto gap-2">
<span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
<span> | </span>
<span v-text="uploadDate" />
</div>
<!-- Likes/dilikes -->
<div class="flex children:mr-2">
<div class="flex gap-2">
<template v-if="video.likes >= 0">
<div class="flex items-center">
<div class="i-fa6-solid:thumbs-up" />
@ -68,7 +68,7 @@
</div>
</div>
<!-- Channel info & options flex container -->
<div class="flex">
<div class="flex flex-wrap gap-1">
<!-- Channel Image & Info -->
<div class="flex items-center">
<img :src="video.uploaderAvatar" alt="" loading="lazy" class="rounded-full" />
@ -78,19 +78,6 @@
<!-- Verified Badge -->
<font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
</div>
<div class="flex relative ml-auto children:mr-1 items-center">
<button class="btn" v-if="authenticated" @click="showModal = !showModal">
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
</button>
<button
class="btn"
@click="subscribeHandler"
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(video.uploaderSubscriberCount) },
}"
/>
</div>
<PlaylistAddModal v-if="showModal" :video-id="getVideoId()" @close="showModal = !showModal" />
<ShareModal
v-if="showShareModal"
@ -100,8 +87,20 @@
:playlist-index="index"
@close="showShareModal = !showShareModal"
/>
<div class="flex">
<div class="self-center children:mr-1 my-1">
<div class="flex flex-wrap gap-1 ml-auto">
<!-- Subscribe Button -->
<button class="btn flex items-center" v-if="authenticated" @click="showModal = !showModal">
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
</button>
<button
class="btn"
@click="subscribeHandler"
v-t="{
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
args: { count: numberFormat(video.uploaderSubscriberCount) },
}"
/>
<div class="flex flex-wrap gap-1">
<!-- RSS Feed button -->
<a
aria-label="RSS feed"
@ -110,18 +109,22 @@
v-if="video.uploaderUrl"
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
target="_blank"
class="btn flex-col"
class="btn flex items-center"
>
<font-awesome-icon icon="rss" />
<font-awesome-icon class="mx-1.5" icon="rss" />
</a>
<WatchOnYouTubeButton :link="`https://youtu.be/${getVideoId()}`" />
<!-- Share Dialog -->
<button class="btn" @click="showShareModal = !showShareModal">
<button class="btn flex items-center" @click="showShareModal = !showShareModal">
<i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
<font-awesome-icon class="mx-1.5" icon="fa-share" />
</button>
<!-- LBRY -->
<a v-if="video.lbryId" :href="'https://odysee.com/' + video.lbryId" class="btn">
<a
v-if="video.lbryId"
:href="'https://odysee.com/' + video.lbryId"
class="btn flex items-center"
>
<i18n-t keypath="player.watch_on" tag="strong">LBRY</i18n-t>
</a>
<!-- listen / watch toggle -->
@ -129,9 +132,9 @@
:to="toggleListenUrl"
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
class="btn flex-col"
class="btn flex items-center"
>
<font-awesome-icon :icon="isListening ? 'tv' : 'headphones'" />
<font-awesome-icon class="mx-1.5" :icon="isListening ? 'tv' : 'headphones'" />
</router-link>
</div>
</div>

View File

@ -107,7 +107,6 @@
"follow_link": "اتبع الرابط",
"copy_link": "نسخ الرابط",
"time_code": "رمز الوقت (بالثواني)",
"rename_playlist": "إعادة تسمية قائمة التشغيل",
"new_playlist_name": "اسم قائمة تشغيل جديد",
"show_chapters": "الفصول",
"store_search_history": "حفظ سجل البحث",

View File

@ -100,7 +100,6 @@
"instance_auth_selection": "Təsdiqləmə Nümunəsi Seçimi",
"clone_playlist": "Oynatma Siyahısın Klonla",
"clone_playlist_success": "Uğurla klonlandı!",
"rename_playlist": "Oynatma siyahısın yenidən adlandır",
"time_code": "Vaxt kodu (saniyələrlə)",
"store_search_history": "Axtarış tarixçəsini saxla",
"documentation": "Sənədləşdirmə",

View File

@ -99,7 +99,6 @@
"clone_playlist": "Клониране на плейлист",
"clone_playlist_success": "Успешно клониране!",
"backup_preferences": "Архивиране на настройките",
"rename_playlist": "Преименуване на плейлиста",
"new_playlist_name": "Ново име на плейлиста",
"back_to_home": "Обратно към начална страница",
"status_page": "Статус",

View File

@ -77,7 +77,6 @@
"minimize_chapters_default": "Smanjite poglavlja po zadanom",
"show_watch_on_youtube": "Prikaži „Gledaj na YouTube-u” dugme",
"different_auth_instance": "Koristite drugu instancu za autentifikaciju",
"rename_playlist": "Preimenuj listu izvođenja",
"new_playlist_name": "Novi naziv liste izvođenja",
"with_timecode": "Podijelite s vremenskim kodom",
"piped_link": "Piped poveznica",

View File

@ -103,7 +103,6 @@
"time_code": "Moment (en segons)",
"copy_link": "Copiar l'enllaç",
"follow_link": "Vés a l'enllaç",
"rename_playlist": "Canviar el nom de la llista de reproducció",
"new_playlist_name": "Nom nou de la llista de reproducció",
"store_search_history": "Emmagatzema l'historial de cerca",
"instance_donations": "Donacions a instàncies",

View File

@ -104,7 +104,6 @@
"follow_link": "Otevřít odkaz",
"copy_link": "Kopírovat odkaz",
"time_code": "Časový kód (v sekundách)",
"rename_playlist": "Přejmenovat playlist",
"new_playlist_name": "Nový název playlistu",
"show_chapters": "Kapitoly",
"store_search_history": "Ukládat historii vyhledávání",

View File

@ -75,7 +75,6 @@
"instance_auth_selection": "Auswahl der Autentifizierungsinstanz",
"clone_playlist": "Playlist duplizieren",
"clone_playlist_success": "Erfolgreich dupliziert!",
"rename_playlist": "Playlist umbenennen",
"new_playlist_name": "Neuer Name der Playlist",
"piped_link": "Piped-Link",
"download_as_txt": "Als .txt herunterladen",

View File

@ -111,7 +111,9 @@
"backup_preferences": "Backup preferences",
"restore_preferences": "Restore preferences",
"back_to_home": "Back to home",
"rename_playlist": "Rename playlist",
"edit_playlist": "Edit playlist",
"playlist_name": "Playlist name",
"playlist_description": "Playlist description",
"new_playlist_name": "New playlist name",
"share": "Share",
"with_timecode": "Share with time code",
@ -137,7 +139,7 @@
"create_group": "Create group",
"group_name": "Group name",
"cancel": "Cancel",
"confirm": "Confirm"
"okay": "Okay"
},
"comment": {
"pinned_by": "Pinned by {author}",

View File

@ -55,7 +55,6 @@
"hide_replies": "Kaŝi Respondojn",
"add_to_playlist": "Aldoni al ludlisto",
"delete_playlist": "Forigi Ludliston",
"rename_playlist": "Renomi ludliston",
"download_as_txt": "Elŝuti kiel .txt",
"piped_link": "Piped-ligilo",
"copy_link": "Kopii ligilon",

View File

@ -103,7 +103,6 @@
"invalidate_session": "Cerrar sesión en todos los dispositivos",
"instance_auth_selection": "Selección de la Instancia de Autentificación",
"download_as_txt": "Descargar como .txt",
"rename_playlist": "Cambiar el nombre de la lista de reproducción",
"new_playlist_name": "Nuevo nombre de la lista de reproducción",
"share": "Compartir",
"with_timecode": "Compartir con código de tiempo",

View File

@ -101,7 +101,6 @@
"show_watch_on_youtube": "Näytä Katso YouTubessa -painike",
"different_auth_instance": "Käytä eri instanssia todennukseen",
"download_as_txt": "Lataa .txt-tiedostona",
"rename_playlist": "Nimeä soittolista uudelleen",
"show_chapters": "Luvut",
"minimize_comments": "Minimoi kommentit",
"minimize_comments_default": "Minimoi kommentit oletusarvoisesti",

View File

@ -103,7 +103,6 @@
"piped_link": "Lien vers Piped",
"follow_link": "Ouvrir le lien",
"time_code": "Horodatage (en secondes)",
"rename_playlist": "Renommer la liste de lecture",
"new_playlist_name": "Nouveau nom de la liste de lecture",
"show_chapters": "Chapitres",
"store_search_history": "Mémoriser l'historique de recherche",

View File

@ -104,7 +104,6 @@
"search": "חיפוש (Ctrl+K)",
"loop_this_video": "ניגון הסרטון בלולאה",
"minimize_recommendations": "מזעור המלצות",
"rename_playlist": "שינוי שם רשימת נגינה",
"new_playlist_name": "שם לרשימת נגינה חדשה",
"show_chapters": "פרקים",
"skip_intro": "דילוג על הפוגה/הנפשת הקדמה",

View File

@ -113,7 +113,6 @@
"confirm_reset_preferences": "Stvarno želiš resetirati tvoje postavke?",
"backup_preferences": "Spremi sigurnosnu kopiju postavki",
"with_timecode": "Dijeli s vremenskim kodom",
"rename_playlist": "Preimenuj popis snimaka",
"new_playlist_name": "Ime novog popisa snimaka",
"share": "Dijeli",
"show_chapters": "Poglavlja",
@ -140,7 +139,8 @@
"autoplay_next_countdown": "Standardno odbrojavanje do sljedećeg videa (u sekundama)",
"dismiss": "Odbaci",
"create_group": "Stvori grupu",
"group_name": "Ime grupe"
"group_name": "Ime grupe",
"auto_display_captions": "Automatski prikaži titlove"
},
"player": {
"watch_on": "Gledaj na {0}"

View File

@ -92,7 +92,6 @@
"clone_playlist_success": "Sikeresen klónozva!",
"reset_preferences": "Alaphelyzetbe állítás",
"restore_preferences": "Beállítások betöltése fájlból",
"rename_playlist": "Átnevez",
"instance_donations": "Szerver adományozások",
"piped_link": "Piped link",
"time_code": "Idő kód (másodpercekben)",

View File

@ -76,7 +76,6 @@
"confirm_reset_preferences": "Իսկապե՞ս ուզում եք վերակայել ձեր նախապատվությունները:",
"backup_preferences": "Պահուստային նախապատվություններ",
"restore_preferences": "Վերականգնել նախապատվությունները",
"rename_playlist": "Վերանվանել տեսացանկը",
"new_playlist_name": "Տեսացանկի նոր անվանում",
"follow_link": "Հետևել հղմանը",
"instance_donations": "Օրինակների նվիրատվություններ",

View File

@ -100,7 +100,6 @@
"restore_preferences": "Pulihkan preferensi",
"confirm_reset_preferences": "Apakah Anda yakin ingin mengatur ulang preferensi Anda?",
"backup_preferences": "Cadangkan preferensi",
"rename_playlist": "Ubah nama daftar putar",
"new_playlist_name": "Nama daftar putar baru",
"share": "Bagikan",
"with_timecode": "Bagikan dengan kode waktu",

View File

@ -12,7 +12,9 @@
"account": "Reikningur",
"instance": "Tilvik",
"livestreams": "Útsendingar í beinni",
"channels": "Rásir"
"channels": "Rásir",
"bookmarks": "Bókamerki",
"channel_groups": "Rásarhópar"
},
"actions": {
"sort_by": "Raða eftir:",
@ -94,7 +96,6 @@
"instance_donations": "Framlög til netþjóns",
"status_page": "Staða",
"source_code": "Frumkóði",
"rename_playlist": "Endurnefna spilunarlista",
"new_playlist_name": "Nýtt heiti spilunarlista",
"share": "Deila",
"with_timecode": "Deilа með tímakóða",

View File

@ -88,7 +88,6 @@
"follow_link": "Apri il collegamento",
"with_timecode": "Condividi con marca temporale",
"new_playlist_name": "Nuovo nome dalla playlist",
"rename_playlist": "Rinomina la playlist",
"show_chapters": "Capitoli",
"store_search_history": "Memorizza la cronologia delle ricerche",
"status_page": "Stato",

View File

@ -17,7 +17,7 @@
"channel_groups": "グループ"
},
"player": {
"watch_on": "{0}で視聴"
"watch_on": "{0}で視聴"
},
"actions": {
"subscribe": "チャンネル登録 - {count}",
@ -29,13 +29,13 @@
"channel_name_asc": "チャンネル名 (AからZ)",
"channel_name_desc": "チャンネル名 (ZからA)",
"back": "戻る",
"uses_api_from": "API使用元 ",
"uses_api_from": "API 提供元 ",
"enable_sponsorblock": "SponsorBlock を有効化",
"skip_sponsors": "広告をスキップ",
"skip_intro": "休止時間/導入アニメをスキップ",
"skip_outro": "終了シーン/クレジットをスキップ",
"skip_preview": "プレビュー/要約をスキップ",
"skip_interaction": "チャンネル登録など操作を求める自己宣伝をスキップ",
"skip_interaction": "登録など操作を頼む自己宣伝をスキップ",
"skip_self_promo": "無報酬/自己の宣伝をスキップ",
"skip_non_music": "音楽: 非音楽部分をスキップ",
"theme": "テーマ",
@ -43,14 +43,14 @@
"dark": "ダーク",
"light": "ライト",
"autoplay_video": "動画を自動再生",
"audio_only": "音声のみ",
"audio_only": "音声のみのモード",
"default_quality": "標準の画質",
"buffering_goal": "バッファリング目標値 (秒)",
"country_selection": "国の選択",
"default_homepage": "ホームに表示するページ",
"show_comments": "コメントを表示",
"minimize_description_default": "最初から説明を最小化",
"store_watch_history": "再生履歴を保存する",
"store_watch_history": "再生履歴を保存",
"language_selection": "言語の選択",
"instances_list": "インスタンス一覧",
"enabled_codecs": "コーデックの有効化 (複数選択)",
@ -68,7 +68,7 @@
"minimize_recommendations": "おすすめを最小化",
"show_recommendations": "おすすめを見る",
"disable_lbry": "ストリーミングのLBRYを無効化",
"enable_lbry_proxy": "LBRYプロキシをオン",
"enable_lbry_proxy": "LBRYにプロキシを使用",
"view_ssl_score": "SSLの評価を表示",
"search": "検索 (Ctrl+K)",
"filter": "フィルター",
@ -87,8 +87,8 @@
"show_markers": "プレイヤーに目印の区切りを表示",
"select_playlist": "再生リストを選択",
"delete_playlist_confirm": "再生リストを削除しますか?",
"delete_account": "アカウントを削除する",
"store_search_history": "検索履歴を保存する",
"delete_account": "アカウントを削除",
"store_search_history": "検索履歴を保存",
"show_chapters": "チャプター",
"status_page": "状態",
"source_code": "ソースコード",
@ -96,15 +96,15 @@
"minimize_comments": "コメントを最小化",
"share": "共有",
"with_timecode": "時間指定で共有",
"different_auth_instance": "認証に別のインスタンスを使う",
"different_auth_instance": "認証に別のインスタンスを使う",
"download_as_txt": ".txtでダウンロード",
"logout": "このデバイスでログアウト",
"logout": "この端末からログアウト",
"minimize_recommendations_default": "最初からおすすめを最小化",
"hide_watched": "再生済みの動画をフィードに表示しない",
"minimize_chapters_default": "最初からチャプターを最小化",
"show_watch_on_youtube": "「YouTubeで見る」ボタンを表示する",
"invalidate_session": "すべてのデバイスでログアウトする",
"instance_auth_selection": "認証インスタンスの選択",
"show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
"invalidate_session": "すべての端末からログアウト",
"instance_auth_selection": "認証インスタンスの選択",
"clone_playlist_success": "複製に成功しました!",
"backup_preferences": "設定をバックアップ",
"restore_preferences": "設定を復元",
@ -114,7 +114,6 @@
"documentation": "ドキュメント",
"reset_preferences": "設定を初期化",
"confirm_reset_preferences": "設定をリセットしますか?",
"rename_playlist": "再生リスト名を変更する",
"piped_link": "Pipedリンク",
"new_playlist_name": "新しい再生リスト名",
"follow_link": "リンクを開く",
@ -147,7 +146,7 @@
"instance_locations": "インスタンスの場所",
"has_cdn": "CDNの有無",
"ssl_score": "SSLの評価",
"registered_users": "登録ユーザー数",
"registered_users": "登録利用者数",
"version": "バージョン",
"up_to_date": "最新?"
},
@ -184,8 +183,8 @@
"cannot_copy": "コピーできません!",
"preferences_note": "注意: 設定は、お使いのブラウザの保存領域に保存されます。ブラウザのデータを削除すると初期化されます。",
"local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか",
"register_no_email_note": "Eメールアドレスをユーザー名として使用することは推奨されていません。それでも続行しますか?",
"next_video_countdown": "{0} 秒後に次の動画を再生"
"register_no_email_note": "ユーザー名としてのメールアドレスの使用は推奨しません。それでも続けますか?",
"next_video_countdown": "{0} 秒後に次の動画"
},
"subscriptions": {
"subscribed_channels_count": "チャンネル登録: {0}"

View File

@ -98,7 +98,6 @@
"backup_preferences": "Ḥrez ismenyifen",
"restore_preferences": "Err-d ismenyifen",
"back_to_home": "Uɣal ɣer ugejdan",
"rename_playlist": "Beddel isem i tebdart n tɣuri",
"follow_link": "Ḍfer aseɣwen",
"show_chapters": "Ixfawen",
"show_watch_on_youtube": "Sken taqeffalt Wali ɣef YouTube",

View File

@ -70,7 +70,6 @@
"show_chapters": "챕터",
"download_as_txt": ".txt로 다운로드",
"new_playlist_name": "새 재생목록 이름",
"rename_playlist": "재생목록 이름 변경",
"share": "공유",
"copy_link": "링크 복사",
"time_code": "시작 시간 (초)",

View File

@ -80,7 +80,6 @@
"reply_count": "{count} atsakymai",
"show_chapters": "Skirsniai",
"piped_link": "Piped nuoroda",
"rename_playlist": "Pervardyti grojaraštį",
"follow_link": "Sekti nuorodą",
"store_search_history": "Išsaugoti paieškos istoriją",
"hide_watched": "Slėpti žiūrėtus vaizdo įrašus sklaidos kanale",

View File

@ -81,7 +81,6 @@
"confirm_reset_preferences": "Tilbakestill alle innstillingene?",
"restore_preferences": "Gjenopprett innstillinger",
"show_chapters": "Kapitler",
"rename_playlist": "Gi spillelisten ny navn",
"new_playlist_name": "Nytt spillelistenavn",
"share": "Del",
"with_timecode": "Del med tidskode",

View File

@ -80,7 +80,6 @@
"instance_auth_selection": "Selectie authenticatie-instantie",
"clone_playlist": "Afspeellijst dupliceren",
"download_as_txt": "Downloaden als .txt",
"rename_playlist": "Afspeellijst hernoemen",
"new_playlist_name": "Nieuwe afspeellijstnaam",
"share": "Delen",
"documentation": "Documentatie",

View File

@ -98,7 +98,6 @@
"invalidate_session": "Se desconnectar de totes los aparelhs",
"different_auth_instance": "Utilizar una instància diferenta per lautentificacion",
"back_to_home": "Tornar a lacuèlh",
"rename_playlist": "Renomenar la lista de lectura",
"new_playlist_name": "Nom novèl de la lista de lectura",
"with_timecode": "Partejar amb còdi orari",
"piped_link": "Ligam cap a Piped",

View File

@ -35,7 +35,6 @@
"show_recommendations": "ସୁପାରିଶଗୁଡିକ ଦେଖାନ୍ତୁ",
"disable_lbry": "ଷ୍ଟ୍ରିମିଂ ପାଇଁ LBRY ଅକ୍ଷମ କରନ୍ତୁ",
"search": "ସନ୍ଧାନ କରନ୍ତୁ (Ctrl+K)",
"rename_playlist": "ପ୍ଲେ ଲିଷ୍ଟର ନାମ ପରିବର୍ତ୍ତନ କରନ୍ତୁ",
"new_playlist_name": "ନୂତନ ପ୍ଲେଲିଷ୍ଟ ନାମ",
"channel_name_asc": "ସ୍ରୋତ ର ନାମ (A-Z)",
"least_recent": "ସର୍ବନିମ୍ନ ସାମ୍ପ୍ରତିକ",

View File

@ -102,7 +102,6 @@
"source_code": "Kod źródłowy",
"show_chapters": "Rozdziały",
"minimize_chapters_default": "Ukryj rozdziały",
"rename_playlist": "Zmień nazwę playlisty",
"follow_link": "Otwórz link",
"minimize_comments_default": "Ukryj sekcję komentarzy",
"minimize_comments": "Ukryj komentarze",

View File

@ -110,7 +110,6 @@
"new_playlist_name": "Novo nome da lista de reprodução",
"minimize_comments": "Minimizar Comentários",
"back_to_home": "Voltar ao início",
"rename_playlist": "Renomear",
"copy_link": "Copiar ligação",
"time_code": "Código de tempo (em segundos)",
"minimize_comments_default": "Minimizar Comentários por defeito",
@ -130,7 +129,8 @@
"dismiss": "Ignorar",
"autoplay_next_countdown": "Predefinição Contagem decrescente até ao próximo vídeo (em segundos)",
"create_group": "Criar grupo",
"group_name": "Nome do grupo"
"group_name": "Nome do grupo",
"auto_display_captions": "Visualização automática de legendas"
},
"preferences": {
"instance_name": "Nome da Instância",

View File

@ -87,7 +87,6 @@
"restore_preferences": "Restaurar preferências",
"back_to_home": "Voltar ao início",
"share": "Compartilhar",
"rename_playlist": "Renomear playlist",
"new_playlist_name": "Novo nome da playlist",
"with_timecode": "Compartilhar com código de tempo",
"piped_link": "Link do Piped",

View File

@ -93,7 +93,6 @@
"invalidate_session": "Terminar sessão em todos os aparelhos",
"clone_playlist": "Clonar Lista de Reprodução",
"clone_playlist_success": "Clonada com sucesso!",
"rename_playlist": "Renomear",
"restore_preferences": "Restaurar configurações",
"confirm_reset_preferences": "Tem a certeza que quer redefinir as suas configurações?",
"new_playlist_name": "Novo nome da lista de reprodução",
@ -130,7 +129,8 @@
"autoplay_next_countdown": "Predefinição Contagem decrescente até ao próximo vídeo (em segundos)",
"dismiss": "Ignorar",
"create_group": "Criar grupo",
"group_name": "Nome do grupo"
"group_name": "Nome do grupo",
"auto_display_captions": "Visualização automática de legendas"
},
"comment": {
"pinned_by": "Afixado por {author}",

View File

@ -59,7 +59,6 @@
"clone_playlist_success": "Clonată cu succes!",
"reset_preferences": "Resetați preferințele",
"confirm_reset_preferences": "Sunteți sigur că doriți să vă resetați preferințele?",
"rename_playlist": "Redenumiți playlist-ul",
"new_playlist_name": "Numele playlist-ului nou",
"share": "Distribuiți",
"follow_link": "Urmați link-ul",

View File

@ -97,7 +97,6 @@
"clone_playlist": "Клонировать плейлист",
"clone_playlist_success": "Успешно клонировано!",
"show_chapters": "Главы",
"rename_playlist": "Переименовать плейлист",
"new_playlist_name": "Новое название плейлиста",
"share": "Поделиться",
"with_timecode": "Поделиться с таймкодом",

View File

@ -81,7 +81,6 @@
"backup_preferences": "සැකසුම් උපස්ථ කරන්න",
"restore_preferences": "සැකසුම් නැවත පිහිටුවන්න",
"back_to_home": "ආපසු මුල් පිටුවට",
"rename_playlist": "වාදන ලැයිස්තුව නැවත නම් කරන්න",
"share": "බෙදාගන්න",
"with_timecode": "කාල කේතය සමඟ බෙදා ගන්න",
"piped_link": "පයිප්ඩ් සබැඳිය",

View File

@ -72,7 +72,6 @@
"view_ssl_score": "Zobraziť SSL skóre",
"filter": "Filter",
"delete_playlist_video_confirm": "Odstrániť video zo zoznamu?",
"rename_playlist": "Premenovať zoznam skladieb",
"new_playlist_name": "Nový názov zoznamu skladieb",
"share": "Zdieľať",
"follow_link": "Nasledujte odkaz",

View File

@ -100,7 +100,6 @@
"status_page": "Статус",
"instance_donations": "Донације инстанци",
"show_chapters": "Поглавља",
"rename_playlist": "Преименуј плејлисту",
"with_timecode": "Подели са временским кодом",
"piped_link": "Piped веза",
"back_to_home": "Врати се на почетну",

View File

@ -87,7 +87,6 @@
"with_timecode": "Zaman Koduyla Paylaş",
"piped_link": "Piped Bağlantısı",
"share": "Paylaş",
"rename_playlist": "Oynatma Listesini Yeniden Adlandır",
"new_playlist_name": "Yeni Oynatma Listesi Adı",
"show_chapters": "Bölümler",
"store_search_history": "Arama Geçmişini Sakla",

View File

@ -79,7 +79,6 @@
"logout": "Вийти з цього пристрою",
"backup_preferences": "Налаштування резервного копіювання",
"download_as_txt": "Завантажити як .txt",
"rename_playlist": "Перейменувати список відтворення",
"show_chapters": "Розділи",
"invalidate_session": "Вийти з усіх пристроїв",
"clone_playlist": "Клонувати список відтворення",

View File

@ -87,7 +87,6 @@
"share": "分享",
"with_timecode": "用时间码分享",
"time_code": "时间码(单位:秒)",
"rename_playlist": "重命名播放列表",
"new_playlist_name": "新播放列表名",
"show_chapters": "章节",
"store_search_history": "保存搜索历史",

View File

@ -64,7 +64,6 @@
"download_as_txt": "以 .txt 下載",
"share": "分享",
"new_playlist_name": "播放清單的新名稱",
"rename_playlist": "重新命名播放清單",
"reset_preferences": "重設偏好設定",
"confirm_reset_preferences": "確定要重設偏好設定嗎?",
"backup_preferences": "備份偏好設定",

1298
yarn.lock

File diff suppressed because it is too large Load Diff