mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-11-14 20:28:23 +00:00
Fix Visual Bugs
This commit is contained in:
parent
240c419823
commit
941d974503
22
src/App.vue
22
src/App.vue
@ -1,18 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<NavBar />
|
||||
|
||||
<div class="flex-1">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :max="5">
|
||||
<component :is="Component" :key="$route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
<FooterComponent />
|
||||
</div>
|
||||
<FooterComponent />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
min-height: calc(var(--efy_100vh) - var(--efy_gap2));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*Radius*/
|
||||
input,
|
||||
.btn,
|
||||
@ -32,8 +36,7 @@ video {
|
||||
border-radius: var(--efy_radius) !important;
|
||||
}
|
||||
|
||||
/*Radius 0*/
|
||||
.video-grid img {
|
||||
.video-card .thumbnail {
|
||||
border-radius: var(--efy_radius) var(--efy_radius) 0 0;
|
||||
}
|
||||
|
||||
@ -61,14 +64,15 @@ video {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
margin: 0 0 5rem 0;
|
||||
padding: 0 10rem 5rem 10rem;
|
||||
line-height: 22rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.pp-video-card-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--efy_gap0);
|
||||
margin: 5rem 15rem 15rem 15rem;
|
||||
margin: 5rem 10rem 10rem 10rem;
|
||||
}
|
||||
.pp-video-card-buttons :is(a, button) {
|
||||
padding: 4rem 8rem;
|
||||
@ -113,7 +117,7 @@ video {
|
||||
gap: var(--efy_gap0);
|
||||
place-items: center;
|
||||
background: transparent;
|
||||
margin: var(--efy_gap0) 0 0;
|
||||
margin: var(--efy_gap0) 0 var(--efy_gap0) var(--efy_gap0);
|
||||
width: fit-content;
|
||||
}
|
||||
.pp-video-card-channel > a {
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
|
||||
<div v-if="channel" v-show="!channel.error">
|
||||
<div v-if="channel" v-show="!channel.error" class="mt-[15rem]">
|
||||
<LoadingIndicatorPage :show-content="channel != null && !channel.error">
|
||||
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
|
||||
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full efy_shadow_trans" loading="lazy" />
|
||||
<div class="pp-channel-page-author flex">
|
||||
<img height="48" width="48" class="m-1" :src="channel.avatarUrl" />
|
||||
<img height="48" width="48" class="efy_shadow_trans" :src="channel.avatarUrl" />
|
||||
<h5 v-text="channel.name" />
|
||||
<font-awesome-icon v-if="channel.verified" class="ml-1.5" icon="check" />
|
||||
</div>
|
||||
@ -71,6 +71,7 @@
|
||||
}
|
||||
.pp-channel-tabs :is(button, [role="button"]) {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<!-- desktop view -->
|
||||
<div v-if="!mobileLayout" class="pp-chapters flex-col overflow-y-scroll max-h-75vh min-h-64 lt-lg:hidden">
|
||||
<h6 aria-label="chapters" title="chapters" class="efy_trans_filter">
|
||||
<div v-if="!mobileLayout" class="pp-chapters flex-col max-h-75vh min-h-64 lt-lg:hidden">
|
||||
<h6 aria-label="chapters" title="chapters" class="efy_trans_filter efy_shadow_trans">
|
||||
{{ $t("video.chapters") }} - {{ chapters.length }}
|
||||
</h6>
|
||||
<div
|
||||
v-for="(chapter, index) in chapters"
|
||||
:key="chapter.start"
|
||||
class="chapter efy_anim_pulse efy_trans_filter"
|
||||
:class="{ 'pp-chapter-active': isCurrentChapter(index) }"
|
||||
class="chapter efy_anim_pulse"
|
||||
:class="isCurrentChapter(index) ? 'pp-chapter-active' : 'efy_shadow_trans efy_trans_filter'"
|
||||
:role="isCurrentChapter(index) ? 'button' : ''"
|
||||
@click="$emit('seek', chapter.start)"
|
||||
>
|
||||
<div class="flex">
|
||||
@ -77,10 +78,37 @@ defineEmits(["seek"]);
|
||||
.text-truncate {
|
||||
@apply truncate overflow-hidden inline-block w-10em;
|
||||
}
|
||||
.pp-chapters {
|
||||
margin: -15rem 0 0 0;
|
||||
padding: var(--efy_gap);
|
||||
max-width: 400rem;
|
||||
gap: var(--efy_gap0);
|
||||
border-radius: var(--efy_radius);
|
||||
overflow: auto;
|
||||
}
|
||||
.pp-chapters .chapter {
|
||||
padding: 10rem;
|
||||
border-radius: var(--efy_radius);
|
||||
border: var(--efy_border);
|
||||
}
|
||||
.pp-chapters [title="chapters"] {
|
||||
padding: 5rem 10rem;
|
||||
border-radius: var(--efy_radius);
|
||||
border: var(--efy_border);
|
||||
}
|
||||
.pp-chapters .chapter .flex {
|
||||
gap: 0 15rem;
|
||||
}
|
||||
.pp-chapters.pp-mobile {
|
||||
margin: 15rem 0 0 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.pp-chapter-active,
|
||||
.pp-chapters .chapter:hover {
|
||||
background: var(--efy_color);
|
||||
background-clip: padding-box;
|
||||
color: var(--efy_text2);
|
||||
border: 0 !important;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<hr />
|
||||
<div class="flex flex-wrap align-center" style="place-content: space-between">
|
||||
<div class="flex flex-wrap align-center" style="place-content: space-between; gap: var(--efy_gap0)">
|
||||
<span class="buttons flex" style="gap: var(--efy_gap0)">
|
||||
<router-link role="button" to="/subscriptions">Subscriptions</router-link>
|
||||
<a :href="getRssUrl" role="button" class="pp-square">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<footer class="efy_trans_filter">
|
||||
<footer class="efy_trans_filter efy_shadow_trans efy_shadow_button_off">
|
||||
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped" target="_blank">
|
||||
<font-awesome-icon :icon="['fab', 'github']" />
|
||||
<span v-t="'actions.source_code'" />
|
||||
@ -58,12 +58,13 @@ footer {
|
||||
padding: 15rem 5rem;
|
||||
border: var(--efy_border);
|
||||
}
|
||||
footer > a {
|
||||
margin: 0;
|
||||
}
|
||||
footer a {
|
||||
color: var(--efy_text) !important;
|
||||
-webkit-text-fill-color: var(--efy_text) !important;
|
||||
background: transparent !important;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: 8rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,19 +1,21 @@
|
||||
<template>
|
||||
<div class="flex place-items-center">
|
||||
<div class="flex flex-col gap-2 md:flex-row md:items-center">
|
||||
<button v-t="'actions.clear_history'" class="btn" @click="clearHistory" />
|
||||
|
||||
<button v-t="'actions.export_to_json'" class="btn" @click="exportHistory" />
|
||||
|
||||
<div class="ml-auto flex items-center gap-1">
|
||||
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex flex-wrap items-center place-content-between" style="gap: var(--efy_gap0)">
|
||||
<div class="flex" style="gap: var(--efy_gap0)">
|
||||
<button v-t="'actions.clear_history'" class="m-0" @click="clearHistory" />
|
||||
<button v-t="'actions.export_to_json'" class="m-0" @click="exportHistory" />
|
||||
</div>
|
||||
|
||||
<div class="ml-4 flex items-center">
|
||||
<div class="flex flex-wrap items-center" style="gap: var(--efy_gap0)">
|
||||
<div efy_select class="flex flex-wrap" style="gap: var(--efy_gap0)">
|
||||
<input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
|
||||
<label v-t="'actions.delete_automatically'" class="ml-2" for="autoDelete" />
|
||||
<select v-model="autoDeleteDelayHours" class="select ml-3 pl-3" @change="onChange">
|
||||
<label v-t="'actions.delete_automatically'" style="margin: 0" for="autoDelete" />
|
||||
<select
|
||||
v-model="autoDeleteDelayHours"
|
||||
class="w-auto"
|
||||
style="margin: 0 var(--efy_gap0) 0 0"
|
||||
@change="onChange"
|
||||
>
|
||||
<option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
|
||||
<option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
|
||||
<option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
|
||||
@ -26,6 +28,8 @@
|
||||
<option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
|
||||
</select>
|
||||
</div>
|
||||
<SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" style="gap: 0" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@ -33,8 +37,6 @@
|
||||
<div class="video-grid">
|
||||
<VideoItem v-for="video in videos" :key="video.url" :item="video" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<hr />
|
||||
<div>
|
||||
<form style="display: grid; gap: 15rem">
|
||||
<div>
|
||||
@ -11,9 +12,7 @@
|
||||
<input v-model="override" id="import-override" type="checkbox" />
|
||||
<label for="import-override">Override</label>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn w-auto" @click="handleImport">Import</a>
|
||||
</div>
|
||||
<a class="btn w-auto" @click="handleImport" role="button" style="margin: 0">Import</a>
|
||||
</form>
|
||||
<br />
|
||||
<strong>Importing Subscriptions from YouTube</strong>
|
||||
|
@ -121,13 +121,13 @@
|
||||
|
||||
<style>
|
||||
.pp-nav {
|
||||
margin-bottom: 15rem;
|
||||
gap: 15rem;
|
||||
}
|
||||
.pp-nav > .pp-logo > a {
|
||||
font-size: 25rem;
|
||||
font-family: "nunito";
|
||||
background: transparent;
|
||||
margin-left: 5rem;
|
||||
}
|
||||
.pp-nav > div input {
|
||||
margin: 0 !important;
|
||||
|
@ -1,30 +1,36 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-justify-between">
|
||||
<div class="video-card flex flex-col flex-justify-between efy_shadow_trans">
|
||||
<router-link :to="props.item.url">
|
||||
<div class="relative">
|
||||
<img class="w-full" :src="props.item.thumbnail" loading="lazy" />
|
||||
<img class="thumbnail" :src="props.item.thumbnail" loading="lazy" />
|
||||
</div>
|
||||
<p>
|
||||
<span v-text="props.item.name" />
|
||||
<span v-text="props.item.name" class="pp-video-card-title" />
|
||||
<font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
|
||||
</p>
|
||||
</router-link>
|
||||
<p v-if="props.item.description" v-text="props.item.description" />
|
||||
|
||||
<router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
|
||||
<p>
|
||||
<span v-text="props.item.uploaderName" />
|
||||
<font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
|
||||
</p>
|
||||
<div class="pp-video-card-buttons">
|
||||
<button
|
||||
v-if="props.item.videos >= 0"
|
||||
v-text="`${props.item.videos} ${$t('video.videos')}`"
|
||||
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
|
||||
/>
|
||||
<router-link
|
||||
v-if="props.item.uploaderUrl && item.uploaderName"
|
||||
:to="props.item.uploaderUrl"
|
||||
:title="props.item.uploaderName"
|
||||
class="pp-video-card-channel"
|
||||
style="padding: 0; flex-grow: 1; background: transparent; border: 0"
|
||||
>
|
||||
<div class="pp-text efy_shadow_trans efy_shadow_button_off flex-grow-1">
|
||||
<span v-text="props.item.uploaderName" style="max-width: 106rem" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
|
||||
</div>
|
||||
</router-link>
|
||||
<a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
|
||||
|
||||
<template v-if="props.item.videos >= 0">
|
||||
<br v-if="props.item.uploaderName" />
|
||||
<strong v-text="`${props.item.videos} ${$t('video.videos')}`" />
|
||||
</template>
|
||||
|
||||
<br />
|
||||
<a v-else-if="props.item.uploaderName" class="pp-video-card-channel" v-text="props.item.uploaderName" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,9 +1,18 @@
|
||||
<template>
|
||||
<hr />
|
||||
<div class="flex justify-between">
|
||||
<button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
|
||||
<div class="flex">
|
||||
<button v-if="playlists.length > 0" v-t="'actions.export_to_json'" @click="exportPlaylists" />
|
||||
<div class="flex justify-between items-center">
|
||||
<button
|
||||
v-t="'actions.create_playlist'"
|
||||
style="height: var(--efy_ratio_width); margin: 0"
|
||||
@click="onCreatePlaylist"
|
||||
/>
|
||||
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
|
||||
<button
|
||||
v-if="playlists.length > 0"
|
||||
v-t="'actions.export_to_json'"
|
||||
@click="exportPlaylists"
|
||||
style="height: var(--efy_ratio_width); margin: 0"
|
||||
/>
|
||||
<input
|
||||
id="fileSelector"
|
||||
ref="fileSelector"
|
||||
@ -12,14 +21,15 @@
|
||||
multiple="multiple"
|
||||
@change="importPlaylists"
|
||||
/>
|
||||
<label v-t="'actions.import_from_json_csv'" for="fileSelector" class="btn ml-2" role="button" />
|
||||
<label v-t="'actions.import_from_json_csv'" for="fileSelector" class="m-0! font-bold" role="button" />
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div class="video-grid">
|
||||
<div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
|
||||
<div v-for="playlist in playlists" :key="playlist.id" class="video-card efy_trans_filter">
|
||||
<router-link :to="`/playlist?list=${playlist.id}`">
|
||||
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
|
||||
<img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" />
|
||||
<p
|
||||
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 15rem"
|
||||
class="flex link"
|
||||
@ -68,32 +78,32 @@
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<h2 v-t="'titles.bookmarks'" class="my-4 font-bold" />
|
||||
|
||||
<h5 v-if="bookmarks" v-t="'titles.bookmarks'" class="mb-[15rem]" />
|
||||
<div v-if="bookmarks" class="video-grid">
|
||||
<router-link
|
||||
<div
|
||||
v-for="(playlist, index) in bookmarks"
|
||||
:key="playlist.playlistId"
|
||||
:to="`/playlist?list=${playlist.playlistId}`"
|
||||
class="pp-bookmark video-card efy_trans_filter"
|
||||
>
|
||||
<img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
|
||||
<div class="relative text-sm">
|
||||
<span class="thumbnail-overlay thumbnail-right" v-text="`${playlist.videos} ${$t('video.videos')}`" />
|
||||
<div class="absolute bottom-100px right-5px z-100 px-5px" @click.prevent="removeBookmark(index)">
|
||||
<font-awesome-icon class="ml-3" icon="bookmark" />
|
||||
<router-link :to="`/playlist?list=${playlist.playlistId}`">
|
||||
<img class="thumbnail" :src="playlist.thumbnail" alt="thumbnail" />
|
||||
<div class="flex items-center h-[44rem] overflow-hidden">
|
||||
<p class="pp-video-card-title" :title="playlist.name" v-text="playlist.name" />
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 0 0 10rem"
|
||||
class="link my-2 flex overflow-hidden"
|
||||
:title="playlist.name"
|
||||
v-text="playlist.name"
|
||||
/>
|
||||
<a :href="playlist.uploaderUrl" class="flex items-center">
|
||||
<img class="h-32px w-32px rounded-full" :src="playlist.uploaderAvatar" />
|
||||
<span class="ml-3 hover:underline" v-text="playlist.uploader" />
|
||||
</a>
|
||||
</router-link>
|
||||
<div class="pp-video-card-buttons flex gap-15rem">
|
||||
<button @click.prevent="removeBookmark(index)">
|
||||
<font-awesome-icon class="ml-3" icon="bookmark" />
|
||||
</button>
|
||||
<button v-text="`${playlist.videos} ${$t('video.videos')}`" class="thumbnail-overlay" />
|
||||
</div>
|
||||
<a :href="playlist.uploaderUrl" class="pp-video-card-channel">
|
||||
<img class="w-36rem h-36rem efy_shadow_trans" :src="playlist.uploaderAvatar" width="36" height="36" />
|
||||
<div class="pp-text efy_shadow_trans efy_shadow_button_off">
|
||||
<span v-text="playlist.uploader" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<!--efy-->
|
||||
<div class="pp-pref-cards">
|
||||
<div efy_card="grid">
|
||||
<h2>Quick</h2>
|
||||
<h5>Quick</h5>
|
||||
<label class="pref" for="ddlLanguageSelection">
|
||||
<strong v-t="'actions.language_selection'" />
|
||||
<select
|
||||
@ -86,56 +85,60 @@
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
<br />
|
||||
|
||||
<p v-t="'info.preferences_note'" />
|
||||
<button class="btn" v-t="'actions.reset_preferences'" @click="resetPreferences()" />
|
||||
<button class="btn mx-4" v-t="'actions.backup_preferences'" @click="backupPreferences()" />
|
||||
<label
|
||||
for="fileSelector"
|
||||
class="btn text-center"
|
||||
v-t="'actions.restore_preferences'"
|
||||
@click="restorePreferences()"
|
||||
<div class="pref items-start! flex-col">
|
||||
<strong>Preferences</strong>
|
||||
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
|
||||
<button style="height: var(--efy_ratio_width)" @click="showConfirmResetPrefsDialog = true">
|
||||
Reset
|
||||
</button>
|
||||
<button style="height: var(--efy_ratio_width)" @click="backupPreferences()">Backup</button>
|
||||
<label for="fileSelector" class="btn text-center" role="button" @click="restorePreferences()">
|
||||
Restore
|
||||
</label>
|
||||
<input
|
||||
id="fileSelector"
|
||||
ref="fileSelector"
|
||||
class="hidden"
|
||||
type="file"
|
||||
@change="restorePreferences()"
|
||||
/>
|
||||
</div>
|
||||
<p v-t="'info.preferences_note'" />
|
||||
<ConfirmModal
|
||||
v-if="showConfirmResetPrefsDialog"
|
||||
:message="$t('actions.confirm_reset_preferences')"
|
||||
@close="showConfirmResetPrefsDialog = false"
|
||||
@confirm="resetPreferences()"
|
||||
/>
|
||||
</div>
|
||||
<!--master-->
|
||||
<div class="flex">
|
||||
<button @click="$router.go(-1) || $router.push('/')">
|
||||
<font-awesome-icon icon="chevron-left" /><span v-t="'actions.back'" class="ml-1.5" />
|
||||
</button>
|
||||
<!-- options that are visible only when logged in -->
|
||||
<div v-if="authenticated" class="pref items-start! flex-col">
|
||||
<label v-t="'actions.delete_account'" for="txtDeleteAccountPassword" class="font-bold" />
|
||||
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
|
||||
<input
|
||||
id="txtDeleteAccountPassword"
|
||||
ref="txtDeleteAccountPassword"
|
||||
v-model="password"
|
||||
:placeholder="$t('login.password')"
|
||||
:aria-label="$t('login.password')"
|
||||
class="input mr-2 w-auto"
|
||||
type="password"
|
||||
@keyup.enter="deleteAccount"
|
||||
/>
|
||||
<button v-t="'actions.delete_account'" class="w-auto" @click="deleteAccount" />
|
||||
</div>
|
||||
<h1 v-t="'titles.preferences'" class="text-center font-bold" />
|
||||
<hr />
|
||||
<label for="ddlTheme" class="pref">
|
||||
<strong v-t="'actions.theme'" />
|
||||
<select id="ddlTheme" v-model="selectedTheme" class="select w-auto" @change="onChange($event)">
|
||||
<option v-t="'actions.auto'" value="auto" />
|
||||
<option v-t="'actions.dark'" value="dark" />
|
||||
<option v-t="'actions.light'" value="light" />
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="ddlLanguageSelection">
|
||||
<strong v-t="'actions.language_selection'" />
|
||||
<select id="ddlLanguageSelection" v-model="selectedLanguage" class="select w-auto" @change="onChange($event)">
|
||||
<option v-for="language in languages" :key="language.code" :value="language.code" v-text="language.name" />
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="ddlCountrySelection">
|
||||
<strong v-t="'actions.country_selection'" />
|
||||
<select id="ddlCountrySelection" v-model="countrySelected" class="select w-50" @change="onChange($event)">
|
||||
<option v-for="country in countryMap" :key="country.code" :value="country.code" v-text="country.name" />
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="ddlDefaultHomepage">
|
||||
<strong v-t="'actions.default_homepage'" />
|
||||
<select id="ddlDefaultHomepage" v-model="defaultHomepage" class="select w-auto" @change="onChange($event)">
|
||||
<option v-t="'titles.trending'" value="trending" />
|
||||
<option v-t="'titles.feed'" value="feed" />
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<h2 v-t="'titles.player'" class="text-center" />
|
||||
</div>
|
||||
<div v-if="authenticated" class="pref items-start! flex-col" style="border-bottom: var(--efy_border)">
|
||||
<strong>Logout</strong>
|
||||
<div class="flex flex-wrap" style="gap: var(--efy_gap0)">
|
||||
<button v-t="'actions.logout'" class="w-auto" @click="logout" />
|
||||
<button v-t="'actions.invalidate_session'" class="w-auto" @click="invalidateSession" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div efy_card="grid">
|
||||
<h5 v-t="'titles.player'" />
|
||||
<label class="pref" for="chkAutoPlayVideo">
|
||||
<strong v-t="'actions.autoplay_video'" />
|
||||
<input
|
||||
@ -172,9 +175,19 @@
|
||||
</label>
|
||||
<label class="pref" for="ddlDefaultQuality">
|
||||
<strong v-t="'actions.default_quality'" />
|
||||
<select id="ddlDefaultQuality" v-model="defaultQuality" class="select w-auto" @change="onChange($event)">
|
||||
<select
|
||||
id="ddlDefaultQuality"
|
||||
v-model="defaultQuality"
|
||||
class="select w-auto"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option v-t="'actions.auto'" value="0" />
|
||||
<option v-for="resolution in resolutions" :key="resolution" :value="resolution" v-text="`${resolution}p`" />
|
||||
<option
|
||||
v-for="resolution in resolutions"
|
||||
:key="resolution"
|
||||
:value="resolution"
|
||||
v-text="`${resolution}p`"
|
||||
/>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="txtBufferingGoal">
|
||||
@ -228,10 +241,15 @@
|
||||
/>
|
||||
</label>
|
||||
<!-- chapters layout on mobile -->
|
||||
<label class="pref lg:invisible" for="chkMinimizeChapters">
|
||||
<label class="pref" for="chkMinimizeChapters">
|
||||
<strong v-t="'actions.chapters_layout_mobile'" />
|
||||
|
||||
<select id="ddlDefaultHomepage" v-model="mobileChapterLayout" class="select w-auto" @change="onChange($event)">
|
||||
<select
|
||||
id="ddlDefaultHomepage"
|
||||
v-model="mobileChapterLayout"
|
||||
class="select w-auto"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option v-t="'video.chapters_horizontal'" value="Horizontal" />
|
||||
<option v-t="'video.chapters_vertical'" value="Vertical" />
|
||||
</select>
|
||||
@ -276,299 +294,6 @@
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label v-if="watchHistory" class="pref" for="chkHideWatched">
|
||||
<strong v-t="'actions.hide_watched'" />
|
||||
<input id="chkHideWatched" v-model="hideWatched" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||
</label>
|
||||
<label class="pref" for="ddlEnabledCodecs">
|
||||
<strong v-t="'actions.enabled_codecs'" />
|
||||
<select
|
||||
id="ddlEnabledCodecs"
|
||||
v-model="enabledCodecs"
|
||||
class="select h-auto w-auto"
|
||||
multiple
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option value="av1">AV1</option>
|
||||
<option value="vp9">VP9</option>
|
||||
<option value="avc">AVC (h.264)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="chkDisableLBRY">
|
||||
<strong v-t="'actions.disable_lbry'" />
|
||||
<input id="chkDisableLBRY" v-model="disableLBRY" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||
</label>
|
||||
<label class="pref" for="chkEnableLBRYProxy">
|
||||
<strong v-t="'actions.enable_lbry_proxy'" />
|
||||
<input
|
||||
id="chkEnableLBRYProxy"
|
||||
v-model="proxyLBRY"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<h2 class="text-center">SponsorBlock</h2>
|
||||
<p class="text-center">
|
||||
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
|
||||
</p>
|
||||
<label class="pref" for="chkEnableSponsorblock">
|
||||
<strong v-t="'actions.enable_sponsorblock'" />
|
||||
<input
|
||||
id="chkEnableSponsorblock"
|
||||
v-model="sponsorBlock"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<div v-if="sponsorBlock">
|
||||
<label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
|
||||
<strong v-t="item.label" />
|
||||
<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.skip_button_only'" value="button" />
|
||||
<option v-t="'actions.skip_automatically'" value="auto" />
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="chkShowMarkers">
|
||||
<strong v-t="'actions.show_markers'" />
|
||||
<input
|
||||
id="chkShowMarkers"
|
||||
v-model="showMarkers"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="txtMinSegmentLength">
|
||||
<strong v-t="'actions.min_segment_length'" />
|
||||
<input
|
||||
id="txtMinSegmentLength"
|
||||
v-model="minSegmentLength"
|
||||
class="input w-24"
|
||||
type="text"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h2 v-t="'titles.dearrow'" 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>
|
||||
</p>
|
||||
<label class="pref" for="chkDeArrow">
|
||||
<strong v-t="'actions.enable_dearrow'" />
|
||||
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||
</label>
|
||||
|
||||
<h2 v-t="'titles.instance'" class="text-center" />
|
||||
<label class="pref" for="ddlInstanceSelection">
|
||||
<strong v-text="`${$t('actions.instance_selection')}:`" />
|
||||
<select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
|
||||
<option
|
||||
v-for="instance in instances"
|
||||
:key="instance.name"
|
||||
:value="instance.api_url"
|
||||
v-text="instance.name"
|
||||
/>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="chkAuthInstance">
|
||||
<strong v-text="`${$t('actions.different_auth_instance')}:`" />
|
||||
<input
|
||||
id="chkAuthInstance"
|
||||
v-model="authInstance"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<template v-if="authInstance">
|
||||
<label class="pref" for="ddlAuthInstanceSelection">
|
||||
<strong v-text="`${$t('actions.instance_auth_selection')}:`" />
|
||||
<select
|
||||
id="ddlAuthInstanceSelection"
|
||||
v-model="selectedAuthInstance"
|
||||
class="select w-auto"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="instance in instances"
|
||||
:key="instance.name"
|
||||
:value="instance.api_url"
|
||||
v-text="instance.name"
|
||||
/>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
<br />
|
||||
|
||||
<!-- options that are visible only when logged in -->
|
||||
<div v-if="authenticated">
|
||||
<h2 v-t="'titles.account'" class="text-center"></h2>
|
||||
<label class="pref" for="txtDeleteAccountPassword">
|
||||
<strong v-t="'actions.delete_account'" />
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="txtDeleteAccountPassword"
|
||||
ref="txtDeleteAccountPassword"
|
||||
v-model="password"
|
||||
:placeholder="$t('login.password')"
|
||||
:aria-label="$t('login.password')"
|
||||
class="input mr-2 w-auto"
|
||||
type="password"
|
||||
@keyup.enter="deleteAccount"
|
||||
/>
|
||||
<a v-t="'actions.delete_account'" class="btn w-auto" @click="deleteAccount" />
|
||||
</div>
|
||||
</label>
|
||||
<div class="pref">
|
||||
<a v-t="'actions.logout'" class="btn w-auto" @click="logout" />
|
||||
<a
|
||||
v-t="'actions.invalidate_session'"
|
||||
class="btn w-auto"
|
||||
style="margin-left: 0.5em"
|
||||
@click="invalidateSession"
|
||||
/>
|
||||
<!--masterend-->
|
||||
<input class="hidden" id="fileSelector" ref="fileSelector" type="file" @change="restorePreferences()" />
|
||||
|
||||
<!-- options that are visible only when logged in -->
|
||||
<div v-if="this.authenticated">
|
||||
<label class="pp-delete-account pref" for="txtDeleteAccountPassword" efy_card="grid">
|
||||
<h6 v-t="'actions.delete_account'" />
|
||||
<input
|
||||
id="txtDeleteAccountPassword"
|
||||
ref="txtDeleteAccountPassword"
|
||||
v-model="password"
|
||||
v-on:keyup.enter="deleteAccount"
|
||||
:placeholder="$t('login.password')"
|
||||
:aria-label="$t('login.password')"
|
||||
class="input w-auto mr-2"
|
||||
type="password"
|
||||
/>
|
||||
<a class="btn w-full" @click="deleteAccount" v-t="'actions.delete_account'" />
|
||||
</label>
|
||||
<button class="btn w-full" @click="logout" v-t="'actions.logout'" />
|
||||
<button class="btn w-full" @click="invalidateSession" v-t="'actions.invalidate_session'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div efy_card="grid">
|
||||
<h2 v-t="'titles.player'" />
|
||||
<label class="pref" for="chkAutoPlayVideo">
|
||||
<strong v-t="'actions.autoplay_video'" />
|
||||
<input
|
||||
id="chkAutoPlayVideo"
|
||||
v-model="autoPlayVideo"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkAudioOnly">
|
||||
<strong v-t="'actions.audio_only'" />
|
||||
<input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||
</label>
|
||||
<label class="pref" for="ddlDefaultQuality">
|
||||
<strong v-t="'actions.default_quality'" />
|
||||
<select
|
||||
id="ddlDefaultQuality"
|
||||
v-model="defaultQuality"
|
||||
class="select w-auto"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option v-t="'actions.auto'" value="0" />
|
||||
<option
|
||||
v-for="resolution in resolutions"
|
||||
:key="resolution"
|
||||
:value="resolution"
|
||||
v-text="`${resolution}p`"
|
||||
/>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="txtBufferingGoal">
|
||||
<strong v-t="'actions.buffering_goal'" />
|
||||
<input
|
||||
id="txtBufferingGoal"
|
||||
v-model="bufferingGoal"
|
||||
class="input w-auto"
|
||||
type="text"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkMinimizeComments">
|
||||
<strong v-t="'actions.minimize_comments_default'" />
|
||||
<input
|
||||
id="chkMinimizeComments"
|
||||
v-model="minimizeComments"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkMinimizeDescription">
|
||||
<strong v-t="'actions.minimize_description_default'" />
|
||||
<input
|
||||
id="chkMinimizeDescription"
|
||||
v-model="minimizeDescription"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkMinimizeRecommendations">
|
||||
<strong v-t="'actions.minimize_recommendations_default'" />
|
||||
<input
|
||||
id="chkMinimizeRecommendations"
|
||||
v-model="minimizeRecommendations"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkMinimizeChapters">
|
||||
<strong v-t="'actions.minimize_chapters_default'" />
|
||||
<input
|
||||
id="chkMinimizeChapters"
|
||||
v-model="minimizeChapters"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkShowWatchOnYouTube">
|
||||
<strong v-t="'actions.show_watch_on_youtube'" />
|
||||
<input
|
||||
id="chkShowWatchOnYouTube"
|
||||
v-model="showWatchOnYouTube"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkStoreSearchHistory">
|
||||
<strong v-t="'actions.store_search_history'" />
|
||||
<input
|
||||
id="chkStoreSearchHistory"
|
||||
v-model="searchHistory"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkStoreWatchHistory">
|
||||
<strong v-t="'actions.store_watch_history'" />
|
||||
<input
|
||||
id="chkStoreWatchHistory"
|
||||
v-model="watchHistory"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label v-if="watchHistory" class="pref" for="chkHideWatched">
|
||||
<strong v-t="'actions.hide_watched'" />
|
||||
<input
|
||||
@ -584,7 +309,7 @@
|
||||
<select
|
||||
id="ddlEnabledCodecs"
|
||||
v-model="enabledCodecs"
|
||||
class="select w-auto h-auto"
|
||||
class="select h-auto w-auto"
|
||||
multiple
|
||||
@change="onChange($event)"
|
||||
>
|
||||
@ -616,12 +341,16 @@
|
||||
</div>
|
||||
|
||||
<div efy_card="grid">
|
||||
<h2>SponsorBlock</h2>
|
||||
<p>
|
||||
<h5>SponsorBlock + DeArrow</h5>
|
||||
<p class="pref" style="justify-content: unset !important">
|
||||
<span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/"
|
||||
>sponsor.ajay.app</a
|
||||
>
|
||||
</p>
|
||||
<label class="pref" for="chkDeArrow">
|
||||
<strong v-t="'actions.enable_dearrow'" />
|
||||
<input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||
</label>
|
||||
<label class="pref" for="chkEnableSponsorblock">
|
||||
<strong v-t="'actions.enable_sponsorblock'" />
|
||||
<input
|
||||
@ -632,95 +361,19 @@
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipSponsors">
|
||||
<strong v-t="'actions.skip_sponsors'" />
|
||||
<input
|
||||
id="chkSkipSponsors"
|
||||
v-model="skipSponsor"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
<div v-if="sponsorBlock">
|
||||
<label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
|
||||
<strong v-t="item.label" />
|
||||
<select
|
||||
:id="'ddlSkip_' + name"
|
||||
v-model="item.value"
|
||||
class="select w-auto"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipIntro">
|
||||
<strong v-t="'actions.skip_intro'" />
|
||||
<input
|
||||
id="chkSkipIntro"
|
||||
v-model="skipIntro"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipOutro">
|
||||
<strong v-t="'actions.skip_outro'" />
|
||||
<input
|
||||
id="chkSkipOutro"
|
||||
v-model="skipOutro"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipPreview">
|
||||
<strong v-t="'actions.skip_preview'" />
|
||||
<input
|
||||
id="chkSkipPreview"
|
||||
v-model="skipPreview"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipInteraction">
|
||||
<strong v-t="'actions.skip_interaction'" />
|
||||
<input
|
||||
id="chkSkipInteraction"
|
||||
v-model="skipInteraction"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipSelfPromo">
|
||||
<strong v-t="'actions.skip_self_promo'" />
|
||||
<input
|
||||
id="chkSkipSelfPromo"
|
||||
v-model="skipSelfPromo"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipNonMusic">
|
||||
<strong v-t="'actions.skip_non_music'" />
|
||||
<input
|
||||
id="chkSkipNonMusic"
|
||||
v-model="skipMusicOffTopic"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipHighlight">
|
||||
<strong v-t="'actions.skip_highlight'" />
|
||||
<input
|
||||
id="chkSkipHighlight"
|
||||
v-model="skipHighlight"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="chkSkipFiller">
|
||||
<strong v-t="'actions.skip_filler_tangent'" />
|
||||
<input
|
||||
id="chkSkipFiller"
|
||||
v-model="skipFiller"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
>
|
||||
<option v-t="'actions.no'" value="no" />
|
||||
<option v-t="'actions.skip_button_only'" value="button" />
|
||||
<option v-t="'actions.skip_automatically'" value="auto" />
|
||||
</select>
|
||||
</label>
|
||||
<label class="pref" for="chkShowMarkers">
|
||||
<strong v-t="'actions.show_markers'" />
|
||||
@ -732,6 +385,17 @@
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="pref" for="txtMinSegmentLength" style="border-bottom: var(--efy_border)">
|
||||
<strong v-t="'actions.min_segment_length'" />
|
||||
<input
|
||||
id="txtMinSegmentLength"
|
||||
v-model="minSegmentLength"
|
||||
class="input w-24"
|
||||
type="text"
|
||||
@change="onChange($event)"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -762,21 +426,6 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--master-->
|
||||
<br />
|
||||
<p v-t="'info.preferences_note'" />
|
||||
<br />
|
||||
<button v-t="'actions.reset_preferences'" class="btn" @click="showConfirmResetPrefsDialog = true" />
|
||||
<button v-t="'actions.backup_preferences'" class="btn mx-4" @click="backupPreferences()" />
|
||||
<label v-t="'actions.restore_preferences'" for="fileSelector" class="btn" @click="restorePreferences()" />
|
||||
<input id="fileSelector" ref="fileSelector" class="hidden" type="file" @change="restorePreferences()" />
|
||||
<ConfirmModal
|
||||
v-if="showConfirmResetPrefsDialog"
|
||||
:message="$t('actions.confirm_reset_preferences')"
|
||||
@close="showConfirmResetPrefsDialog = false"
|
||||
@confirm="resetPreferences()"
|
||||
/>
|
||||
<!--masterend-->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -1082,11 +731,37 @@ export default {
|
||||
<style>
|
||||
.pref {
|
||||
@apply flex justify-between items-center;
|
||||
padding: 10rem;
|
||||
border-top: var(--efy_border);
|
||||
gap: 10rem;
|
||||
}
|
||||
.pref:nth-child(odd) {
|
||||
@apply bg-gray-200;
|
||||
/*.pref:nth-child(odd) {
|
||||
background: var(--efy_bg1);
|
||||
}*/
|
||||
.pref :is(input, select, button, [role="button"]) {
|
||||
margin: 0;
|
||||
}
|
||||
.dark .pref:nth-child(odd) {
|
||||
@apply bg-dark-800;
|
||||
.pref strong {
|
||||
line-height: 1;
|
||||
}
|
||||
.pref :is([type="number"], [type="text"], select) {
|
||||
min-width: 60rem;
|
||||
max-width: 250rem;
|
||||
}
|
||||
.pp-pref-cards {
|
||||
margin-top: 15rem;
|
||||
}
|
||||
[efy_card*="grid"] {
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
}
|
||||
[efy_card*="grid"]:active {
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
[efy_card*="grid"] h5 {
|
||||
padding: 5rem 10rem;
|
||||
}
|
||||
tbody:nth-child(odd) {
|
||||
background: var(--efy_bg1) !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,13 +1,20 @@
|
||||
<template>
|
||||
<h1 class="my-2 text-center" v-text="$route.query.search_query" />
|
||||
|
||||
<label for="ddlSearchFilters" class="mr-2">
|
||||
<strong v-text="`${$t('actions.filter')}:`" />
|
||||
</label>
|
||||
<select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
|
||||
<hr />
|
||||
<div class="flex flex-wrap place-content-between items-center">
|
||||
<h5 class="ml-[5rem]" v-text="$route.query.search_query" />
|
||||
<div class="flex items-center" style="gap: var(--efy_gap0)">
|
||||
<label v-text="`${$t('actions.filter')}:`" for="ddlSearchFilters" />
|
||||
<select
|
||||
id="ddlSearchFilters"
|
||||
v-model="selectedFilter"
|
||||
default="all"
|
||||
class="w-auto; m-0"
|
||||
@change="updateFilter()"
|
||||
>
|
||||
<option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div v-if="results && results.corrected">
|
||||
|
@ -90,6 +90,6 @@ export default {
|
||||
background: var(--efy_text2);
|
||||
box-shadow: 0 0 20rem var(--efy_text_trans);
|
||||
padding: var(--efy_gap);
|
||||
margin: calc(-12rem + var(--efy_gap)) 0 var(--efy_gap) 0;
|
||||
margin: calc(40rem + var(--efy_gap)) 0 var(--efy_gap) 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<label v-t="'actions.sort_by'" for="ddlSortBy" class="mr-2" />
|
||||
<select id="ddlSortBy" v-model="selectedSort" class="select w-auto m-0">
|
||||
<label v-t="'actions.sort_by'" for="ddlSortBy" class="m-0" />
|
||||
<select id="ddlSortBy" v-model="selectedSort" class="w-auto m-0">
|
||||
<option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
|
||||
</select>
|
||||
</template>
|
||||
|
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<hr />
|
||||
<!-- import / export section -->
|
||||
<div class="w-full flex justify-between">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex justify-between flex-wrap m0c">
|
||||
<div efy_card class="w-auto!" style="padding: var(--efy_padding)">
|
||||
<i18n-t keypath="titles.subscriptions" efy_card />{{ ": " + subscriptions.length }}
|
||||
</div>
|
||||
<div class="m0c flex flex-wrap">
|
||||
<router-link v-t="'actions.import_from_json_csv'" to="/import" role="button" />
|
||||
<button v-t="'actions.export_to_json'" @click="exportHandler" />
|
||||
<input
|
||||
@ -16,26 +20,28 @@
|
||||
for="fileSelector"
|
||||
role="button"
|
||||
v-text="`${$t('actions.import_from_json')} (${$t('titles.channel_groups')})`"
|
||||
class="font-bold"
|
||||
/>
|
||||
<button
|
||||
@click="exportGroupsHandler"
|
||||
v-text="`${$t('actions.export_to_json')} (${$t('titles.channel_groups')})`"
|
||||
/>
|
||||
</div>
|
||||
<i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="w-full flex flex-wrap">
|
||||
<div class="m0c w-full flex flex-wrap">
|
||||
<button
|
||||
v-for="group in channelGroups"
|
||||
:key="group.groupName"
|
||||
class="mx-1 w-max"
|
||||
class="flex gap-[10rem] items-center"
|
||||
:class="{ selected: selectedGroup === group }"
|
||||
@click="selectGroup(group)"
|
||||
>
|
||||
<span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
|
||||
<div v-if="group.groupName != '' && selectedGroup == group">
|
||||
<div v-if="group.groupName != '' && selectedGroup == group" class="flex flex-wrap gap-[10rem] items-center">
|
||||
<div>|</div>
|
||||
<font-awesome-icon class="mx-2" icon="edit" @click="showEditGroupModal = true" />
|
||||
<div>|</div>
|
||||
<font-awesome-icon class="mx-2" icon="circle-minus" @click="deleteGroup(group)" />
|
||||
</div>
|
||||
</button>
|
||||
@ -43,7 +49,6 @@
|
||||
<font-awesome-icon icon="circle-plus" @click="showCreateGroupModal = true" />
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
<!-- Subscriptions card list -->
|
||||
<div class="pp-subs-cards">
|
||||
@ -61,7 +66,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
|
||||
<h2 v-t="'actions.create_group'" />
|
||||
@ -101,6 +105,9 @@
|
||||
}
|
||||
.pp-subs-card :is(a, span) {
|
||||
-webkit-text-fill-color: var(--efy_text) !important;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.pp-subs-card button {
|
||||
margin-bottom: 0;
|
||||
@ -109,6 +116,12 @@
|
||||
.selected {
|
||||
border: 0.1rem outset red;
|
||||
}
|
||||
.m0c {
|
||||
gap: var(--efy_gap0);
|
||||
}
|
||||
.m0c :is(button, [role="button"]) {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="toast">
|
||||
<slot />
|
||||
<button v-t="'actions.dismiss'" @click="dismiss" />
|
||||
<button v-t="'actions.dismiss'" @click="dismiss" class="m-0 mt-[10rem]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -18,12 +18,11 @@ export default {
|
||||
|
||||
<style>
|
||||
.toast {
|
||||
@apply bg-white/80 text-black flex flex-col justify-center fixed top-12 right-12 p-4 min-w-max shadow rounded duration-200 z-9999;
|
||||
}
|
||||
.dark .toast {
|
||||
@apply bg-dark-900/80 text-white;
|
||||
}
|
||||
.toast button {
|
||||
@apply underline;
|
||||
@apply flex flex-col justify-center fixed top-[15rem] right-[15rem] min-w-max z-9999;
|
||||
background: var(--efy_text2);
|
||||
color: var(--efy_text);
|
||||
padding: 15rem;
|
||||
border-radius: var(--efy_radius);
|
||||
border: var(--efy_border);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<LoadingIndicatorPage :show-content="videos.length != 0" class="video-grid">
|
||||
<LoadingIndicatorPage :show-content="videos.length != 0" class="video-grid mt-[15rem]">
|
||||
<VideoItem v-for="video in videos" :key="video.url" :item="video" height="118" width="210" />
|
||||
</LoadingIndicatorPage>
|
||||
</template>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div v-if="showVideo" class="efy_trans_filter efy_shadow_trans">
|
||||
<div v-if="showVideo" class="video-card efy_trans_filter efy_shadow_trans">
|
||||
<!-- EFY-->
|
||||
<router-link
|
||||
class="video_item_link inline-block w-full focus:underline hover:underline"
|
||||
class="video_item_link"
|
||||
:to="{
|
||||
path: '/watch',
|
||||
query: {
|
||||
@ -11,15 +12,43 @@
|
||||
},
|
||||
}"
|
||||
>
|
||||
<!-- EFY
|
||||
<img
|
||||
:src="thumbnail"
|
||||
:alt="title"
|
||||
:class="{ 'shorts-img': item.isShort }"
|
||||
class="thumbnail"
|
||||
loading="lazy"
|
||||
/>
|
||||
<!-- progress bar -->
|
||||
<div class="relative h-1 w-full">
|
||||
<div
|
||||
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%` }"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center h-[44rem] overflow-hidden">
|
||||
<p v-text="title" class="pp-video-card-title" />
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<div class="pp-video-card-buttons">
|
||||
<button v-if="item.duration > 0" v-text="timeFormat(item.duration)" tabindex="-1" />
|
||||
<button v-if="item.views >= 0" tabindex="-1">
|
||||
<font-awesome-icon icon="eye" />
|
||||
<span class="pl-0.5" v-text="`${numberFormat(item.views)}`" />
|
||||
<button
|
||||
v-if="item.duration > 0"
|
||||
v-text="timeFormat(item.duration)"
|
||||
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
|
||||
tabindex="-1"
|
||||
/>
|
||||
<button
|
||||
v-if="item.views >= 0"
|
||||
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
|
||||
tabindex="-1"
|
||||
>
|
||||
<font-awesome-icon icon="eye" style="margin-right: 5rem" />
|
||||
<span v-text="`${numberFormat(item.views)}`" />
|
||||
</button>
|
||||
<router-link
|
||||
class="btn"
|
||||
class="btn efy_shadow_trans efy_shadow_button_off efy_button_text_off"
|
||||
:to="{
|
||||
path: '/watch',
|
||||
query: {
|
||||
@ -34,137 +63,11 @@
|
||||
>
|
||||
<font-awesome-icon icon="headphones" />
|
||||
</router-link>
|
||||
<button v-if="authenticated" :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
|
||||
<font-awesome-icon icon="circle-plus" />
|
||||
</button>
|
||||
<button
|
||||
v-if="admin"
|
||||
:title="$t('actions.remove_from_playlist')"
|
||||
ref="removeButton"
|
||||
@click="removeVideo(item.url.substr(-11))"
|
||||
:title="$t('actions.add_to_playlist')"
|
||||
@click="showModal = !showModal"
|
||||
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
|
||||
>
|
||||
<font-awesome-icon icon="circle-minus" />
|
||||
</button>
|
||||
<button v-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" tabindex="-1" />
|
||||
<button class="pp-color" v-if="item.isShort" v-t="'video.shorts'" tabindex="-1" />
|
||||
<button v-else-if="item.duration < 0" v-t="'video.live'" class="pp-color" tabindex="-1" />
|
||||
<button v-if="item.watched" v-t="'video.watched'" class="pp-color" tabindex="-1" />
|
||||
</div>
|
||||
|
||||
<router-link
|
||||
:to="item.uploaderUrl"
|
||||
class="pp-video-card-channel"
|
||||
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
|
||||
>
|
||||
<img
|
||||
v-if="item.uploaderAvatar"
|
||||
:src="item.uploaderAvatar"
|
||||
loading="lazy"
|
||||
:alt="item.uploaderName"
|
||||
class="mt-0.5 w-36rem h-36rem"
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
|
||||
<div class="pp-text" title="item.uploaderName">
|
||||
<span v-text="item.uploaderName" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />-->
|
||||
|
||||
<div class="w-full">
|
||||
<img
|
||||
class="w-full"
|
||||
:src="thumbnail"
|
||||
:alt="title"
|
||||
:class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
|
||||
loading="lazy"
|
||||
/>
|
||||
<!-- progress bar -->
|
||||
<div class="relative h-1 w-full">
|
||||
<div
|
||||
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%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative text-sm">
|
||||
<span
|
||||
v-if="item.duration > 0"
|
||||
class="thumbnail-overlay thumbnail-right"
|
||||
v-text="timeFormat(item.duration)"
|
||||
/>
|
||||
<!-- shorts thumbnail -->
|
||||
<span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
|
||||
<span
|
||||
v-else-if="item.duration >= 0"
|
||||
class="thumbnail-overlay thumbnail-right"
|
||||
v-text="timeFormat(item.duration)"
|
||||
/>
|
||||
<i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
|
||||
<font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
|
||||
</i18n-t>
|
||||
<span v-if="item.watched" v-t="'video.watched'" class="thumbnail-overlay bottom-5px left-5px" />
|
||||
</div>
|
||||
|
||||
<p
|
||||
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
|
||||
class="pp-video-card-title my-2 overflow-hidden flex link"
|
||||
:title="title"
|
||||
v-text="title"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<div class="flex">
|
||||
<router-link :to="item.uploaderUrl">
|
||||
<img
|
||||
v-if="item.uploaderAvatar"
|
||||
:src="item.uploaderAvatar"
|
||||
loading="lazy"
|
||||
class="mr-0.5 mt-0.5 h-32px w-32px rounded-full"
|
||||
width="68"
|
||||
height="68"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<div class="flex-1 px-2">
|
||||
<router-link
|
||||
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
|
||||
class="link-secondary block overflow-hidden text-sm"
|
||||
:to="item.uploaderUrl"
|
||||
:title="item.uploaderName"
|
||||
>
|
||||
<span v-text="item.uploaderName" />
|
||||
<font-awesome-icon v-if="item.uploaderVerified" class="ml-1.5" icon="check" />
|
||||
</router-link>
|
||||
|
||||
<div v-if="item.views >= 0 || item.uploadedDate" class="mt-1 text-xs font-normal text-gray-300">
|
||||
<span v-if="item.views >= 0">
|
||||
<font-awesome-icon icon="eye" />
|
||||
<span class="pl-1" v-text="`${numberFormat(item.views)} •`" />
|
||||
</span>
|
||||
<span v-if="item.uploaded > 0" class="pl-0.5" v-text="timeAgo(item.uploaded)" />
|
||||
<span v-else-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2.5">
|
||||
<router-link
|
||||
:to="{
|
||||
path: '/watch',
|
||||
query: {
|
||||
v: item.url.substr(-11),
|
||||
...(playlistId && { list: playlistId }),
|
||||
...(index >= 0 && { index: index + 1 }),
|
||||
listen: '1',
|
||||
},
|
||||
}"
|
||||
:aria-label="'Listen to ' + title"
|
||||
:title="'Listen to ' + title"
|
||||
>
|
||||
<font-awesome-icon icon="headphones" />
|
||||
</router-link>
|
||||
<button :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
|
||||
<font-awesome-icon icon="circle-plus" />
|
||||
</button>
|
||||
<button
|
||||
@ -187,12 +90,37 @@
|
||||
:video-info="item"
|
||||
@close="showModal = !showModal"
|
||||
/>
|
||||
<!--Master-->
|
||||
<button
|
||||
v-if="item.uploaded > 0"
|
||||
class="efy_shadow_trans efy_shadow_button_off efy_button_text_off"
|
||||
v-text="timeAgo(item.uploaded)"
|
||||
/>
|
||||
<button v-else-if="item.uploadedDate" v-text="item.uploadedDate" tabindex="-1" />
|
||||
<button class="pp-color" v-if="item.isShort" v-t="'video.shorts'" tabindex="-1" />
|
||||
<button v-else-if="item.duration < 0" v-t="'video.live'" class="pp-color" tabindex="-1" />
|
||||
<button v-if="item.watched" v-t="'video.watched'" class="pp-color" tabindex="-1" />
|
||||
</div>
|
||||
<!-- </router-link> -->
|
||||
|
||||
<router-link
|
||||
v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
|
||||
:to="item.uploaderUrl"
|
||||
:title="item.uploaderName"
|
||||
class="pp-video-card-channel"
|
||||
>
|
||||
<img
|
||||
v-if="item.uploaderAvatar"
|
||||
:src="item.uploaderAvatar"
|
||||
loading="lazy"
|
||||
class="mt-0.5 w-36rem h-36rem efy_shadow_trans efy_shadow_button_off"
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
<div class="pp-text efy_shadow_trans efy_shadow_button_off">
|
||||
<span v-text="item.uploaderName" />
|
||||
<font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<PlaylistAddModal v-if="showModal" :video-id="video.url.substr(-11)" @close="showModal = !showModal" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
@ -11,8 +11,17 @@
|
||||
ref="previewContainer"
|
||||
class="absolute bottom-0 z-[2000] mb-[3.5%] hidden flex-col items-center"
|
||||
>
|
||||
<canvas id="preview" ref="preview" class="rounded-sm" />
|
||||
<span class="mt-2 w-min rounded-xl bg-dark-700 px-2 pb-1 pt-1.5 text-sm" v-text="timeFormat(currentTime)" />
|
||||
<canvas id="preview" ref="preview" style="border-radius: var(--efy_radius0)" />
|
||||
<span
|
||||
class="w-min"
|
||||
style="
|
||||
border-radius: var(--efy_radius0);
|
||||
background: var(--efy_text2);
|
||||
color: var(--efy_text);
|
||||
padding: 3rem 6rem;
|
||||
"
|
||||
v-text="timeFormat(currentTime)"
|
||||
/>
|
||||
</span>
|
||||
<button
|
||||
v-if="inSegment"
|
||||
|
@ -10,7 +10,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full">
|
||||
<LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full mt-[15rem]">
|
||||
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
|
||||
<Transition>
|
||||
<ToastComponent v-if="shouldShowToast" @dismissed="dismiss">
|
||||
|
@ -7,7 +7,7 @@
|
||||
--efy_gap: 15rem;
|
||||
--efy_sidebar_button: right_middle, off;
|
||||
--efy_body_width: 100%;
|
||||
--efy_body_padding: 15rem calc(15rem + var(--efy_gap));
|
||||
--efy_body_padding: 15rem;
|
||||
--efy_font_family: 'nunito', sans-serif,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
|
||||
--efy_audio_folder: ./efy/audio;
|
||||
--efy_folder: ./efy;
|
||||
@ -45,8 +45,6 @@
|
||||
|
||||
/*Video Grid */ .video-grid {display: grid; gap: var(--efy_gap); grid-template-columns: repeat(auto-fill, minmax(240rem, 1fr))}
|
||||
|
||||
tbody:nth-child(odd) {background: var(--efy_bg1)!important; box-shadow: inset 0 0 0 1.5px var(--efy_bg1)}
|
||||
|
||||
/*Bellow*/ .pp-watch-bellow-options {margin-top: 15rem}
|
||||
.pp-watch-buttons {flex-wrap: wrap; gap: var(--efy_gap0)}
|
||||
.pp-watch-buttons .btn {padding: 6rem 15rem; border: 0; color: var(--efy_text2); min-height: var(--efy_ratio_width); max-height: var(--efy_ratio_width); place-self: center; place-content: center}
|
||||
@ -95,13 +93,6 @@ tbody:nth-child(odd) {background: var(--efy_bg1)!important; box-shadow: inset 0
|
||||
/*Preferences*/ .pp-pref-cards {display: grid; grid-template-columns: repeat(auto-fit, minmax(300rem, 1fr))}
|
||||
table {margin-top: 0}
|
||||
|
||||
/*Chapters*/ .pp-chapters {margin-left: var(--efy_gap); max-width: 400rem; gap: var(--efy_gap0); border-radius: var(--efy_radius)}
|
||||
.pp-chapters .chapter {padding: 10rem; border-radius: var(--efy_radius); border: var(--efy_border)}
|
||||
.pp-chapters [title=chapters] {padding: 5rem 10rem; border-radius: var(--efy_radius); border: var(--efy_border)}
|
||||
.pp-chapters .chapter .flex {gap: 0 15rem}
|
||||
|
||||
.pp-chapters.pp-mobile {margin: 15rem 0 0 0; max-width: 100%}
|
||||
|
||||
/*Modal*/ .modal {backdrop-filter: blur(5px)}
|
||||
.modal-container {background: var(--efy_text2)!important; padding: 15rem!important; box-shadow: 0 0 1px var(--efy_text_trans)}
|
||||
.modal-container button + button {margin-left: 10rem}
|
||||
@ -115,7 +106,7 @@ table {margin-top: 0}
|
||||
|
||||
/*Other*/ .pp-show-recs, .pp-show-playlist, .pp-show-playlist {display: grid; gap: var(--efy_gap)}
|
||||
.pp-show-playlist {margin-bottom: 15rem}
|
||||
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div {padding: 15rem; background: var(--efy_bg1); border: var(--efy_border)}
|
||||
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div {background: var(--efy_bg1); border: var(--efy_border)}
|
||||
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div div {padding: 0; border: none; background: transparent}
|
||||
|
||||
.video-grid > div {
|
||||
@ -160,6 +151,10 @@ table {margin-top: 0}
|
||||
padding: 4rem 10rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
.thumbnail[src*="/?host=i.ytimg.com"] {
|
||||
aspect-ratio: 16/9;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/*Convergence*/
|
||||
/*Desktop*/
|
||||
@ -172,7 +167,6 @@ table {margin-top: 0}
|
||||
@media (max-width: 767.9px) {
|
||||
.pp-rec-vids {grid-template-columns: 1fr 1fr}
|
||||
.\<md\:hidden {display: none}
|
||||
.md\:hidden {margin-bottom: 16rem}
|
||||
}
|
||||
@media (max-width: 639px) {
|
||||
.pp-rec-vids {grid-template-columns: 1fr}
|
||||
|
Loading…
Reference in New Issue
Block a user