mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-11-22 05:27:20 +00:00
Fix eslint config and apply all fixes.
This commit is contained in:
parent
6c05f63bef
commit
301877e2e1
7
.eslintrc.cjs
Normal file
7
.eslintrc.cjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: ["plugin:vue/vue3-recommended", "eslint:recommended", "plugin:prettier/recommended"],
|
||||||
|
};
|
12
package.json
12
package.json
@ -51,18 +51,6 @@
|
|||||||
"vite-plugin-pwa": "0.16.4",
|
"vite-plugin-pwa": "0.16.4",
|
||||||
"workbox-window": "7.0.0"
|
"workbox-window": "7.0.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:vue/vue3-essential",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 chrome version",
|
"last 1 chrome version",
|
||||||
"last 1 firefox version"
|
"last 1 firefox version"
|
||||||
|
38
src/App.vue
38
src/App.vue
@ -29,25 +29,6 @@ export default {
|
|||||||
theme: "dark",
|
theme: "dark",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
setTheme() {
|
|
||||||
let themePref = this.getPreferenceString("theme", "dark");
|
|
||||||
if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
|
|
||||||
else this.theme = themePref;
|
|
||||||
|
|
||||||
// Change title bar color based on user's theme
|
|
||||||
const themeColor = document.querySelector("meta[name='theme-color']");
|
|
||||||
if (this.theme === "light") {
|
|
||||||
themeColor.setAttribute("content", "#FFF");
|
|
||||||
} else {
|
|
||||||
themeColor.setAttribute("content", "#0F0F0F");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used for the scrollbar
|
|
||||||
const root = document.querySelector(":root");
|
|
||||||
this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setTheme();
|
this.setTheme();
|
||||||
darkModePreference.addEventListener("change", () => {
|
darkModePreference.addEventListener("change", () => {
|
||||||
@ -112,6 +93,25 @@ export default {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
setTheme() {
|
||||||
|
let themePref = this.getPreferenceString("theme", "dark");
|
||||||
|
if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
|
||||||
|
else this.theme = themePref;
|
||||||
|
|
||||||
|
// Change title bar color based on user's theme
|
||||||
|
const themeColor = document.querySelector("meta[name='theme-color']");
|
||||||
|
if (this.theme === "light") {
|
||||||
|
themeColor.setAttribute("content", "#FFF");
|
||||||
|
} else {
|
||||||
|
themeColor.setAttribute("content", "#0F0F0F");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for the scrollbar
|
||||||
|
const root = document.querySelector(":root");
|
||||||
|
this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<span v-text="props.item.name" />
|
<span v-text="props.item.name" />
|
||||||
<font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
|
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
<p v-if="props.item.description" v-text="props.item.description" />
|
<p v-if="props.item.description" v-text="props.item.description" />
|
||||||
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
|
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="props.item.uploader" />
|
<span v-text="props.item.uploader" />
|
||||||
<font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
|
<font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: Object,
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,27 +12,27 @@
|
|||||||
<div class="flex place-items-center">
|
<div class="flex place-items-center">
|
||||||
<img height="48" width="48" class="rounded-full m-1" :src="channel.avatarUrl" />
|
<img height="48" width="48" class="rounded-full m-1" :src="channel.avatarUrl" />
|
||||||
<div class="flex gap-1 items-center">
|
<div class="flex gap-1 items-center">
|
||||||
<h1 v-text="channel.name" class="!text-xl" />
|
<h1 class="!text-xl" v-text="channel.name" />
|
||||||
<font-awesome-icon class="!text-xl" v-if="channel.verified" icon="check" />
|
<font-awesome-icon v-if="channel.verified" class="!text-xl" icon="check" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
class="btn"
|
|
||||||
@click="subscribeHandler"
|
|
||||||
v-t="{
|
v-t="{
|
||||||
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
|
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
|
||||||
args: { count: numberFormat(channel.subscriberCount) },
|
args: { count: numberFormat(channel.subscriberCount) },
|
||||||
}"
|
}"
|
||||||
|
class="btn"
|
||||||
|
@click="subscribeHandler"
|
||||||
></button>
|
></button>
|
||||||
|
|
||||||
<!-- RSS Feed button -->
|
<!-- RSS Feed button -->
|
||||||
<a
|
<a
|
||||||
|
v-if="channel.id"
|
||||||
aria-label="RSS feed"
|
aria-label="RSS feed"
|
||||||
title="RSS feed"
|
title="RSS feed"
|
||||||
role="button"
|
role="button"
|
||||||
v-if="channel.id"
|
|
||||||
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
|
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="btn flex-col"
|
class="btn flex-col"
|
||||||
@ -44,15 +44,15 @@
|
|||||||
|
|
||||||
<CollapsableText :text="channel.description" />
|
<CollapsableText :text="channel.description" />
|
||||||
|
|
||||||
<WatchOnButton :link="`https://youtube.com/channel/${this.channel.id}`" />
|
<WatchOnButton :link="`https://youtube.com/channel/${channel.id}`" />
|
||||||
|
|
||||||
<div class="flex my-2 mx-1">
|
<div class="flex my-2 mx-1">
|
||||||
<button
|
<button
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="tab.name"
|
:key="tab.name"
|
||||||
class="btn mr-2"
|
class="btn mr-2"
|
||||||
@click="loadTab(index)"
|
|
||||||
:class="{ active: selectedTab == index }"
|
:class="{ active: selectedTab == index }"
|
||||||
|
@click="loadTab(index)"
|
||||||
>
|
>
|
||||||
<span v-text="tab.translatedName"></span>
|
<span v-text="tab.translatedName"></span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
{{ $t("video.chapters") }} ({{ chapters.length }})
|
{{ $t("video.chapters") }} ({{ chapters.length }})
|
||||||
</h2>
|
</h2>
|
||||||
<div
|
<div
|
||||||
:key="chapter.start"
|
|
||||||
v-for="(chapter, index) in chapters"
|
v-for="(chapter, index) in chapters"
|
||||||
@click="$emit('seek', chapter.start)"
|
:key="chapter.start"
|
||||||
class="chapter-vertical"
|
class="chapter-vertical"
|
||||||
:class="{ 'bg-red-500/50': isCurrentChapter(index) }"
|
:class="{ 'bg-red-500/50': isCurrentChapter(index) }"
|
||||||
|
@click="$emit('seek', chapter.start)"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<span class="mt-5 mr-2 text-current" v-text="index + 1" />
|
<span class="mt-5 mr-2 text-current" v-text="index + 1" />
|
||||||
@ -31,11 +31,11 @@
|
|||||||
{{ $t("video.chapters") }} ({{ chapters.length }})
|
{{ $t("video.chapters") }} ({{ chapters.length }})
|
||||||
</h2>
|
</h2>
|
||||||
<div
|
<div
|
||||||
:key="chapter.start"
|
|
||||||
v-for="(chapter, index) in chapters"
|
v-for="(chapter, index) in chapters"
|
||||||
@click="$emit('seek', chapter.start)"
|
:key="chapter.start"
|
||||||
class="chapter-vertical"
|
class="chapter-vertical"
|
||||||
:class="{ 'bg-red-500/50': isCurrentChapter(index) }"
|
:class="{ 'bg-red-500/50': isCurrentChapter(index) }"
|
||||||
|
@click="$emit('seek', chapter.start)"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<span class="mt-5 mr-2 text-current" v-text="index + 1" />
|
<span class="mt-5 mr-2 text-current" v-text="index + 1" />
|
||||||
@ -50,11 +50,11 @@
|
|||||||
<!-- mobile Horizontal view -->
|
<!-- mobile Horizontal view -->
|
||||||
<div v-if="getPreferenceString('mobileChapterLayout') == 'Horizontal' && mobileLayout" class="flex overflow-x-auto">
|
<div v-if="getPreferenceString('mobileChapterLayout') == 'Horizontal' && mobileLayout" class="flex overflow-x-auto">
|
||||||
<div
|
<div
|
||||||
:key="chapter.start"
|
|
||||||
v-for="(chapter, index) in chapters"
|
v-for="(chapter, index) in chapters"
|
||||||
@click="$emit('seek', chapter.start)"
|
:key="chapter.start"
|
||||||
class="chapter"
|
class="chapter"
|
||||||
:class="{ 'bg-red-500/50': isCurrentChapter(index) }"
|
:class="{ 'bg-red-500/50': isCurrentChapter(index) }"
|
||||||
|
@click="$emit('seek', chapter.start)"
|
||||||
>
|
>
|
||||||
<img :src="chapter.image" :alt="chapter.title" />
|
<img :src="chapter.image" :alt="chapter.title" />
|
||||||
<div class="m-1 flex">
|
<div class="m-1 flex">
|
||||||
@ -65,6 +65,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
chapters: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
mobileLayout: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true,
|
||||||
|
},
|
||||||
|
playerPosition: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isCurrentChapter = index => {
|
||||||
|
return (
|
||||||
|
props.playerPosition >= props.chapters[index].start &&
|
||||||
|
props.playerPosition < (props.chapters[index + 1]?.start ?? Infinity)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
defineEmits(["seek"]);
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
height: 5px;
|
height: 5px;
|
||||||
@ -89,26 +115,3 @@
|
|||||||
@apply truncate overflow-hidden inline-block w-10em;
|
@apply truncate overflow-hidden inline-block w-10em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const props = defineProps({
|
|
||||||
chapters: Object,
|
|
||||||
mobileLayout: {
|
|
||||||
type: Boolean,
|
|
||||||
default: () => true,
|
|
||||||
},
|
|
||||||
playerPosition: {
|
|
||||||
type: Number,
|
|
||||||
default: () => 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isCurrentChapter = index => {
|
|
||||||
return (
|
|
||||||
props.playerPosition >= props.chapters[index].start &&
|
|
||||||
props.playerPosition < (props.chapters[index + 1]?.start ?? Infinity)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
defineEmits(["seek"]);
|
|
||||||
</script>
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template v-if="text">
|
||||||
|
<div class="whitespace-pre-wrap py-2 mx-1">
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- 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="fullText()" />
|
<span v-if="showFullText" v-html="fullText()" />
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<span v-else v-html="colapsedText()" />
|
<span v-else v-html="colapsedText()" />
|
||||||
<span v-if="text.length > 100 && !showFullText">...</span>
|
<span v-if="text.length > 100 && !showFullText">...</span>
|
||||||
<button
|
<button
|
||||||
@ -19,7 +20,10 @@ import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
text: String,
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -14,34 +14,35 @@
|
|||||||
<div v-if="comment.pinned" class="comment-pinned">
|
<div v-if="comment.pinned" class="comment-pinned">
|
||||||
<font-awesome-icon icon="thumbtack" />
|
<font-awesome-icon icon="thumbtack" />
|
||||||
<span
|
<span
|
||||||
class="ml-1.5"
|
|
||||||
v-t="{
|
v-t="{
|
||||||
path: 'comment.pinned_by',
|
path: 'comment.pinned_by',
|
||||||
args: { author: uploader },
|
args: { author: uploader },
|
||||||
}"
|
}"
|
||||||
|
class="ml-1.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="comment-author">
|
<div class="comment-author">
|
||||||
<router-link class="font-bold link" :to="comment.commentorUrl">{{ comment.author }}</router-link>
|
<router-link class="font-bold link" :to="comment.commentorUrl">{{ comment.author }}</router-link>
|
||||||
<font-awesome-icon class="ml-1.5" v-if="comment.verified" icon="check" />
|
<font-awesome-icon v-if="comment.verified" class="ml-1.5" icon="check" />
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-meta text-sm mb-1.5" v-text="comment.commentedTime" />
|
<div class="comment-meta text-sm mb-1.5" v-text="comment.commentedTime" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div class="whitespace-pre-wrap" v-html="purifiedText" />
|
<div class="whitespace-pre-wrap" v-html="purifiedText" />
|
||||||
<div class="comment-footer mt-1 flex items-center">
|
<div class="comment-footer mt-1 flex items-center">
|
||||||
<div class="i-fa6-solid:thumbs-up" />
|
<div class="i-fa6-solid:thumbs-up" />
|
||||||
<span class="ml-1" v-text="numberFormat(comment.likeCount)" />
|
<span class="ml-1" v-text="numberFormat(comment.likeCount)" />
|
||||||
<font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
|
<font-awesome-icon v-if="comment.hearted" class="ml-1" icon="heart" />
|
||||||
</div>
|
</div>
|
||||||
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
|
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
|
||||||
<div @click="loadReplies" class="cursor-pointer">
|
<div class="cursor-pointer" @click="loadReplies">
|
||||||
<a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
|
<a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
|
||||||
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
|
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="showingReplies">
|
<template v-if="showingReplies">
|
||||||
<div @click="hideReplies" class="cursor-pointer">
|
<div class="cursor-pointer" @click="hideReplies">
|
||||||
<a v-t="'actions.hide_replies'" />
|
<a v-t="'actions.hide_replies'" />
|
||||||
<font-awesome-icon class="ml-1.5" icon="level-up-alt" />
|
<font-awesome-icon class="ml-1.5" icon="level-up-alt" />
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +51,7 @@
|
|||||||
<div v-for="reply in replies" :key="reply.commentId" class="w-full">
|
<div v-for="reply in replies" :key="reply.commentId" class="w-full">
|
||||||
<CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
|
<CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="nextpage" @click="loadReplies" class="cursor-pointer">
|
<div v-if="nextpage" class="cursor-pointer" @click="loadReplies">
|
||||||
<a v-t="'actions.load_more_replies'" />
|
<a v-t="'actions.load_more_replies'" />
|
||||||
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
|
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl" v-text="message" />
|
<h3 class="text-xl" v-text="message" />
|
||||||
<div class="ml-auto mt-8 flex gap-2 w-min">
|
<div class="ml-auto mt-8 flex gap-2 w-min">
|
||||||
<button class="btn" v-t="'actions.cancel'" @click="$emit('close')" />
|
<button v-t="'actions.cancel'" class="btn" @click="$emit('close')" />
|
||||||
<button class="btn" v-t="'actions.okay'" @click="$emit('confirm')" />
|
<button v-t="'actions.okay'" class="btn" @click="$emit('confirm')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalComponent>
|
</ModalComponent>
|
||||||
@ -18,7 +18,10 @@ export default {
|
|||||||
ModalComponent,
|
ModalComponent,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
message: String,
|
message: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ["close", "confirm"],
|
emits: ["close", "confirm"],
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
import { defineAsyncComponent } from "vue";
|
import { defineAsyncComponent } from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: Object,
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));
|
const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<p v-text="message" />
|
<p v-text="message" />
|
||||||
<button @click="toggleTrace" class="btn" v-t="'actions.show_more'" />
|
<button v-t="'actions.show_more'" class="btn" @click="toggleTrace" />
|
||||||
<p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
|
<p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
class="select flex-grow"
|
class="select flex-grow"
|
||||||
@change="onFilterChange()"
|
@change="onFilterChange()"
|
||||||
>
|
>
|
||||||
<option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`video.${filter}`" />
|
<option v-for="filter in availableFilters" :key="filter" v-t="`video.${filter}`" :value="filter" />
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<strong v-text="`${$t('titles.channel_groups')}:`" />
|
<strong v-text="`${$t('titles.channel_groups')}:`" />
|
||||||
</label>
|
</label>
|
||||||
<select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
|
<select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
|
||||||
<option value="" v-t="`video.all`" />
|
<option v-t="`video.all`" value="" />
|
||||||
<option
|
<option
|
||||||
v-for="group in channelGroups"
|
v-for="group in channelGroups"
|
||||||
:key="group.groupName"
|
:key="group.groupName"
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
<footer class="text-center py-4 rounded-xl children:(mx-3) w-full mt-10">
|
<footer class="text-center py-4 rounded-xl children:(mx-3) w-full mt-10">
|
||||||
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
|
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
|
||||||
<font-awesome-icon :icon="['fab', 'github']" />
|
<font-awesome-icon :icon="['fab', 'github']" />
|
||||||
<span class="ml-2" v-t="'actions.source_code'" />
|
<span v-t="'actions.source_code'" class="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://docs.piped.video/" target="_blank">
|
<a href="https://docs.piped.video/" target="_blank">
|
||||||
<font-awesome-icon :icon="['fa', 'book']" />
|
<font-awesome-icon :icon="['fa', 'book']" />
|
||||||
<span class="ml-2" v-t="'actions.documentation'" />
|
<span v-t="'actions.documentation'" class="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/TeamPiped/Piped#donations" target="_blank">
|
<a href="https://github.com/TeamPiped/Piped#donations" target="_blank">
|
||||||
<font-awesome-icon :icon="['fab', 'bitcoin']" />
|
<font-awesome-icon :icon="['fab', 'bitcoin']" />
|
||||||
<span class="ml-2" v-t="'actions.donations'" />
|
<span v-t="'actions.donations'" class="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
<a v-if="statusPageHref" :href="statusPageHref">
|
<a v-if="statusPageHref" :href="statusPageHref">
|
||||||
<font-awesome-icon :icon="['fa', 'server']" />
|
<font-awesome-icon :icon="['fa', 'server']" />
|
||||||
<span class="ml-2" v-t="'actions.status_page'" />
|
<span v-t="'actions.status_page'" class="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
<a v-if="donationHref" :href="donationHref">
|
<a v-if="donationHref" :href="donationHref">
|
||||||
<font-awesome-icon :icon="['fa', 'donate']" />
|
<font-awesome-icon :icon="['fa', 'donate']" />
|
||||||
<span class="ml-2" v-t="'actions.instance_donations'" />
|
<span v-t="'actions.instance_donations'" class="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 class="font-bold text-center mb-3" v-t="'titles.history'" />
|
<h1 v-t="'titles.history'" class="font-bold text-center mb-3" />
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex md:items-center gap-2 flex-col md:flex-row">
|
<div class="flex md:items-center gap-2 flex-col md:flex-row">
|
||||||
<button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
|
<button v-t="'actions.clear_history'" class="btn" @click="clearHistory" />
|
||||||
|
|
||||||
<button class="btn" v-t="'actions.export_to_json'" @click="exportHistory" />
|
<button v-t="'actions.export_to_json'" class="btn" @click="exportHistory" />
|
||||||
|
|
||||||
<div class="ml-auto flex gap-1 items-center">
|
<div class="ml-auto flex gap-1 items-center">
|
||||||
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
|
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
|
||||||
@ -13,19 +13,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex ml-4 items-center">
|
<div class="flex ml-4 items-center">
|
||||||
<input id="autoDelete" type="checkbox" v-model="autoDeleteHistory" @change="onChange" />
|
<input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
|
||||||
<label class="ml-2" for="autoDelete" v-t="'actions.delete_automatically'" />
|
<label v-t="'actions.delete_automatically'" class="ml-2" for="autoDelete" />
|
||||||
<select class="pl-3 ml-3 select" v-model="autoDeleteDelayHours" @change="onChange">
|
<select v-model="autoDeleteDelayHours" class="pl-3 ml-3 select" @change="onChange">
|
||||||
<option value="1" v-t="{ path: 'info.hours', args: { amount: '1' } }" />
|
<option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
|
||||||
<option value="3" v-t="{ path: 'info.hours', args: { amount: '3' } }" />
|
<option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
|
||||||
<option value="6" v-t="{ path: 'info.hours', args: { amount: '6' } }" />
|
<option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
|
||||||
<option value="12" v-t="{ path: 'info.hours', args: { amount: '12' } }" />
|
<option v-t="{ path: 'info.hours', args: { amount: '12' } }" value="12" />
|
||||||
<option value="24" v-t="{ path: 'info.days', args: { amount: '1' } }" />
|
<option v-t="{ path: 'info.days', args: { amount: '1' } }" value="24" />
|
||||||
<option value="72" v-t="{ path: 'info.days', args: { amount: '3' } }" />
|
<option v-t="{ path: 'info.days', args: { amount: '3' } }" value="72" />
|
||||||
<option value="168" v-t="{ path: 'info.weeks', args: { amount: '1' } }" />
|
<option v-t="{ path: 'info.weeks', args: { amount: '1' } }" value="168" />
|
||||||
<option value="336" v-t="{ path: 'info.weeks', args: { amount: '3' } }" />
|
<option v-t="{ path: 'info.weeks', args: { amount: '3' } }" value="336" />
|
||||||
<option value="672" v-t="{ path: 'info.months', args: { amount: '1' } }" />
|
<option v-t="{ path: 'info.months', args: { amount: '1' } }" value="672" />
|
||||||
<option value="1344" v-t="{ path: 'info.months', args: { amount: '2' } }" />
|
<option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,6 +7,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
showContent: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#spinner:after {
|
#spinner:after {
|
||||||
--spinner-color: #000;
|
--spinner-color: #000;
|
||||||
@ -42,14 +53,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
showContent: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
:placeholder="$t('login.username')"
|
:placeholder="$t('login.username')"
|
||||||
:aria-label="$t('login.username')"
|
:aria-label="$t('login.username')"
|
||||||
v-on:keyup.enter="login"
|
@keyup.enter="login"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -22,11 +22,11 @@
|
|||||||
autocomplete="password"
|
autocomplete="password"
|
||||||
:placeholder="$t('login.password')"
|
:placeholder="$t('login.password')"
|
||||||
:aria-label="$t('login.password')"
|
:aria-label="$t('login.password')"
|
||||||
v-on:keyup.enter="login"
|
@keyup.enter="login"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn w-auto" @click="login" v-t="'titles.login'" />
|
<a v-t="'titles.login'" class="btn w-auto" @click="login" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
emits: ["close"],
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener("keydown", this.handleKeyDown);
|
window.addEventListener("keydown", this.handleKeyDown);
|
||||||
},
|
},
|
||||||
|
@ -13,11 +13,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="lt-md:hidden search-container">
|
<div class="lt-md:hidden search-container">
|
||||||
<input
|
<input
|
||||||
|
ref="videoSearch"
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
class="input w-72 h-10 pr-20"
|
class="input w-72 h-10 pr-20"
|
||||||
type="text"
|
type="text"
|
||||||
role="search"
|
role="search"
|
||||||
ref="videoSearch"
|
|
||||||
:title="$t('actions.search')"
|
:title="$t('actions.search')"
|
||||||
:placeholder="$t('actions.search')"
|
:placeholder="$t('actions.search')"
|
||||||
@keyup="onKeyUp"
|
@keyup="onKeyUp"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
/>
|
/>
|
||||||
<span v-if="searchText" class="delete-search" @click="searchText = ''">⨉</span>
|
<span v-if="searchText" class="delete-search" @click="searchText = ''">⨉</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="onSearchClick" id="search-btn" class="input btn mx-1 h-10">
|
<button id="search-btn" class="input btn mx-1 h-10" @click="onSearchClick">
|
||||||
<div class="i-fa6-solid:magnifying-glass"></div>
|
<div class="i-fa6-solid:magnifying-glass"></div>
|
||||||
</button>
|
</button>
|
||||||
<!-- three vertical lines for toggling the hamburger menu on mobile -->
|
<!-- three vertical lines for toggling the hamburger menu on mobile -->
|
||||||
@ -135,13 +135,6 @@ export default {
|
|||||||
registrationDisabled: false,
|
registrationDisabled: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.fetchAuthConfig();
|
|
||||||
const query = new URLSearchParams(window.location.search).get("search_query");
|
|
||||||
if (query) this.onSearchTextChange(query);
|
|
||||||
this.focusOnSearchBar();
|
|
||||||
this.homePagePath = this.getHomePage(this);
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
shouldShowLogin(_this) {
|
shouldShowLogin(_this) {
|
||||||
return _this.getAuthToken() == null;
|
return _this.getAuthToken() == null;
|
||||||
@ -159,6 +152,13 @@ export default {
|
|||||||
return _this.getPreferenceBoolean("searchHistory", false) && localStorage.getItem("search_history");
|
return _this.getPreferenceBoolean("searchHistory", false) && localStorage.getItem("search_history");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchAuthConfig();
|
||||||
|
const query = new URLSearchParams(window.location.search).get("search_query");
|
||||||
|
if (query) this.onSearchTextChange(query);
|
||||||
|
this.focusOnSearchBar();
|
||||||
|
this.homePagePath = this.getHomePage(this);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// focus on search bar when Ctrl+k is pressed
|
// focus on search bar when Ctrl+k is pressed
|
||||||
focusOnSearchBar() {
|
focusOnSearchBar() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-center items-center min-h-[88vh]">
|
<div class="flex flex-col justify-center items-center min-h-[88vh]">
|
||||||
<h1 class="font-bold !text-9xl">404</h1>
|
<h1 class="font-bold !text-9xl">404</h1>
|
||||||
<h2 class="!text-2xl" v-t="'info.page_not_found'" />
|
<h2 v-t="'info.page_not_found'" class="!text-2xl" />
|
||||||
<a class="btn mt-16" href="/" v-t="'actions.back_to_home'" />
|
<a v-t="'actions.back_to_home'" class="btn mt-16" href="/" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<ModalComponent>
|
<ModalComponent>
|
||||||
<span class="text-2xl w-max inline-block" v-t="'actions.select_playlist'" />
|
<span v-t="'actions.select_playlist'" class="text-2xl w-max inline-block" />
|
||||||
<select class="select w-full mt-3" v-model="selectedPlaylist">
|
<select v-model="selectedPlaylist" class="select w-full mt-3">
|
||||||
<option v-for="playlist in playlists" :value="playlist.id" :key="playlist.id" v-text="playlist.name" />
|
<option v-for="playlist in playlists" :key="playlist.id" :value="playlist.id" v-text="playlist.name" />
|
||||||
</select>
|
</select>
|
||||||
<div class="flex justify-between w-full mt-3">
|
<div class="flex justify-between w-full mt-3">
|
||||||
<button class="btn" @click="onCreatePlaylist" ref="addButton" v-t="'actions.create_playlist'" />
|
<button ref="addButton" v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
|
||||||
<button
|
<button
|
||||||
class="btn"
|
|
||||||
@click="handleClick(selectedPlaylist)"
|
|
||||||
ref="addButton"
|
ref="addButton"
|
||||||
v-t="'actions.add_to_playlist'"
|
v-t="'actions.add_to_playlist'"
|
||||||
|
class="btn"
|
||||||
|
@click="handleClick(selectedPlaylist)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ModalComponent>
|
</ModalComponent>
|
||||||
@ -33,6 +33,7 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ["close"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
playlists: [],
|
playlists: [],
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<span v-text="props.item.name" />
|
<span v-text="props.item.name" />
|
||||||
<font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
|
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
<p v-if="props.item.description" v-text="props.item.description" />
|
<p v-if="props.item.description" v-text="props.item.description" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
|
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="props.item.uploaderName" />
|
<span v-text="props.item.uploaderName" />
|
||||||
<font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
|
<font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
|
<a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
|
||||||
@ -30,6 +30,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: Object,
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
|
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
|
||||||
|
|
||||||
<LoadingIndicatorPage :show-content="playlist" v-show="!playlist?.error">
|
<LoadingIndicatorPage v-show="!playlist?.error" :show-content="playlist">
|
||||||
<h1 class="ml-1 mb-1 mt-4 text-3xl!" v-text="playlist.name" />
|
<h1 class="ml-1 mb-1 mt-4 text-3xl!" v-text="playlist.name" />
|
||||||
|
|
||||||
<CollapsableText :text="playlist.description" />
|
<CollapsableText :text="playlist.description" />
|
||||||
@ -14,12 +14,12 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" class="mr-2" />
|
<strong class="mr-2" v-text="`${playlist.videos} ${$t('video.videos')}`" />
|
||||||
<button class="btn mx-1" v-if="!isPipedPlaylist" @click="bookmarkPlaylist">
|
<button v-if="!isPipedPlaylist" class="btn mx-1" @click="bookmarkPlaylist">
|
||||||
{{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
|
{{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
|
||||||
}}<font-awesome-icon class="ml-3" icon="bookmark" />
|
}}<font-awesome-icon class="ml-3" icon="bookmark" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn mr-1" v-if="authenticated && !isPipedPlaylist" @click="clonePlaylist">
|
<button v-if="authenticated && !isPipedPlaylist" class="btn mr-1" @click="clonePlaylist">
|
||||||
{{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
|
{{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn mr-1" @click="downloadPlaylistAsTxt">
|
<button class="btn mr-1" @click="downloadPlaylistAsTxt">
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<a class="btn mr-1" :href="getRssUrl">
|
<a class="btn mr-1" :href="getRssUrl">
|
||||||
<font-awesome-icon icon="rss" />
|
<font-awesome-icon icon="rss" />
|
||||||
</a>
|
</a>
|
||||||
<WatchOnButton :link="`https://www.youtube.com/playlist?list=${this.$route.query.list}`" />
|
<WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -42,9 +42,9 @@
|
|||||||
:index="index"
|
:index="index"
|
||||||
:playlist-id="$route.query.list"
|
:playlist-id="$route.query.list"
|
||||||
:admin="admin"
|
:admin="admin"
|
||||||
@remove="removeVideo(index)"
|
|
||||||
height="94"
|
height="94"
|
||||||
width="168"
|
width="168"
|
||||||
|
@remove="removeVideo(index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</LoadingIndicatorPage>
|
</LoadingIndicatorPage>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-x-scroll h-screen-sm" ref="scrollable">
|
<div ref="scrollable" class="overflow-x-scroll h-screen-sm">
|
||||||
<VideoItem
|
<VideoItem
|
||||||
v-for="(related, index) in playlist.relatedStreams"
|
v-for="(related, index) in playlist.relatedStreams"
|
||||||
:key="related.url"
|
:key="related.url"
|
||||||
@ -28,6 +28,18 @@ export default {
|
|||||||
},
|
},
|
||||||
selectedIndex: {
|
selectedIndex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
playlist: {
|
||||||
|
handler() {
|
||||||
|
if (this.selectedIndex - 1 < this.playlist.relatedStreams.length)
|
||||||
|
nextTick(() => {
|
||||||
|
this.updateScroll();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -43,16 +55,5 @@ export default {
|
|||||||
elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
|
elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
playlist: {
|
|
||||||
handler() {
|
|
||||||
if (this.selectedIndex - 1 < this.playlist.relatedStreams.length)
|
|
||||||
nextTick(() => {
|
|
||||||
this.updateScroll();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<h2 class="font-bold my-4" v-t="'titles.playlists'" />
|
<h2 v-t="'titles.playlists'" class="font-bold my-4" />
|
||||||
|
|
||||||
<div class="flex justify-between mb-3">
|
<div class="flex justify-between mb-3">
|
||||||
<button v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
|
<button v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button
|
<button v-if="playlists.length > 0" v-t="'actions.export_to_json'" class="btn" @click="exportPlaylists" />
|
||||||
v-if="this.playlists.length > 0"
|
|
||||||
v-t="'actions.export_to_json'"
|
|
||||||
class="btn"
|
|
||||||
@click="exportPlaylists"
|
|
||||||
/>
|
|
||||||
<input
|
<input
|
||||||
id="fileSelector"
|
id="fileSelector"
|
||||||
ref="fileSelector"
|
ref="fileSelector"
|
||||||
type="file"
|
type="file"
|
||||||
class="display-none"
|
class="display-none"
|
||||||
@change="importPlaylists"
|
|
||||||
multiple="multiple"
|
multiple="multiple"
|
||||||
|
@change="importPlaylists"
|
||||||
/>
|
/>
|
||||||
<label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" />
|
<label v-t="'actions.import_from_json'" for="fileSelector" class="btn ml-2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -39,24 +34,24 @@
|
|||||||
v-text="playlist.name"
|
v-text="playlist.name"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<button class="btn h-auto" @click="showPlaylistEditModal(playlist)" v-t="'actions.edit_playlist'" />
|
<button v-t="'actions.edit_playlist'" class="btn h-auto" @click="showPlaylistEditModal(playlist)" />
|
||||||
<button class="btn h-auto ml-2" @click="playlistToDelete = playlist.id" v-t="'actions.delete_playlist'" />
|
<button v-t="'actions.delete_playlist'" class="btn h-auto ml-2" @click="playlistToDelete = playlist.id" />
|
||||||
<ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
|
<ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h2 v-t="'actions.edit_playlist'" />
|
<h2 v-t="'actions.edit_playlist'" />
|
||||||
<input
|
<input
|
||||||
|
v-model="newPlaylistName"
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="newPlaylistName"
|
|
||||||
:placeholder="$t('actions.playlist_name')"
|
:placeholder="$t('actions.playlist_name')"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
v-model="newPlaylistDescription"
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="newPlaylistDescription"
|
|
||||||
:placeholder="$t('actions.playlist_description')"
|
:placeholder="$t('actions.playlist_description')"
|
||||||
/>
|
/>
|
||||||
<button class="btn ml-auto" @click="editPlaylist(playlist)" v-t="'actions.okay'" />
|
<button v-t="'actions.okay'" class="btn ml-auto" @click="editPlaylist(playlist)" />
|
||||||
</div>
|
</div>
|
||||||
</ModalComponent>
|
</ModalComponent>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@ -69,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<h2 class="font-bold my-4" v-t="'titles.bookmarks'" />
|
<h2 v-t="'titles.bookmarks'" class="font-bold my-4" />
|
||||||
|
|
||||||
<div v-if="bookmarks" class="video-grid">
|
<div v-if="bookmarks" class="video-grid">
|
||||||
<router-link
|
<router-link
|
||||||
@ -104,6 +99,7 @@ import ConfirmModal from "./ConfirmModal.vue";
|
|||||||
import ModalComponent from "./ModalComponent.vue";
|
import ModalComponent from "./ModalComponent.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { ConfirmModal, ModalComponent },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
playlists: [],
|
playlists: [],
|
||||||
@ -250,6 +246,5 @@ export default {
|
|||||||
this.bookmarks.splice(index, 1);
|
this.bookmarks.splice(index, 1);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { ConfirmModal, ModalComponent },
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button @click="$router.go(-1) || $router.push('/')">
|
<button @click="$router.go(-1) || $router.push('/')">
|
||||||
<font-awesome-icon icon="chevron-left" /><span class="ml-1.5" v-t="'actions.back'" />
|
<font-awesome-icon icon="chevron-left" /><span v-t="'actions.back'" class="ml-1.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 v-t="'titles.preferences'" class="font-bold text-center" />
|
<h1 v-t="'titles.preferences'" class="font-bold text-center" />
|
||||||
@ -34,7 +34,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<h2 class="text-center" v-t="'titles.player'" />
|
<h2 v-t="'titles.player'" class="text-center" />
|
||||||
<label class="pref" for="chkAutoPlayVideo">
|
<label class="pref" for="chkAutoPlayVideo">
|
||||||
<strong v-t="'actions.autoplay_video'" />
|
<strong v-t="'actions.autoplay_video'" />
|
||||||
<input
|
<input
|
||||||
@ -223,7 +223,7 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div v-if="sponsorBlock">
|
<div v-if="sponsorBlock">
|
||||||
<label v-for="[name, item] in skipOptions" class="pref" :for="'ddlSkip_' + name" :key="name">
|
<label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
|
||||||
<strong v-t="item.label" />
|
<strong v-t="item.label" />
|
||||||
<select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
|
<select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
|
||||||
<option v-t="'actions.no'" value="no" />
|
<option v-t="'actions.no'" value="no" />
|
||||||
@ -253,7 +253,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-center" v-t="'titles.dearrow'" />
|
<h2 v-t="'titles.dearrow'" class="text-center" />
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
|
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
|
||||||
</p>
|
</p>
|
||||||
@ -262,7 +262,7 @@
|
|||||||
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<h2 class="text-center" v-t="'titles.instance'" />
|
<h2 v-t="'titles.instance'" class="text-center" />
|
||||||
<label class="pref" for="ddlInstanceSelection">
|
<label class="pref" for="ddlInstanceSelection">
|
||||||
<strong v-text="`${$t('actions.instance_selection')}:`" />
|
<strong v-text="`${$t('actions.instance_selection')}:`" />
|
||||||
<select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
|
<select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
|
||||||
@ -305,8 +305,8 @@
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<!-- options that are visible only when logged in -->
|
<!-- options that are visible only when logged in -->
|
||||||
<div v-if="this.authenticated">
|
<div v-if="authenticated">
|
||||||
<h2 class="text-center" v-t="'titles.account'"></h2>
|
<h2 v-t="'titles.account'" class="text-center"></h2>
|
||||||
<label class="pref" for="txtDeleteAccountPassword">
|
<label class="pref" for="txtDeleteAccountPassword">
|
||||||
<strong v-t="'actions.delete_account'" />
|
<strong v-t="'actions.delete_account'" />
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@ -314,22 +314,22 @@
|
|||||||
id="txtDeleteAccountPassword"
|
id="txtDeleteAccountPassword"
|
||||||
ref="txtDeleteAccountPassword"
|
ref="txtDeleteAccountPassword"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
v-on:keyup.enter="deleteAccount"
|
|
||||||
:placeholder="$t('login.password')"
|
:placeholder="$t('login.password')"
|
||||||
:aria-label="$t('login.password')"
|
:aria-label="$t('login.password')"
|
||||||
class="input w-auto mr-2"
|
class="input w-auto mr-2"
|
||||||
type="password"
|
type="password"
|
||||||
|
@keyup.enter="deleteAccount"
|
||||||
/>
|
/>
|
||||||
<a class="btn w-auto" @click="deleteAccount" v-t="'actions.delete_account'" />
|
<a v-t="'actions.delete_account'" class="btn w-auto" @click="deleteAccount" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div class="pref">
|
<div class="pref">
|
||||||
<a class="btn w-auto" @click="logout" v-t="'actions.logout'" />
|
<a v-t="'actions.logout'" class="btn w-auto" @click="logout" />
|
||||||
<a
|
<a
|
||||||
|
v-t="'actions.invalidate_session'"
|
||||||
class="btn w-auto"
|
class="btn w-auto"
|
||||||
style="margin-left: 0.5em"
|
style="margin-left: 0.5em"
|
||||||
@click="invalidateSession"
|
@click="invalidateSession"
|
||||||
v-t="'actions.invalidate_session'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@ -342,7 +342,7 @@
|
|||||||
<th v-t="'preferences.instance_locations'" />
|
<th v-t="'preferences.instance_locations'" />
|
||||||
<th v-t="'preferences.has_cdn'" />
|
<th v-t="'preferences.has_cdn'" />
|
||||||
<th v-t="'preferences.registered_users'" />
|
<th v-t="'preferences.registered_users'" />
|
||||||
<th class="lt-md:hidden" v-t="'preferences.version'" />
|
<th v-t="'preferences.version'" class="lt-md:hidden" />
|
||||||
<th v-t="'preferences.up_to_date'" />
|
<th v-t="'preferences.up_to_date'" />
|
||||||
<th v-t="'preferences.ssl_score'" />
|
<th v-t="'preferences.ssl_score'" />
|
||||||
</tr>
|
</tr>
|
||||||
@ -356,7 +356,7 @@
|
|||||||
<td class="lt-md:hidden" v-text="instance.version" />
|
<td class="lt-md:hidden" v-text="instance.version" />
|
||||||
<td v-text="`${instance.up_to_date ? '✅' : '❌'}`" />
|
<td v-text="`${instance.up_to_date ? '✅' : '❌'}`" />
|
||||||
<td>
|
<td>
|
||||||
<a :href="sslScore(instance.api_url)" target="_blank" v-t="'actions.view_ssl_score'" />
|
<a v-t="'actions.view_ssl_score'" :href="sslScore(instance.api_url)" target="_blank" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -364,15 +364,15 @@
|
|||||||
<br />
|
<br />
|
||||||
<p v-t="'info.preferences_note'" />
|
<p v-t="'info.preferences_note'" />
|
||||||
<br />
|
<br />
|
||||||
<button class="btn" v-t="'actions.reset_preferences'" @click="showConfirmResetPrefsDialog = true" />
|
<button v-t="'actions.reset_preferences'" class="btn" @click="showConfirmResetPrefsDialog = true" />
|
||||||
<button class="btn mx-4" v-t="'actions.backup_preferences'" @click="backupPreferences()" />
|
<button v-t="'actions.backup_preferences'" class="btn mx-4" @click="backupPreferences()" />
|
||||||
<label for="fileSelector" class="btn" v-t="'actions.restore_preferences'" @click="restorePreferences()" />
|
<label v-t="'actions.restore_preferences'" for="fileSelector" class="btn" @click="restorePreferences()" />
|
||||||
<input class="hidden" id="fileSelector" ref="fileSelector" type="file" @change="restorePreferences()" />
|
<input id="fileSelector" ref="fileSelector" class="hidden" type="file" @change="restorePreferences()" />
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="showConfirmResetPrefsDialog"
|
v-if="showConfirmResetPrefsDialog"
|
||||||
|
:message="$t('actions.confirm_reset_preferences')"
|
||||||
@close="showConfirmResetPrefsDialog = false"
|
@close="showConfirmResetPrefsDialog = false"
|
||||||
@confirm="resetPreferences()"
|
@confirm="resetPreferences()"
|
||||||
:message="$t('actions.confirm_reset_preferences')"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -380,6 +380,9 @@
|
|||||||
import CountryMap from "@/utils/CountryMaps/en.json";
|
import CountryMap from "@/utils/CountryMaps/en.json";
|
||||||
import ConfirmModal from "./ConfirmModal.vue";
|
import ConfirmModal from "./ConfirmModal.vue";
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
ConfirmModal,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mobileChapterLayout: "Vertical",
|
mobileChapterLayout: "Vertical",
|
||||||
@ -670,9 +673,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
ConfirmModal,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
:placeholder="$t('login.username')"
|
:placeholder="$t('login.username')"
|
||||||
:aria-label="$t('login.username')"
|
:aria-label="$t('login.username')"
|
||||||
v-on:keyup.enter="register"
|
@keyup.enter="register"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -22,23 +22,23 @@
|
|||||||
autocomplete="password"
|
autocomplete="password"
|
||||||
:placeholder="$t('login.password')"
|
:placeholder="$t('login.password')"
|
||||||
:aria-label="$t('login.password')"
|
:aria-label="$t('login.password')"
|
||||||
v-on:keyup.enter="register"
|
@keyup.enter="register"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn w-auto" @click="register" v-t="'titles.register'" />
|
<a v-t="'titles.register'" class="btn w-auto" @click="register" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="showUnsecureRegisterDialog"
|
v-if="showUnsecureRegisterDialog"
|
||||||
|
:message="$t('info.register_no_email_note')"
|
||||||
@close="showUnsecureRegisterDialog = false"
|
@close="showUnsecureRegisterDialog = false"
|
||||||
@confirm="
|
@confirm="
|
||||||
forceUnsecureRegister = true;
|
forceUnsecureRegister = true;
|
||||||
showUnsecureRegisterDialog = false;
|
showUnsecureRegisterDialog = false;
|
||||||
register();
|
register();
|
||||||
"
|
"
|
||||||
:message="$t('info.register_no_email_note')"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -47,6 +47,7 @@ import { isEmail } from "../utils/Misc.js";
|
|||||||
import ConfirmModal from "./ConfirmModal.vue";
|
import ConfirmModal from "./ConfirmModal.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { ConfirmModal },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
username: null,
|
username: null,
|
||||||
@ -85,6 +86,5 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { ConfirmModal },
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<strong v-text="`${$t('actions.filter')}:`" />
|
<strong v-text="`${$t('actions.filter')}:`" />
|
||||||
</label>
|
</label>
|
||||||
<select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
|
<select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
|
||||||
<option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`search.${filter}`" />
|
<option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -3,26 +3,26 @@
|
|||||||
<h2 v-t="'actions.share'" />
|
<h2 v-t="'actions.share'" />
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<label v-t="'actions.piped_link'" />
|
<label v-t="'actions.piped_link'" />
|
||||||
<input type="checkbox" v-model="pipedLink" @change="onChange" />
|
<input v-model="pipedLink" type="checkbox" @change="onChange" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="this.hasPlaylist" class="flex justify-between">
|
<div v-if="hasPlaylist" class="flex justify-between">
|
||||||
<label v-t="'actions.with_playlist'" />
|
<label v-t="'actions.with_playlist'" />
|
||||||
<input type="checkbox" v-model="withPlaylist" @change="onChange" />
|
<input v-model="withPlaylist" type="checkbox" @change="onChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<label v-t="'actions.with_timecode'" for="withTimeCode" />
|
<label v-t="'actions.with_timecode'" for="withTimeCode" />
|
||||||
<input id="withTimeCode" type="checkbox" v-model="withTimeCode" @change="onChange" />
|
<input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="this.withTimeCode" class="flex justify-between mt-2">
|
<div v-if="withTimeCode" class="flex justify-between mt-2">
|
||||||
<label v-t="'actions.time_code'" />
|
<label v-t="'actions.time_code'" />
|
||||||
<input class="input w-12" type="text" v-model="timeStamp" />
|
<input v-model="timeStamp" class="input w-12" type="text" />
|
||||||
</div>
|
</div>
|
||||||
<a :href="generatedLink" target="_blank">
|
<a :href="generatedLink" target="_blank">
|
||||||
<h3 class="mt-4" v-text="generatedLink" />
|
<h3 class="mt-4" v-text="generatedLink" />
|
||||||
</a>
|
</a>
|
||||||
<div class="flex justify-end mt-4">
|
<div class="flex justify-end mt-4">
|
||||||
<button class="btn" v-t="'actions.follow_link'" @click="followLink()" />
|
<button v-t="'actions.follow_link'" class="btn" @click="followLink()" />
|
||||||
<button class="btn ml-3" v-t="'actions.copy_link'" @click="copyLink()" />
|
<button v-t="'actions.copy_link'" class="btn ml-3" @click="copyLink()" />
|
||||||
</div>
|
</div>
|
||||||
</ModalComponent>
|
</ModalComponent>
|
||||||
</template>
|
</template>
|
||||||
@ -31,6 +31,9 @@
|
|||||||
import ModalComponent from "./ModalComponent.vue";
|
import ModalComponent from "./ModalComponent.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
ModalComponent,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
videoId: {
|
videoId: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -42,14 +45,13 @@ export default {
|
|||||||
},
|
},
|
||||||
playlistId: {
|
playlistId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
default: undefined,
|
||||||
},
|
},
|
||||||
playlistIndex: {
|
playlistIndex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
ModalComponent,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
withTimeCode: true,
|
withTimeCode: true,
|
||||||
@ -59,6 +61,20 @@ export default {
|
|||||||
hasPlaylist: false,
|
hasPlaylist: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
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);
|
||||||
|
if (this.hasPlaylist && this.withPlaylist) {
|
||||||
|
url.searchParams.append("list", this.playlistId);
|
||||||
|
url.searchParams.append("index", this.playlistIndex);
|
||||||
|
}
|
||||||
|
return url.href;
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.timeStamp = parseInt(this.currentTime);
|
this.timeStamp = parseInt(this.currentTime);
|
||||||
this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
|
this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
|
||||||
@ -87,19 +103,5 @@ export default {
|
|||||||
this.setPreference("shareWithPlaylist", this.withPlaylist, true);
|
this.setPreference("shareWithPlaylist", this.withPlaylist, true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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);
|
|
||||||
if (this.hasPlaylist && this.withPlaylist) {
|
|
||||||
url.searchParams.append("list", this.playlistId);
|
|
||||||
url.searchParams.append("index", this.playlistIndex);
|
|
||||||
}
|
|
||||||
return url.href;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<label for="ddlSortBy" v-t="'actions.sort_by'" />
|
<label v-t="'actions.sort_by'" for="ddlSortBy" />
|
||||||
<select id="ddlSortBy" v-model="selectedSort" class="select flex-grow">
|
<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" />
|
<option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -18,7 +18,10 @@ const options = {
|
|||||||
const selectedSort = ref("descending");
|
const selectedSort = ref("descending");
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
byKey: String,
|
byKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["apply"]);
|
const emit = defineEmits(["apply"]);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 class="font-bold text-center my-4" v-t="'titles.subscriptions'" />
|
<h1 v-t="'titles.subscriptions'" class="font-bold text-center my-4" />
|
||||||
<!-- import / export section -->
|
<!-- import / export section -->
|
||||||
<div class="flex justify-between w-full">
|
<div class="flex justify-between w-full">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button class="btn mx-1">
|
<button class="btn mx-1">
|
||||||
<router-link to="/import" v-t="'actions.import_from_json'" />
|
<router-link v-t="'actions.import_from_json'" to="/import" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" @click="exportHandler" v-t="'actions.export_to_json'" />
|
<button v-t="'actions.export_to_json'" class="btn" @click="exportHandler" />
|
||||||
</div>
|
</div>
|
||||||
<!-- subscriptions count, only shown if there are any -->
|
<!-- subscriptions count, only shown if there are any -->
|
||||||
<i18n-t v-if="subscriptions.length > 0" keypath="subscriptions.subscribed_channels_count">{{
|
<i18n-t v-if="subscriptions.length > 0" keypath="subscriptions.subscribed_channels_count">{{
|
||||||
@ -18,9 +18,9 @@
|
|||||||
<div class="w-full flex flex-wrap">
|
<div class="w-full flex flex-wrap">
|
||||||
<button
|
<button
|
||||||
v-for="group in channelGroups"
|
v-for="group in channelGroups"
|
||||||
|
:key="group.groupName"
|
||||||
class="btn mx-1 w-max"
|
class="btn mx-1 w-max"
|
||||||
:class="{ selected: selectedGroup === group }"
|
:class="{ selected: selectedGroup === group }"
|
||||||
:key="group.groupName"
|
|
||||||
@click="selectedGroup = group"
|
@click="selectedGroup = group"
|
||||||
>
|
>
|
||||||
<span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
|
<span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
|
||||||
@ -39,9 +39,9 @@
|
|||||||
<div class="xl:grid xl:grid-cols-5 <md:flex-wrap">
|
<div class="xl:grid xl:grid-cols-5 <md:flex-wrap">
|
||||||
<!-- channel info card -->
|
<!-- channel info card -->
|
||||||
<div
|
<div
|
||||||
class="col m-2 p-1 border rounded-lg border-gray-500"
|
|
||||||
v-for="subscription in filteredSubscriptions"
|
v-for="subscription in filteredSubscriptions"
|
||||||
:key="subscription.url"
|
:key="subscription.url"
|
||||||
|
class="col m-2 p-1 border rounded-lg border-gray-500"
|
||||||
>
|
>
|
||||||
<router-link :to="subscription.url" class="flex p-2 font-bold text-4x4">
|
<router-link :to="subscription.url" class="flex p-2 font-bold text-4x4">
|
||||||
<img :src="subscription.avatar" class="rounded-full h-[fit-content]" width="48" height="48" />
|
<img :src="subscription.avatar" class="rounded-full h-[fit-content]" width="48" height="48" />
|
||||||
@ -49,9 +49,9 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<!-- subscribe / unsubscribe btn -->
|
<!-- subscribe / unsubscribe btn -->
|
||||||
<button
|
<button
|
||||||
|
v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
|
||||||
class="btn w-full mt-2"
|
class="btn w-full mt-2"
|
||||||
@click="handleButton(subscription)"
|
@click="handleButton(subscription)"
|
||||||
v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,8 +60,8 @@
|
|||||||
<ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
|
<ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
|
||||||
<h2 v-t="'actions.create_group'" />
|
<h2 v-t="'actions.create_group'" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<input class="input my-4" type="text" v-model="newGroupName" :placeholder="$t('actions.group_name')" />
|
<input v-model="newGroupName" class="input my-4" type="text" :placeholder="$t('actions.group_name')" />
|
||||||
<button class="ml-auto btn w-max" v-t="'actions.create_group'" @click="createGroup()" />
|
<button v-t="'actions.create_group'" class="ml-auto btn w-max" @click="createGroup()" />
|
||||||
</div>
|
</div>
|
||||||
</ModalComponent>
|
</ModalComponent>
|
||||||
|
|
||||||
@ -88,6 +88,7 @@
|
|||||||
import ModalComponent from "./ModalComponent.vue";
|
import ModalComponent from "./ModalComponent.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { ModalComponent },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
@ -101,6 +102,13 @@ export default {
|
|||||||
newGroupName: "",
|
newGroupName: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
filteredSubscriptions(_this) {
|
||||||
|
return _this.selectedGroup.groupName == ""
|
||||||
|
? _this.subscriptions
|
||||||
|
: _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchSubscriptions().then(json => {
|
this.fetchSubscriptions().then(json => {
|
||||||
this.subscriptions = json;
|
this.subscriptions = json;
|
||||||
@ -201,14 +209,6 @@ export default {
|
|||||||
this.createOrUpdateChannelGroup(this.selectedGroup);
|
this.createOrUpdateChannelGroup(this.selectedGroup);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
filteredSubscriptions(_this) {
|
|
||||||
return _this.selectedGroup.groupName == ""
|
|
||||||
? _this.subscriptions
|
|
||||||
: _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { ModalComponent },
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="toast">
|
<div class="toast">
|
||||||
<slot />
|
<slot />
|
||||||
<button @click="dismiss" v-t="'actions.dismiss'" />
|
<button v-t="'actions.dismiss'" @click="dismiss" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
emits: ["dismissed"],
|
||||||
methods: {
|
methods: {
|
||||||
dismiss() {
|
dismiss() {
|
||||||
this.$emit("dismissed");
|
this.$emit("dismissed");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-justify-between" v-if="showVideo">
|
<div v-if="showVideo" class="flex flex-col flex-justify-between">
|
||||||
<router-link
|
<router-link
|
||||||
class="focus:underline hover:underline inline-block w-full"
|
class="focus:underline hover:underline inline-block w-full"
|
||||||
:to="{
|
:to="{
|
||||||
@ -22,8 +22,8 @@
|
|||||||
<!-- progress bar -->
|
<!-- progress bar -->
|
||||||
<div class="relative w-full h-1">
|
<div class="relative w-full h-1">
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 left-0 h-1 bg-red-600"
|
|
||||||
v-if="item.watched && item.duration > 0"
|
v-if="item.watched && item.duration > 0"
|
||||||
|
class="absolute bottom-0 left-0 h-1 bg-red-600"
|
||||||
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
|
:style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -31,21 +31,21 @@
|
|||||||
|
|
||||||
<div class="relative text-sm">
|
<div class="relative text-sm">
|
||||||
<span
|
<span
|
||||||
class="thumbnail-overlay thumbnail-right"
|
|
||||||
v-if="item.duration > 0"
|
v-if="item.duration > 0"
|
||||||
|
class="thumbnail-overlay thumbnail-right"
|
||||||
v-text="timeFormat(item.duration)"
|
v-text="timeFormat(item.duration)"
|
||||||
/>
|
/>
|
||||||
<!-- shorts thumbnail -->
|
<!-- shorts thumbnail -->
|
||||||
<span class="thumbnail-overlay thumbnail-left" v-if="item.isShort" v-t="'video.shorts'" />
|
<span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
|
||||||
<span
|
<span
|
||||||
class="thumbnail-overlay thumbnail-right"
|
|
||||||
v-else-if="item.duration >= 0"
|
v-else-if="item.duration >= 0"
|
||||||
|
class="thumbnail-overlay thumbnail-right"
|
||||||
v-text="timeFormat(item.duration)"
|
v-text="timeFormat(item.duration)"
|
||||||
/>
|
/>
|
||||||
<i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
|
<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>
|
</i18n-t>
|
||||||
<span v-if="item.watched" class="thumbnail-overlay bottom-5px left-5px" v-t="'video.watched'" />
|
<span v-if="item.watched" v-t="'video.watched'" class="thumbnail-overlay bottom-5px left-5px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -78,7 +78,7 @@
|
|||||||
:title="item.uploaderName"
|
:title="item.uploaderName"
|
||||||
>
|
>
|
||||||
<span v-text="item.uploaderName" />
|
<span v-text="item.uploaderName" />
|
||||||
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
|
<font-awesome-icon v-if="item.uploaderVerified" class="ml-1.5" icon="check" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div v-if="item.views >= 0 || item.uploadedDate" class="text-xs font-normal text-gray-300 mt-1">
|
<div v-if="item.views >= 0 || item.uploadedDate" class="text-xs font-normal text-gray-300 mt-1">
|
||||||
@ -112,17 +112,17 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="admin"
|
v-if="admin"
|
||||||
:title="$t('actions.remove_from_playlist')"
|
|
||||||
ref="removeButton"
|
ref="removeButton"
|
||||||
|
:title="$t('actions.remove_from_playlist')"
|
||||||
@click="showConfirmRemove = true"
|
@click="showConfirmRemove = true"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="circle-minus" />
|
<font-awesome-icon icon="circle-minus" />
|
||||||
</button>
|
</button>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="showConfirmRemove"
|
v-if="showConfirmRemove"
|
||||||
|
:message="$t('actions.delete_playlist_video_confirm')"
|
||||||
@close="showConfirmRemove = false"
|
@close="showConfirmRemove = false"
|
||||||
@confirm="removeVideo(item.url.substr(-11))"
|
@confirm="removeVideo(item.url.substr(-11))"
|
||||||
:message="$t('actions.delete_playlist_video_confirm')"
|
|
||||||
/>
|
/>
|
||||||
<PlaylistAddModal
|
<PlaylistAddModal
|
||||||
v-if="showModal"
|
v-if="showModal"
|
||||||
@ -135,17 +135,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
.shorts-img {
|
|
||||||
@apply w-full object-contain;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlaylistAddModal from "./PlaylistAddModal.vue";
|
import PlaylistAddModal from "./PlaylistAddModal.vue";
|
||||||
import ConfirmModal from "./ConfirmModal.vue";
|
import ConfirmModal from "./ConfirmModal.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { PlaylistAddModal, ConfirmModal },
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -164,6 +159,7 @@ export default {
|
|||||||
playlistId: { type: String, default: null },
|
playlistId: { type: String, default: null },
|
||||||
admin: { type: Boolean, default: false },
|
admin: { type: Boolean, default: false },
|
||||||
},
|
},
|
||||||
|
emits: ["remove"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showModal: false,
|
showModal: false,
|
||||||
@ -171,9 +167,6 @@ export default {
|
|||||||
showConfirmRemove: false,
|
showConfirmRemove: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.shouldShowVideo();
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
title() {
|
||||||
return this.item.dearrow?.titles[0]?.title ?? this.item.title;
|
return this.item.dearrow?.titles[0]?.title ?? this.item.title;
|
||||||
@ -182,6 +175,9 @@ export default {
|
|||||||
return this.item.dearrow?.thumbnails[0]?.thumbnail ?? this.item.thumbnail;
|
return this.item.dearrow?.thumbnails[0]?.thumbnail ?? this.item.thumbnail;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.shouldShowVideo();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeVideo() {
|
removeVideo() {
|
||||||
this.$refs.removeButton.disabled = true;
|
this.$refs.removeButton.disabled = true;
|
||||||
@ -204,6 +200,11 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { PlaylistAddModal, ConfirmModal },
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.shorts-img {
|
||||||
|
@apply w-full object-contain;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
>
|
>
|
||||||
<video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
|
<video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
|
||||||
<span
|
<span
|
||||||
ref="previewContainer"
|
|
||||||
id="preview-container"
|
id="preview-container"
|
||||||
|
ref="previewContainer"
|
||||||
class="hidden flex-col absolute bottom-0 z-[2000] mb-[3.5%] items-center"
|
class="hidden flex-col absolute bottom-0 z-[2000] mb-[3.5%] items-center"
|
||||||
>
|
>
|
||||||
<canvas ref="preview" id="preview" class="rounded-sm" />
|
<canvas id="preview" ref="preview" class="rounded-sm" />
|
||||||
<span v-text="timeFormat(currentTime)" class="text-sm mt-2 rounded-xl pb-1 pt-1.5 px-2 bg-dark-700 w-min" />
|
<span class="text-sm mt-2 rounded-xl pb-1 pt-1.5 px-2 bg-dark-700 w-min" v-text="timeFormat(currentTime)" />
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
v-if="inSegment"
|
v-if="inSegment"
|
||||||
@ -57,7 +57,7 @@ export default {
|
|||||||
selectedAutoLoop: Boolean,
|
selectedAutoLoop: Boolean,
|
||||||
isEmbed: Boolean,
|
isEmbed: Boolean,
|
||||||
},
|
},
|
||||||
emits: ["timeupdate"],
|
emits: ["timeupdate", "ended"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
lastUpdate: new Date().getTime(),
|
lastUpdate: new Date().getTime(),
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
link: String,
|
link: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
platform: {
|
platform: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@ -12,7 +15,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="this.getPreferenceBoolean('showWatchOnYouTube', false)">
|
<template v-if="getPreferenceBoolean('showWatchOnYouTube', false)">
|
||||||
<!-- For large screens -->
|
<!-- For large screens -->
|
||||||
<a :href="link" class="btn lt-lg:hidden flex items-center">
|
<a :href="link" class="btn lt-lg:hidden flex items-center">
|
||||||
<i18n-t keypath="player.watch_on" tag="strong">{{ platform }}</i18n-t>
|
<i18n-t keypath="player.watch_on" tag="strong">{{ platform }}</i18n-t>
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
/>
|
/>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
<ChaptersBar
|
<ChaptersBar
|
||||||
:mobileLayout="isMobile"
|
|
||||||
v-if="video?.chapters?.length > 0 && showChapters"
|
v-if="video?.chapters?.length > 0 && showChapters"
|
||||||
|
:mobile-layout="isMobile"
|
||||||
:chapters="video.chapters"
|
:chapters="video.chapters"
|
||||||
:player-position="currentTime"
|
:player-position="currentTime"
|
||||||
@seek="navigate"
|
@seek="navigate"
|
||||||
@ -76,7 +76,7 @@
|
|||||||
video.uploader
|
video.uploader
|
||||||
}}</router-link>
|
}}</router-link>
|
||||||
<!-- Verified Badge -->
|
<!-- Verified Badge -->
|
||||||
<font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
|
<font-awesome-icon v-if="video.uploaderVerified" class="ml-1" icon="check" />
|
||||||
</div>
|
</div>
|
||||||
<PlaylistAddModal
|
<PlaylistAddModal
|
||||||
v-if="showModal"
|
v-if="showModal"
|
||||||
@ -98,20 +98,20 @@
|
|||||||
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
|
{{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn"
|
|
||||||
@click="subscribeHandler"
|
|
||||||
v-t="{
|
v-t="{
|
||||||
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
|
path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
|
||||||
args: { count: numberFormat(video.uploaderSubscriberCount) },
|
args: { count: numberFormat(video.uploaderSubscriberCount) },
|
||||||
}"
|
}"
|
||||||
|
class="btn"
|
||||||
|
@click="subscribeHandler"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
<!-- RSS Feed button -->
|
<!-- RSS Feed button -->
|
||||||
<a
|
<a
|
||||||
|
v-if="video.uploaderUrl"
|
||||||
aria-label="RSS feed"
|
aria-label="RSS feed"
|
||||||
title="RSS feed"
|
title="RSS feed"
|
||||||
role="button"
|
role="button"
|
||||||
v-if="video.uploaderUrl"
|
|
||||||
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
|
:href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="btn flex items-center"
|
class="btn flex items-center"
|
||||||
@ -147,14 +147,14 @@
|
|||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-t="`actions.${showDesc ? 'minimize_description' : 'show_description'}`"
|
||||||
class="btn mb-2"
|
class="btn mb-2"
|
||||||
@click="showDesc = !showDesc"
|
@click="showDesc = !showDesc"
|
||||||
v-t="`actions.${showDesc ? 'minimize_description' : 'show_description'}`"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span class="btn ml-2" v-show="video?.chapters?.length > 0">
|
<span v-show="video?.chapters?.length > 0" class="btn ml-2">
|
||||||
<input id="showChapters" type="checkbox" v-model="showChapters" />
|
<input id="showChapters" v-model="showChapters" type="checkbox" />
|
||||||
<label class="ml-2" for="showChapters" v-t="'actions.show_chapters'" />
|
<label v-t="'actions.show_chapters'" class="ml-2" for="showChapters" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
@ -192,10 +192,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!showComments" class="xl:col-span-4 sm:col-span-3"></div>
|
<div v-if="!showComments" class="xl:col-span-4 sm:col-span-3"></div>
|
||||||
<div v-else-if="!comments" class="xl:col-span-4 sm:col-span-3">
|
<div v-else-if="!comments" class="xl:col-span-4 sm:col-span-3">
|
||||||
<p class="text-center mt-8" v-t="'comment.loading'"></p>
|
<p v-t="'comment.loading'" class="text-center mt-8"></p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="comments.disabled" class="xl:col-span-4 sm:col-span-3">
|
<div v-else-if="comments.disabled" class="xl:col-span-4 sm:col-span-3">
|
||||||
<p class="text-center mt-8" v-t="'comment.disabled'"></p>
|
<p v-t="'comment.disabled'" class="text-center mt-8"></p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else ref="comments" class="xl:col-span-4 sm:col-span-3">
|
<div v-else ref="comments" class="xl:col-span-4 sm:col-span-3">
|
||||||
<CommentItem
|
<CommentItem
|
||||||
@ -215,9 +215,9 @@
|
|||||||
:selected-index="index"
|
:selected-index="index"
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
|
v-t="`actions.${showRecs ? 'minimize_recommendations' : 'show_recommendations'}`"
|
||||||
class="btn mb-2"
|
class="btn mb-2"
|
||||||
@click="showRecs = !showRecs"
|
@click="showRecs = !showRecs"
|
||||||
v-t="`actions.${showRecs ? 'minimize_recommendations' : 'show_recommendations'}`"
|
|
||||||
/>
|
/>
|
||||||
<hr v-show="showRecs" />
|
<hr v-show="showRecs" />
|
||||||
<div v-show="showRecs">
|
<div v-show="showRecs">
|
||||||
|
Loading…
Reference in New Issue
Block a user