mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	Fix eslint config and apply all fixes.
This commit is contained in:
		
							parent
							
								
									6c05f63bef
								
							
						
					
					
						commit
						301877e2e1
					
				
					 35 changed files with 308 additions and 285 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue