This commit is contained in:
Bnyro 2022-09-08 23:11:12 +02:00
commit c8d7ceaea4
36 changed files with 1046 additions and 362 deletions

View file

@ -18,7 +18,7 @@ jobs:
cache: "yarn"
- run: yarn install --prefer-offline
- run: yarn build && sed -i 's/fonts.gstatic.com/fonts.kavin.rocks/g' dist/assets/*.css && cp dist/index.html dist/ipfs-404.html
- uses: aquiladev/ipfs-action@v0.3.0-alpha.1
- uses: aquiladev/ipfs-action@v0.3.1-alpha.2
id: ipfs-add
with:
path: ./dist

View file

@ -127,7 +127,8 @@ Contributions in any other form are also welcomed.
# Made with Piped
- [Yattee](https://github.com/yattee/yattee) - an alternative frontend for YouTube, for IOS.
- [LibreTube](https://github.com/Libre-tube/LibreTube) [WIP] - an alternative frontend for YouTube, for Android.
- [LibreTube](https://github.com/Libre-tube/LibreTube) - an alternative frontend for YouTube, for Android.
- [Hyperpipe](https://codeberg.org/Hyperpipe/Hyperpipe) - an alternative privacy respecting frontend for YouTube Music.
## YourKit

View file

@ -9,37 +9,42 @@
"lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@fortawesome/vue-fontawesome": "^3.0.1",
"buffer": "^6.0.3",
"dompurify": "^2.3.12",
"hotkeys-js": "^3.9.4",
"dompurify": "^2.4.0",
"hotkeys-js": "^3.10.0",
"javascript-time-ago": "^2.5.7",
"mux.js": "^6.2.0",
"shaka-player": "4.2.0",
"shaka-player": "4.2.1",
"stream": "^0.0.2",
"vue": "^3.2.37",
"vue": "^3.2.38",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.4",
"vue-router": "^4.1.5",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@iconify/json": "^2.1.103",
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@unocss/preset-icons": "^0.45.13",
"@unocss/preset-web-fonts": "^0.45.7",
"@unocss/transformer-directives": "^0.45.7",
"@unocss/transformer-variant-group": "^0.45.13",
"@vitejs/plugin-legacy": "^1.8.2",
"@vitejs/plugin-vue": "^2.3.4",
"@vue/compiler-sfc": "3.2.37",
"@vue/compiler-sfc": "3.2.38",
"babel-eslint": "^10.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.3.0",
"eslint-plugin-vue": "^9.4.0",
"prettier": "^2.7.1",
"unocss": "^0.45.18",
"vite": "^2.9.14",
"vite-plugin-eslint": "^1.7.0",
"vite-plugin-pwa": "^0.12.3",
"vite-plugin-windicss": "^1.8.7"
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-pwa": "^0.12.7"
},
"eslintConfig": {
"root": true,

View file

@ -102,11 +102,11 @@ b {
}
.video-grid {
@apply grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 col-auto <md:gap-x-2.5 md:gap-x-1vw gap-y-1.5;
@apply grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 col-auto lt-md:gap-x-2.5 md:gap-x-1vw gap-y-1.5;
}
.btn {
@apply h-full py-2 <md:(px-2) md:(px-4) rounded cursor-pointer;
@apply h-full py-2 lt-md:px-2 md:px-4 rounded cursor-pointer;
}
.reset {
@ -114,7 +114,7 @@ b {
}
.auto {
@apply dark:(text-white bg-dark-900);
@apply @dark:(text-white bg-dark-900);
}
.dark {
@ -145,13 +145,18 @@ b {
.auto .input,
.auto .select,
.auto .btn {
@apply dark:(text-gray-400 bg-dark-400);
@apply @dark:(text-gray-400 bg-dark-400);
}
.input {
@apply pl-2.5;
}
.input:focus {
@apply border-2 border-red-500 outline-none;
box-shadow: 0 0 15px rgba(239, 68, 68, var(--un-border-opacity));
}
hr {
@apply !mt-2 !mb-3 border-gray-300;
}
@ -161,7 +166,7 @@ hr {
}
.auto hr {
@apply dark:border-dark-100;
@apply @dark:border-dark-100;
}
h1,
@ -194,7 +199,7 @@ h2 {
}
.auto .link {
@apply dark:hover:(text-gray-300 underline underline-gray-300);
@apply @dark:hover:(text-gray-300 underline underline-gray-300);
}
.dark .link-secondary {
@ -202,7 +207,7 @@ h2 {
}
.auto .link-secondary {
@apply dark:(text-gray-300 hover:(text-gray-400 underline underline-gray-400));
@apply @dark:(text-gray-300 hover:(text-gray-400 underline underline-gray-400));
}
.line {

View file

@ -1,6 +1,6 @@
<template>
<!-- desktop view -->
<div v-if="!mobileLayout" class="flex-col overflow-y-scroll max-h-75vh min-h-64 <lg:hidden">
<div v-if="!mobileLayout" class="flex-col overflow-y-scroll max-h-75vh min-h-64 lt-lg:hidden">
<h2 class="mb-2 bg-gray-500/50 p-2" aria-label="chapters" title="chapters">
{{ $t("video.chapters") }} ({{ chapters.length }})
</h2>
@ -45,16 +45,17 @@
}
.chapter {
@apply cursor-pointer self-center p-2.5;
img {
@apply w-full h-full;
}
.chapter img {
@apply w-full h-full;
}
.chapter-vertical {
@apply cursor-pointer self-center p-2.5;
img {
}
.chapter-vertical img {
@apply w-3/10 h-3/10;
}
}
.chapter-vertical:hover {
@apply bg-gray-500;
}

View file

@ -24,8 +24,8 @@
<div class="comment-meta text-sm mb-1.5" v-text="comment.commentedTime" />
</div>
<div class="whitespace-pre-wrap" v-text="comment.commentText" />
<div class="comment-footer mt-1">
<font-awesome-icon icon="thumbs-up" />
<div class="comment-footer mt-1 flex">
<div class="i-fa-solid:thumbs-up" />
<span class="ml-1" v-text="numberFormat(comment.likeCount)" />
<font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
</div>

View file

@ -0,0 +1,42 @@
<template>
<div class="modal">
<div>
<div class="modal-container">
<slot></slot>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
window.addEventListener("keydown", this.handleKeyDown);
},
unmounted() {
window.removeEventListener("keydown", this.handleKeyDown);
},
methods: {
handleKeyDown(event) {
if (event.code === "Escape") {
this.$emit("close");
} else return;
event.preventDefault();
},
},
};
</script>
<style scoped>
.modal {
@apply fixed z-50 top-0 left-0 w-full h-full bg-dark-900 bg-opacity-80 transition-opacity table;
}
.modal > div {
@apply table-cell align-middle;
}
.modal-container {
@apply w-min m-auto px-8 bg-dark-700 p-6 rounded-xl min-w-[20vw];
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<nav class="flex flex-wrap items-center justify-center px-2 sm:px-4 py-2.5 w-full relative">
<div class="flex-1 flex justify-start">
<router-link class="flex font-bold text-3xl items-center font-sans font-bold" to="/"
<router-link class="flex font-bold text-3xl items-center font-sans" to="/"
><img
alt="logo"
src="/img/icons/logo.svg"
@ -11,10 +11,10 @@
/>iped</router-link
>
</div>
<div class="<md:hidden">
<div class="lt-md:hidden">
<input
v-model="searchText"
class="input !w-72 !h-10"
class="input w-72 h-10"
type="text"
role="search"
ref="videoSearch"
@ -89,7 +89,7 @@
<div class="w-{full - 4} md:hidden mx-2">
<input
v-model="searchText"
class="input !h-10 !w-full"
class="input h-10 w-full"
type="text"
role="search"
:title="$t('actions.search')"
@ -174,9 +174,3 @@ export default {
},
};
</script>
<style>
.input:focus {
@apply border-2 border-red-500;
box-shadow: 0 0 15px rgba(239, 68, 68, var(--tw-border-opacity));
}
</style>

View file

@ -1,46 +1,30 @@
<template>
<div class="modal">
<div>
<div class="modal-container">
<ModalComponent>
<div class="flex">
<span class="text-2xl w-max inline-block" v-t="'actions.select_playlist'" />
<button class="ml-3" @click="$emit('close')"><font-awesome-icon icon="xmark" /></button>
</div>
<select class="select w-full" v-model="selectedPlaylist">
<option
v-for="playlist in playlists"
:value="playlist.id"
:key="playlist.id"
v-text="playlist.name"
/>
<select class="select w-full mt-3" v-model="selectedPlaylist">
<option v-for="playlist in playlists" :value="playlist.id" :key="playlist.id" v-text="playlist.name" />
</select>
<div class="flex justify-end mt-3">
<button
class="btn mt-2"
class="btn"
@click="handleClick(selectedPlaylist)"
ref="addButton"
v-t="'actions.add_to_playlist'"
/>
</div>
</div>
</div>
</ModalComponent>
</template>
<style scoped>
.modal {
@apply fixed z-50 top-0 left-0 w-full h-full bg-dark-900 bg-opacity-80 transition-opacity table;
}
.modal > div {
@apply table-cell align-middle;
}
.modal-container {
@apply w-min m-auto px-8 bg-dark-700 p-6;
}
</style>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
components: {
ModalComponent,
},
props: {
videoId: {
type: String,
@ -65,12 +49,10 @@ export default {
},
methods: {
handleKeyDown(event) {
if (event.code === "Escape") {
this.$emit("close");
} else if (event.code === "Enter") {
if (event.code === "Enter") {
this.handleClick(this.selectedPlaylist);
} else return;
event.preventDefault();
}
},
handleClick(playlistId) {
if (!playlistId) {

View file

@ -53,7 +53,7 @@ export default {
this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
method: "POST",
body: JSON.stringify({
playlist: id,
playlistId: id,
newName: newName,
}),
headers: {

View file

@ -294,7 +294,7 @@
<th v-t="'preferences.instance_locations'" />
<th v-t="'preferences.has_cdn'" />
<th v-t="'preferences.registered_users'" />
<th class="<md:(hidden)" v-t="'preferences.version'" />
<th class="lt-md:hidden" v-t="'preferences.version'" />
<th v-t="'preferences.up_to_date'" />
<th v-t="'preferences.ssl_score'" />
</tr>
@ -305,7 +305,7 @@
<td v-text="instance.locations" />
<td v-text="`${instance.cdn ? '&#9989;' : '&#10060;'}`" />
<td v-text="instance.registered" />
<td class="<md:(hidden)" v-text="instance.version" />
<td class="lt-md:hidden" v-text="instance.version" />
<td v-text="`${instance.up_to_date ? '&#9989;' : '&#10060;'}`" />
<td>
<a :href="sslScore(instance.api_url)" target="_blank" v-t="'actions.view_ssl_score'" />
@ -616,6 +616,6 @@ export default {
<style>
.pref {
@apply flex justify-between items-center my-2 mx-[15vw] <md:(mx-[2vw]);
@apply flex justify-between items-center my-2 mx-[15vw] lt-md:mx-[2vw];
}
</style>

View file

@ -77,7 +77,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 <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 p-y-1.25 z-10 lt-md:max-w-[calc(100%-0.5rem)] bg-gray-300;
}
.dark .suggestions-container {
@ -85,7 +85,7 @@ export default {
}
.auto .suggestions-container {
@apply dark:bg-dark-400;
@apply @dark:bg-dark-400;
}
.suggestion-selected {
@ -97,7 +97,7 @@ export default {
}
.auto .suggestion-selected {
@apply dark:bg-dark-100;
@apply @dark:bg-dark-100;
}
.suggestion {

View file

@ -0,0 +1,81 @@
<template>
<ModalComponent>
<div class="flex">
<h2 v-t="'actions.share'" />
<button class="ml-3" @click="$emit('close')"><font-awesome-icon icon="xmark" /></button>
</div>
<div class="flex justify-between mt-4">
<label v-t="'actions.with_timecode'" for="withTimeCode" />
<input id="withTimeCode" type="checkbox" v-model="withTimeCode" />
</div>
<div class="flex justify-between">
<label v-t="'actions.piped_link'" />
<input type="checkbox" v-model="pipedLink" />
</div>
<div class="flex justify-between mt-2">
<label v-t="'actions.time_code'" />
<input class="input w-12" type="text" v-model="timeStamp" />
</div>
<h3 class="mt-4" v-text="generatedLink" />
<div class="flex justify-end mt-4">
<button class="btn" v-t="'actions.follow_link'" @click="followLink()" />
<button class="btn ml-3" v-t="'actions.copy_link'" @click="copyLink()" />
</div>
</ModalComponent>
</template>
<script>
import ModalComponent from "./ModalComponent.vue";
export default {
props: {
videoId: {
type: String,
required: true,
},
currentTime: {
type: Number,
required: true,
},
},
components: {
ModalComponent,
},
data() {
return {
withTimeCode: true,
pipedLink: true,
timeStamp: null,
};
},
mounted() {
this.timeStamp = parseInt(this.currentTime);
},
methods: {
followLink() {
window.open(this.generatedLink, "_blank").focus();
},
async copyLink() {
await this.copyURL(this.generatedLink);
},
async copyURL(mytext) {
try {
await navigator.clipboard.writeText(mytext);
alert(this.$t("info.copied"));
} catch ($e) {
alert(this.$t("info.cannot_copy"));
}
},
},
computed: {
generatedLink() {
var baseUrl = this.pipedLink
? window.location.origin + "/watch?v=" + this.videoId
: "https://youtu.be/" + this.videoId;
var url = new URL(baseUrl);
if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
return url.href;
},
},
};
</script>

View file

@ -21,7 +21,7 @@
<span class="mx-2" v-text="subscription.name" />
</router-link>
<button
class="btn !w-min"
class="btn w-min"
@click="handleButton(subscription)"
v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
/>

View file

@ -31,7 +31,7 @@
v-text="timeFormat(video.duration)"
/>
<i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
<font-awesome-icon class="!w-3" :icon="['fas', 'broadcast-tower']" />
<font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
</i18n-t>
<span v-if="video.watched" class="thumbnail-overlay bottom-5px left-5px" v-t="'video.watched'" />
</div>

View file

@ -9,7 +9,7 @@ export default {
if (videoId)
this.$router.replace({
path: "/watch",
query: { v: videoId },
query: { v: videoId, t: this.$route.query.t },
});
},
};

View file

@ -47,13 +47,13 @@
<!-- Likes/dilikes -->
<div class="flex children:mr-2">
<template v-if="video.likes >= 0">
<div>
<font-awesome-icon icon="thumbs-up" />
<strong class="ml-2" v-text="addCommas(video.likes)" />
<div class="flex">
<div class="i-fa-solid:thumbs-up" />
<strong class="ml-1" v-text="addCommas(video.likes)" />
</div>
<div>
<font-awesome-icon icon="thumbs-down" />
<strong class="ml-2" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
<div class="flex">
<div class="i-fa-solid:thumbs-down" />
<strong class="ml-1" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
</div>
</template>
<template v-if="video.likes < 0">
@ -88,6 +88,12 @@
/>
</div>
<PlaylistAddModal v-if="showModal" :video-id="getVideoId()" @close="showModal = !showModal" />
<ShareModal
v-if="showShareModal"
:video-id="getVideoId()"
:current-time="currentTime"
@close="showShareModal = !showShareModal"
/>
<div class="flex">
<div class="self-center children:mr-1 my-1">
<!-- RSS Feed button -->
@ -105,15 +111,10 @@
<font-awesome-icon icon="rss" />
</a>
<!-- watch on youtube button -->
<a :href="`https://youtu.be/${getVideoId()}`" class="btn <lg:hidden">
<i18n-t keypath="player.watch_on" tag="strong">
<font-awesome-icon class="mx-1.5" :icon="['fab', 'youtube']" />
</i18n-t>
</a>
<!-- only visible on small screens -->
<a :href="`https://youtu.be/${getVideoId()}`" class="btn lg:hidden">
<font-awesome-icon class="mx-1.5" :icon="['fab', 'youtube']" />
</a>
<button class="btn" @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">
<i18n-t keypath="player.watch_on" tag="strong">LBRY</i18n-t>
@ -211,6 +212,7 @@ import ErrorHandler from "./ErrorHandler.vue";
import CommentItem from "./CommentItem.vue";
import ChaptersBar from "./ChaptersBar.vue";
import PlaylistAddModal from "./PlaylistAddModal.vue";
import ShareModal from "./ShareModal.vue";
import PlaylistVideos from "./PlaylistVideos.vue";
export default {
@ -222,6 +224,7 @@ export default {
CommentItem,
ChaptersBar,
PlaylistAddModal,
ShareModal,
PlaylistVideos,
},
data() {
@ -245,6 +248,7 @@ export default {
smallViewQuery: smallViewQuery,
smallView: smallViewQuery.matches,
showModal: false,
showShareModal: false,
isMobile: true,
currentTime: 0,
};
@ -376,7 +380,10 @@ export default {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(this.video.description, "text/html");
xmlDoc.querySelectorAll("a").forEach(elem => (elem.outerHTML = elem.getAttribute("href")));
xmlDoc.querySelectorAll("a").forEach(elem => {
if (!elem.innerText.match(/(?:[\d]{1,2}:)?(?:[\d]{1,2}):(?:[\d]{1,2})/))
elem.outerHTML = elem.getAttribute("href");
});
xmlDoc.querySelectorAll("br").forEach(elem => (elem.outerHTML = "\n"));
this.video.description = this.urlify(xmlDoc.querySelector("body").innerHTML)
.replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")

View file

@ -93,7 +93,11 @@
"clone_playlist": "استنساخ قائمة التشغيل",
"clone_playlist_success": "تم استنساخها بنجاح!",
"download_as_txt": "تنزيل بتنسيق .txt",
"reset_preferences": "اعادة التعيين للتفضيلات"
"reset_preferences": "اعادة التعيين للتفضيلات",
"confirm_reset_preferences": "هل أنت متأكد من أنك تريد إعادة تعيين تفضيلاتك؟",
"backup_preferences": "تفضيلات النسخ الاحتياطي",
"restore_preferences": "استعادة التفضيلات",
"back_to_home": "العودة إلى الصفحة الرئيسية"
},
"video": {
"sponsor_segments": "المقاطع الإعلانية",
@ -140,5 +144,9 @@
},
"information": {
"preferences_note": "ملاحظة: يتم حفظ التفضيلات في وحدة التخزين المحلية في متصفحك. سيؤدي حذف بيانات المتصفح إلى إعادة تعيينها."
},
"info": {
"preferences_note": "ملاحظة: يتم حفظ التفضيلات في وحدة التخزين المحلية في متصفحك. سيؤدي حذف بيانات المتصفح إلى إعادة تعيينها.",
"page_not_found": "لم يتم العثور على الصفحة"
}
}

View file

@ -7,7 +7,10 @@
"preferences": "Seçimlər",
"history": "Tarixçə",
"subscriptions": "Abunəliklər",
"playlists": "Pleylistlər"
"playlists": "Pleylistlər",
"account": "Hesab",
"instance": "Server",
"player": "Oynadıcı"
},
"player": {
"watch_on": "{0} saytında baxın"
@ -67,14 +70,14 @@
"delete_playlist": "Pleylisti Silin",
"select_playlist": "Pleylist Seçin",
"delete_playlist_confirm": "Bu pleylist silinsin?",
"please_select_playlist": "Lütfən, Pleylist Seçin",
"please_select_playlist": "Lütfən, pleylist seçin",
"country_selection": "Ölkə Seçimi",
"default_homepage": "Defolt Əsas Səhifə",
"show_comments": "Şərhləri Göstərin",
"instance_selection": "Nümunə Seçimi",
"instance_selection": "Server Seçimi",
"minimize_description_default": "Açıqlamanı Defolt Olaraq Kiçildin",
"language_selection": "Dil Seçimi",
"instances_list": "Nümunələr Siyahısı",
"instances_list": "Serverlər Siyahısı",
"show_more": "Daha Çox Göstər",
"no": "Xeyr",
"store_watch_history": "Baxış Tarixçəsini Saxlayın",
@ -83,10 +86,23 @@
"show_markers": "Oynadıcıda Markerləri Göstərin",
"delete_account": "Hesabı Silin",
"logout": "Bu cihazdan çıxın",
"minimize_recommendations_default": "Defolt olaraq Tövsiyələri minimuma endir"
"minimize_recommendations_default": "Defolt olaraq Tövsiyələri minimuma endir",
"download_as_txt": ".txt kimi endirin",
"reset_preferences": "Seçimləri sıfırlayın",
"confirm_reset_preferences": "Seçimləri sıfırlamaq istədiyinizə əminsiniz?",
"backup_preferences": "Yedəkləmə seçimləri",
"restore_preferences": "Seçimləri bərpa edin",
"invalidate_session": "Bütün cihazlardan çıxın",
"different_auth_instance": "Doğrulama üçün fərqli bir serverdən istifadə edin",
"instance_auth_selection": "Autentifikasiya Server Seçimi",
"clone_playlist": "Pleylist Klonlanması",
"clone_playlist_success": "Uğurla klonlandı!"
},
"comment": {
"pinned_by": "Tərəfindən Sabitləndi"
"pinned_by": "Tərəfindən Sabitləndi",
"disabled": "Şərhlər yükləyici tərəfindən deaktiv edilib.",
"loading": "Şərhlər yüklənir...",
"user_disabled": "Şərhlər tənzimləmələrdə deaktiv edilib."
},
"preferences": {
"instance_name": "Nümunə Adı",
@ -94,7 +110,7 @@
"has_cdn": "CDN varmı?",
"registered_users": "Qeydiyyatdan Keçmiş İstifadəçilər",
"version": "Versiya",
"up_to_date": "Yenilənib?",
"up_to_date": "Güncəllənib?",
"ssl_score": "SSL Nəticəsi"
},
"login": {
@ -104,12 +120,12 @@
"video": {
"videos": "Videolar",
"views": "{views} baxış",
"watched": "Baxılmış",
"watched": "Baxılıb",
"sponsor_segments": "Sponsorlar Seqmentləri",
"ratings_disabled": "Reytinqlər Deaktivdir",
"chapters": "Bölümlər",
"live": "{0} Canlı",
"shorts": "Qısa videolar"
"shorts": "Qısa"
},
"search": {
"did_you_mean": "Bunu nəzərdə tutursunuz: {0}?",
@ -121,5 +137,11 @@
"music_videos": "YT Music: Videolar",
"music_albums": "YT Music: Albomlar",
"music_playlists": "YT Music: Pleylistlər"
},
"subscriptions": {
"subscribed_channels_count": "Abunə oldu: {0}"
},
"information": {
"preferences_note": "Qeyd: seçimlər brauzerinizin yerli yaddaşında saxlanılır. Brauzer məlumatlarınızın silinməsi onları sıfırlayacaq."
}
}

View file

@ -90,7 +90,10 @@
"clone_playlist": "Clonar Llista de Reproducció",
"clone_playlist_success": "Clonada correctament!",
"download_as_txt": "Descarrega com a .txt",
"reset_preferences": "Restablir preferències"
"reset_preferences": "Restablir preferències",
"restore_preferences": "Restaura les preferències",
"backup_preferences": "Preferències de la còpia de seguretat",
"confirm_reset_preferences": "Esteu segur que voleu restablir les vostres preferències?"
},
"comment": {
"pinned_by": "Fixat per",

View file

@ -37,7 +37,7 @@
"default_homepage": "Výchozí domovská stránka",
"show_comments": "Zobrazit komentáře",
"minimize_description_default": "Automaticky minimalizovat popis",
"store_watch_history": "Uložit historii sledování",
"store_watch_history": "Ukládat historii sledování",
"language_selection": "Výběr jazyka",
"instances_list": "Seznam instancí",
"enabled_codecs": "Povolené kodeky (několik)",
@ -90,7 +90,11 @@
"clone_playlist": "Duplikovat playlist",
"clone_playlist_success": "Úspěšně duplikováno!",
"download_as_txt": "Stáhnout jako .txt",
"reset_preferences": "Resetovat předvolby"
"reset_preferences": "Resetovat předvolby",
"restore_preferences": "Obnovit předvolby",
"backup_preferences": "Zálohovat předvolby",
"confirm_reset_preferences": "Opravdu chcete resetovat své předvolby?",
"back_to_home": "Zpátky domů"
},
"player": {
"watch_on": "Sledovat na {0}"
@ -107,7 +111,7 @@
"has_cdn": "Používá CDN?",
"ssl_score": "Stav SSL",
"registered_users": "Registrovaní uživatelé",
"up_to_date": "Aktualizovaný?",
"up_to_date": "Aktuální?",
"version": "Verze"
},
"login": {
@ -140,5 +144,9 @@
},
"information": {
"preferences_note": "Poznámka: předvolby jsou uloženy v místním úložišti vašeho prohlížeče. Odstranění dat vašeho prohlížeče je resetuje."
},
"info": {
"preferences_note": "Poznámka: předvolby se ukládají do místního úložiště prohlížeče. Vymazáním dat prohlížeče budou obnoveny.",
"page_not_found": "Stránka nenalezena"
}
}

View file

@ -99,7 +99,13 @@
"restore_preferences": "Restore preferences",
"back_to_home": "Back to home",
"rename_playlist": "Rename playlist",
"new_playlist_name": "New playlist name"
"new_playlist_name": "New playlist name",
"share": "Share",
"with_timecode": "Share with time code",
"piped_link": "Piped link",
"follow_link": "Follow link",
"copy_link": "Copy link",
"time_code": "Time code (in seconds)"
},
"comment": {
"pinned_by": "Pinned by",
@ -146,6 +152,8 @@
},
"info": {
"preferences_note": "Note: preferences are saved in the local storage of your browser. Deleting your browser data will reset them.",
"page_not_found": "Page not found"
"page_not_found": "Page not found",
"copied": "Copied!",
"cannot_copy": "Can't copy!"
}
}

View file

@ -7,7 +7,10 @@
"preferences": "Préférences",
"history": "Historique",
"subscriptions": "Abonnements",
"playlists": "Listes de lecture"
"playlists": "Listes de lecture",
"account": "Compte",
"instance": "Instance",
"player": "Lecteur"
},
"actions": {
"subscribe": "S'abonner - {count}",
@ -86,7 +89,11 @@
"instance_auth_selection": "Sélection de l'instance d'authentification",
"clone_playlist": "Cloner la liste de lecture",
"clone_playlist_success": "Clonage réussi !",
"download_as_txt": "Télécharger en tant que"
"download_as_txt": "Télécharger en tant que",
"reset_preferences": "Réinitialiser les préférences",
"confirm_reset_preferences": "Êtes-vous sûre de vouloir réinitialiser les préférences ?",
"restore_preferences": "Restaurer les préférences",
"backup_preferences": "Sauvegarde des préférences"
},
"player": {
"watch_on": "Regarder sur {0}"
@ -133,5 +140,8 @@
},
"subscriptions": {
"subscribed_channels_count": "Abonné à : {0}"
},
"information": {
"preferences_note": "Note : les préférences sont sauvegardées dans le stockage local de votre navigateur. Supprimer les données de votre navigateur les réinitialiserons."
}
}

View file

@ -7,7 +7,8 @@
"history": "Előzmények",
"subscriptions": "Feliratkozások",
"playlists": "Lejátszási listák",
"trending": "Felkapott"
"trending": "Felkapott",
"account": "Fiók"
},
"actions": {
"subscribe": "Feliratkozás - {count}",

View file

@ -93,7 +93,10 @@
"clone_playlist_success": "Berhasil disalin!",
"clone_playlist": "Salin Daftar Putar",
"download_as_txt": "Unduh sebagai .txt",
"reset_preferences": "Atur ulang preferensi"
"reset_preferences": "Atur ulang preferensi",
"restore_preferences": "Pulihkan preferensi",
"confirm_reset_preferences": "Apakah Anda yakin ingin mengatur ulang preferensi Anda?",
"backup_preferences": "Cadangkan preferensi"
},
"comment": {
"pinned_by": "Dipasangi pin oleh",

View file

@ -76,7 +76,12 @@
"instance_auth_selection": "Selezione dell'istanza di autenticazione",
"clone_playlist_success": "Clonato con successo!",
"clone_playlist": "Clona la playlist",
"download_as_txt": "Scarica come .txt"
"download_as_txt": "Scarica come .txt",
"confirm_reset_preferences": "Confermi di voler reimpostare le preferenze?",
"restore_preferences": "Ripristina le preferenze",
"reset_preferences": "Reimposta le preferenze",
"backup_preferences": "Salva un backup delle preferenze",
"back_to_home": "Torna alla pagina iniziale"
},
"player": {
"watch_on": "Guarda su {0}"
@ -89,7 +94,10 @@
"login": "Accedi",
"trending": "Di tendenza",
"subscriptions": "Iscrizioni",
"playlists": "Playlist"
"playlists": "Playlist",
"account": "Account",
"instance": "Istanza",
"player": "Riproduttore"
},
"video": {
"sponsor_segments": "Segmenti sponsor",
@ -133,5 +141,12 @@
},
"subscriptions": {
"subscribed_channels_count": "Abbonato a: {0}"
},
"information": {
"preferences_note": "Nota: le preferenze sono salvate nella memoria locale del tuo browser. Se cancelli i dati del browser, le preferenze saranno ripristinate."
},
"info": {
"page_not_found": "Pagina non trovata",
"preferences_note": "Nota: le preferenze sono salvate nella memoria locale del tuo browser. L'eliminazione dei dati del tuo browser le ripristinerà."
}
}

View file

@ -1,34 +1,34 @@
{
"actions": {
"instances_list": "Liste over instanser",
"minimize_description_default": "Minimer beskrivelse som forvalg",
"minimize_description_default": "Minimer beskrivelse som standard",
"country_selection": "Land",
"buffering_goal": "Mellomlagringsmål (i sekunder)",
"autoplay_video": "Spill video automatisk",
"skip_non_music": "Hopp over musikk:Del uten musikk",
"skip_non_music": "Hopp over musikk: del uten musikk",
"auto": "Auto",
"skip_self_promo": "Hopp over ubetalt/selvpromotering",
"skip_interaction": "Hopp over interaksjonspåminnelse (abonnering)",
"skip_preview": "Hopp over forhåndsvisning/reintroduksjon",
"skip_outro": "Skru av rulletekst/sluttpresentasjon",
"skip_outro": "Hopp over rulletekst/utro",
"skip_intro": "Hopp over forvideo/introanimasjon",
"enable_sponsorblock": "Skru på sponsorblokkering",
"language_selection": "Språk",
"store_watch_history": "Lagre visningshistorikk",
"show_comments": "Vis kommentarer",
"default_homepage": "Forvalgt hjemmeside",
"default_quality": "Forvalgt kvalitet",
"default_homepage": "Standard hjemmeside",
"default_quality": "Standard kvalitet",
"audio_only": "Kun lyd",
"light": "Lys",
"dark": "Mørk",
"theme": "Drakt",
"theme": "Tema",
"skip_sponsors": "Hopp over sponsorer",
"uses_api_from": "Bruker API-et fra ",
"back": "Tilbake",
"channel_name_desc": "Kanalnavn (Å-A)",
"channel_name_asc": "Kanalnavn (A-Å)",
"least_recent": "Eldst",
"most_recent": "Nyligst",
"most_recent": "Nyest",
"sort_by": "Sorter etter:",
"view_subscriptions": "Vis abonnementer",
"unsubscribe": "Opphev abonnement - {count}",
@ -37,7 +37,7 @@
"disable_lbry": "Skru av LBRY-strømming",
"enabled_codecs": "Aktiverte forskjellige kodek",
"show_description": "Vis beskrivelse",
"minimize_recommendations": "Minimere anbefalinger",
"minimize_recommendations": "Minimer anbefalinger",
"show_recommendations": "Vis anbefalinger",
"donations": "Donasjoner",
"auto_play_next_video": "Autospill neste video",
@ -49,7 +49,7 @@
"show_more": "Vis mer",
"instance_selection": "Valg av instans",
"search": "Søk",
"filter": "Filter",
"filter": "Filtrer",
"loading": "Laster inn …",
"view_ssl_score": "Vis SSL-poengsum",
"minimize_description": "Minimer beskrivelse",
@ -60,10 +60,10 @@
"skip_highlight": "Hopp over hovedmoment",
"load_more_replies": "Last inn flere svar",
"create_playlist": "Opprett spilleliste",
"delete_playlist_confirm": "Slett denne spillelisten?",
"delete_playlist_confirm": "Er du sikker på at du vil slette spillelisten?",
"delete_playlist": "Slett spilleliste",
"select_playlist": "Velg en spilleliste",
"please_select_playlist": "Velg en spilleliste",
"select_playlist": "Velg spilleliste",
"please_select_playlist": "Vennligst velg en spilleliste",
"delete_playlist_video_confirm": "Fjern video fra spilleliste?",
"show_markers": "Vis markører i avspiller",
"add_to_playlist": "Legg til i spilleliste",
@ -78,7 +78,7 @@
"clone_playlist": "Klon spillelisten",
"clone_playlist_success": "Klonet",
"reset_preferences": "Tilbakestill innstillinger",
"backup_preferences": "Sikkerhetskopieringsinnstillinger",
"backup_preferences": "Innstillinger for sikkerhetskopiering",
"confirm_reset_preferences": "Tilbakestill alle innstillingene?",
"restore_preferences": "Gjenopprett innstillinger"
},
@ -87,7 +87,7 @@
},
"titles": {
"feed": "Strøm",
"history": "HIstorikk",
"history": "Historikk",
"preferences": "Innstillinger",
"register": "Registrering",
"login": "Logg inn",
@ -103,7 +103,7 @@
"watched": "Sett",
"views": "{views} visninger",
"videos": "Videoer",
"ratings_disabled": "Vurderinger avskrudd",
"ratings_disabled": "Vurdering deaktivert",
"chapters": "Kapitler",
"live": "{0} direkte",
"shorts": "Korte"

View file

@ -7,7 +7,10 @@
"register": "Registar",
"history": "Histórico",
"feed": "Feed",
"playlists": "Listas de reprodução"
"playlists": "Listas de reprodução",
"account": "Conta",
"instance": "Instância",
"player": "Reprodutor"
},
"actions": {
"sort_by": "Ordenar por:",
@ -85,7 +88,11 @@
"minimize_recommendations_default": "Minimizar recomendações por definição",
"invalidate_session": "Terminar sessão em todos os dispositivos",
"different_auth_instance": "Use uma instância diferente para autenticação",
"instance_auth_selection": "Seleção de instância de autenticação"
"instance_auth_selection": "Seleção de instância de autenticação",
"confirm_reset_preferences": "Tem a certeza de que deseja restaurar as preferências originais?",
"download_as_txt": "Descarregar como txt",
"reset_preferences": "Repor definições originais",
"restore_preferences": "Restaurar preferências"
},
"preferences": {
"instance_name": "Nome da instância",

View file

@ -7,7 +7,10 @@
"subscriptions": "Prenumerationer",
"feed": "Flöde",
"history": "Historik",
"playlists": "Spellistor"
"playlists": "Spellistor",
"account": "Konto",
"instance": "Instans",
"player": "Spelare"
},
"actions": {
"subscribe": "Prenumerera - {count}",
@ -86,7 +89,11 @@
"invalidate_session": "Logga ut alla enheter",
"different_auth_instance": "Använd en annan instans för autentisering",
"instance_auth_selection": "Val av autentiseringsinstans",
"download_as_txt": "Ladda ner som .txt"
"download_as_txt": "Ladda ner som .txt",
"reset_preferences": "Återställ inställningar",
"confirm_reset_preferences": "Är du säker på att du vill återställa dina inställningar?",
"backup_preferences": "Inställningar för säkerhetskopiering",
"restore_preferences": "Återställa inställningar"
},
"player": {
"watch_on": "Titta på {0}"
@ -133,5 +140,8 @@
},
"subscriptions": {
"subscribed_channels_count": "Prenumererar på: {0}"
},
"information": {
"preferences_note": "Observera: inställningar sparas i webbläsarens lokala lagring. Om du raderar dina webbläsardata återställs de."
}
}

View file

@ -80,7 +80,8 @@
"reset_preferences": "Tercihleri sıfırla",
"confirm_reset_preferences": "Tercihlerinizi sıfırlamak istediğinize emin misiniz?",
"backup_preferences": "Tercihleri yedekle",
"restore_preferences": "Tercihleri geri yükle"
"restore_preferences": "Tercihleri geri yükle",
"back_to_home": "Ana sayfaya dön"
},
"player": {
"watch_on": "{0} üzerinde izle"
@ -143,5 +144,9 @@
},
"information": {
"preferences_note": "Not: Tercihler tarayıcınızın yerel depolama alanına kaydedilir. Tarayıcı verilerinizi silmek onları sıfırlayacaktır."
},
"info": {
"preferences_note": "Not: Tercihler tarayıcınızın yerel depolama alanına kaydedilir. Tarayıcı verilerinizi silmek onları sıfırlayacaktır.",
"page_not_found": "Sayfa bulunamadı"
}
}

View file

@ -55,11 +55,12 @@
"remove_from_playlist": "從播放清單中移除",
"create_playlist": "建立播放清單",
"delete_playlist": "刪除播放清單",
"delete_playlist_confirm": "確定要刪除此播放清單嗎?",
"delete_playlist_confirm": "要刪除這份播放清單嗎?",
"please_select_playlist": "請選擇播放清單",
"select_playlist": "選擇播放清單",
"add_to_playlist": "加到播放清單",
"delete_playlist_video_confirm": "確定要將此影片從此播放清單中移除嗎?"
"delete_playlist_video_confirm": "要從播放清單中移除影片嗎?",
"delete_account": "刪除帳戶"
},
"titles": {
"history": "歷史記錄",
@ -69,7 +70,8 @@
"register": "註冊",
"login": "登入",
"subscriptions": "訂閱內容",
"playlists": "播放清單"
"playlists": "播放清單",
"account": "帳戶"
},
"preferences": {
"registered_users": "已註冊的使用者",
@ -99,6 +101,8 @@
"music_albums": "YT Music專輯"
},
"comment": {
"pinned_by": "置頂者:"
"pinned_by": "置頂者:",
"disabled": "上傳者停用了留言功能。",
"loading": "留言載入中……"
}
}

View file

@ -1,8 +1,6 @@
import { createApp } from "vue";
import { library } from "@fortawesome/fontawesome-svg-core";
import {
faThumbsUp,
faThumbsDown,
faEye,
faThumbtack,
faCheck,
@ -18,12 +16,11 @@ import {
faCircleMinus,
faXmark,
faClone,
faShare,
} from "@fortawesome/free-solid-svg-icons";
import { faGithub, faBitcoin, faYoutube } from "@fortawesome/free-brands-svg-icons";
import { faGithub, faBitcoin } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
library.add(
faThumbsUp,
faThumbsDown,
faEye,
faGithub,
faBitcoin,
@ -31,7 +28,6 @@ library.add(
faCheck,
faHeart,
faHeadphones,
faYoutube,
faRss,
faChevronLeft,
faLevelDownAlt,
@ -42,6 +38,7 @@ library.add(
faCircleMinus,
faXmark,
faClone,
faShare,
);
import router from "@/router/router.js";
@ -57,7 +54,8 @@ TimeAgo.addDefaultLocale(en);
import { createI18n } from "vue-i18n";
import enLocale from "@/locales/en.json";
import "windi.css";
import "@unocss/reset/tailwind.css";
import "uno.css";
const timeAgo = new TimeAgo("en-US");
@ -179,6 +177,7 @@ const mixin = {
const emailRegex = /([\w-\\.]+@(?:[\w-]+\.)+[\w-]{2,4})/g;
return string
.replace(urlRegex, url => {
if (url.endsWith("</a>")) return url;
return `<a href="${url}" target="_blank">${url}</a>`;
})
.replace(emailRegex, email => {

34
uno.config.js Normal file
View file

@ -0,0 +1,34 @@
import { defineConfig } from "unocss";
import transformerDirective from "@unocss/transformer-directives";
import transformerVariantGroup from "@unocss/transformer-variant-group";
import presetUno from "@unocss/preset-uno";
import presetIcons from "@unocss/preset-icons";
import presetWebFonts from "@unocss/preset-web-fonts";
export default defineConfig({
transformers: [transformerDirective(), transformerVariantGroup()],
presets: [
presetUno(),
presetIcons(),
presetWebFonts({
provider: "none",
fonts: {
sans: [
"-apple-system",
"BlinkMacSystemFont",
"Segoe UI",
"Roboto",
"Helvetica Neue",
"Arial",
"Noto Sans",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
},
}),
],
});

View file

@ -1,6 +1,6 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import WindiCSS from "vite-plugin-windicss";
import Unocss from "unocss/vite";
import legacy from "@vitejs/plugin-legacy";
import vueI18n from "@intlify/vite-plugin-vue-i18n";
import { VitePWA } from "vite-plugin-pwa";
@ -11,7 +11,7 @@ import eslintPlugin from "vite-plugin-eslint";
export default defineConfig({
plugins: [
vue(),
WindiCSS(),
Unocss(),
vueI18n({
include: path.resolve(__dirname, "./src/locales/**"),
}),

View file

@ -1,23 +0,0 @@
module.exports = {
darkMode: "media",
theme: {
extend: {
fontFamily: {
sans: [
"-apple-system",
"BlinkMacSystemFont",
"Segoe UI",
"Roboto",
"Helvetica Neue",
"Arial",
"Noto Sans",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
},
},
},
};

802
yarn.lock

File diff suppressed because it is too large Load diff