Thumbnail format
This commit is contained in:
@@ -1,167 +1,10 @@
|
|||||||
<template>
|
<script setup>
|
||||||
<div class="shadow-md rounded-2xl bg-gray-50 border custom-border w-full">
|
import { computed, onBeforeMount, ref } from 'vue';
|
||||||
<div>
|
import { time_ago } from "@/internal_time_ago.js";
|
||||||
<v-card-title>
|
import { useClient } from "@/plugins/api.js";
|
||||||
<div class="flex flex-row justify-between items-center">
|
import { useAuthStore } from "@/stores/authStore.js";
|
||||||
<div class="flex items-center">
|
import { useMessageStore } from "@/stores/messageStore.js";
|
||||||
<img
|
import { useBrandingStore } from "@/stores/brandingStore.js";
|
||||||
:src="props.content.createdByPortraitUrl"
|
|
||||||
alt="Profile Image"
|
|
||||||
class="rounded-full"
|
|
||||||
width="32px"
|
|
||||||
height="32px">
|
|
||||||
<router-link class="capitalize px-2" :to="`/@${props.content.createdByName}`">
|
|
||||||
{{ props.content.createdByName }}
|
|
||||||
</router-link>
|
|
||||||
<span class="text-subtitle-2 mt-1">
|
|
||||||
{{ time_ago(props.content.createdAt) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-menu v-if="creatorIsCurrentUser">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn variant="plain" v-bind="props">
|
|
||||||
<v-icon>mdi-dots-vertical</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item v-if="creatorIsCurrentUser" @click="editContent">
|
|
||||||
<v-list-item-title>{{$t('contentCard.edit')}}</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="creatorIsCurrentUser" @click="openDeleteConfirmationDialog">
|
|
||||||
<v-list-item-title>{{$t('contentCard.delete')}}</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</div>
|
|
||||||
<div class="uppercase">
|
|
||||||
{{ props.content.title }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ props.content.description }}
|
|
||||||
</div>
|
|
||||||
<div v-if="props.content.htmlFileUrl !== ''" class="html-content">
|
|
||||||
|
|
||||||
<div v-if="isIframeLoading">
|
|
||||||
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<iframe
|
|
||||||
ref="iframe"
|
|
||||||
:src="props.content.htmlFileUrl"
|
|
||||||
class="w-full"
|
|
||||||
:style="{ height: iframeHeight + 'px', overflow: 'hidden' }"
|
|
||||||
allowfullscreen
|
|
||||||
@load="isIframeLoading = false"
|
|
||||||
v-show="!isIframeLoading"
|
|
||||||
></iframe>
|
|
||||||
<!-- Expand button to toggle full size -->
|
|
||||||
<v-btn v-if="showExpandButton" @click="toggleExpand">
|
|
||||||
{{ isExpanded ? 'Collapse' : 'Expand' }}
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<v-carousel
|
|
||||||
hide-delimiters
|
|
||||||
v-if="hasUrls"
|
|
||||||
:show-arrows="props.content.urls.length > 1"
|
|
||||||
:show-indicators="props.content.urls.length > 1"
|
|
||||||
>
|
|
||||||
<v-carousel-item
|
|
||||||
v-for="url in props.content.urls"
|
|
||||||
:key="url"
|
|
||||||
class="max-w-[800px]"
|
|
||||||
@click="redirectToContent"
|
|
||||||
>
|
|
||||||
<component :is="getComponent(url)" :src="url"></component>
|
|
||||||
</v-carousel-item>
|
|
||||||
</v-carousel>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-4">
|
|
||||||
<div class="flex justify-around py-2">
|
|
||||||
<Reaction :content="content"></Reaction>
|
|
||||||
|
|
||||||
<v-btn
|
|
||||||
:class="{'comment-active': hasMessages}"
|
|
||||||
icon="true"
|
|
||||||
variant="plain"
|
|
||||||
@click="toggleComments">
|
|
||||||
<v-icon>mdi-comment-outline</v-icon>
|
|
||||||
{{ messageCount }}
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<donation-button></donation-button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="{'hidden': !messagesVisible}">
|
|
||||||
<h2 class="font-sans font-semibold mt-2">{{ $t('contentCard.commenttitle') }}</h2>
|
|
||||||
<message-list
|
|
||||||
:subject-id="props.content.id"
|
|
||||||
:messages="messages"
|
|
||||||
></message-list>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="py-2">
|
|
||||||
<post-message :subject-id="props.content.id"
|
|
||||||
@message-posted="addMessage"
|
|
||||||
></post-message>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<v-dialog v-model="openDeleteConfirmationModal" max-width="500">
|
|
||||||
<v-form>
|
|
||||||
<v-card class="text-center rounded-xl"
|
|
||||||
:style="{
|
|
||||||
border: `2px solid `
|
|
||||||
}">
|
|
||||||
<div class="flex items-center justify-between py-4 text-2xl font-bold border-b mb-2">
|
|
||||||
<div class="flex-1 text-center">
|
|
||||||
{{$t('contentCard.deletecontenttitle')}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-btn icon @click="openDeleteConfirmationModal = false" class="ml-auto mr-2" variant="text">
|
|
||||||
<v-icon>mdi-close</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" mr-2">
|
|
||||||
{{$t('contentCard.deeletecontentwarning')}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="py-2 space-x-3">
|
|
||||||
<v-btn variant="flat"
|
|
||||||
@click="deleteContent()" class=" mt-5">
|
|
||||||
{{$t('general.yes')}}
|
|
||||||
</v-btn>
|
|
||||||
<v-btn variant="outlined"
|
|
||||||
@click="openDeleteConfirmationModal = false" class=" mt-5">
|
|
||||||
{{$t('general.no')}}
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</v-form>
|
|
||||||
</v-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {computed, onBeforeMount, ref} from 'vue';
|
|
||||||
import {time_ago} from "@/internal_time_ago.js";
|
|
||||||
import MessageList from "@/views/messages/MessageList.vue";
|
|
||||||
import PostMessage from "@/views/messages/PostMessage.vue";
|
|
||||||
import DonationButton from "@/views/creators/DonationButton.vue";
|
|
||||||
import YoutubePlayer from '../YoutubePlayer.vue';
|
|
||||||
import ImageViewer from '../ImageViewer.vue';
|
|
||||||
import {useClient} from "@/plugins/api.js";
|
|
||||||
import {useAuthStore} from "@/stores/authStore.js";
|
|
||||||
import Reaction from "@/views/contents/Reaction.vue";
|
|
||||||
import {useMessageStore} from "@/stores/messageStore.js";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
content: {
|
content: {
|
||||||
@@ -171,110 +14,101 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const openDeleteConfirmationModal = ref(false);
|
const openDeleteConfirmationModal = ref(false);
|
||||||
const emits = defineEmits(['content-deleted'])
|
const emits = defineEmits(['content-deleted']);
|
||||||
|
const contentId = computed(() => props.content.id);
|
||||||
const contentId = computed(() => props.content.id)
|
const creatorId = computed(() => props.content.createdBy);
|
||||||
const creatorId = computed(() => props.content.createdBy)
|
const branding = useBrandingStore();
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const messageStore = useMessageStore();
|
|
||||||
const messageCount = ref(0);
|
|
||||||
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId === creatorId.value);
|
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId === creatorId.value);
|
||||||
|
|
||||||
const hasUrls = computed(() => !!props.content.urls && props.content.urls.length > 0);
|
|
||||||
const messagesVisible = ref(false);
|
|
||||||
const messages = ref([]);
|
|
||||||
const hasMessages = computed(() => messages.value.length > 0);
|
|
||||||
|
|
||||||
const iframeHeight = ref(300);
|
|
||||||
const minHeight = ref(200);
|
|
||||||
const maxHeight = ref(1200);
|
|
||||||
const isExpanded = ref(false);
|
|
||||||
const showExpandButton = ref(false);
|
|
||||||
const isIframeLoading = ref(true);
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
messageCount.value = await messageStore.fetchMessageCount(contentId.value)
|
const messageStore = useMessageStore();
|
||||||
})
|
messageStore.fetchMessageCount(contentId.value);
|
||||||
|
});
|
||||||
|
|
||||||
function openDeleteConfirmationDialog() {
|
function openDeleteConfirmationDialog() {
|
||||||
openDeleteConfirmationModal.value = true;
|
openDeleteConfirmationModal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMessage(newMessage) {
|
|
||||||
messages.value.unshift(newMessage);
|
|
||||||
messagesVisible.value = true;
|
|
||||||
messageCount.value ++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleComments() {
|
|
||||||
messagesVisible.value = !messagesVisible.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComponent(url) {
|
|
||||||
if (url.includes('youtube.com') || url.includes('youtu.be')) {
|
|
||||||
return YoutubePlayer;
|
|
||||||
} else if (url.match(/\.(jpeg|jpg|gif|png)$/)) {
|
|
||||||
return ImageViewer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function editContent() {
|
|
||||||
console.log('Modifier le contenu');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteContent() {
|
async function deleteContent() {
|
||||||
const client = useClient()
|
const client = useClient();
|
||||||
const response = await client.delete(`/api/contents/${contentId.value}`)
|
const response = await client.delete(`/api/contents/${contentId.value}`);
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
emits('content-deleted', contentId.value)
|
emits('content-deleted', contentId.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const observeIframeSize = () => {
|
|
||||||
const iframe = document.querySelector('iframe');
|
|
||||||
|
|
||||||
if (iframe && iframe.contentWindow) {
|
|
||||||
// Access the iframe's content document and measure the scrollHeight
|
|
||||||
const iframeDocument = iframe.contentWindow.document;
|
|
||||||
const contentHeight = iframeDocument.body.scrollHeight;
|
|
||||||
|
|
||||||
console.log('Content height:', contentHeight);
|
|
||||||
|
|
||||||
// If content height exceeds 200px, show the expand button
|
|
||||||
if (contentHeight > 200) {
|
|
||||||
showExpandButton.value = true;
|
|
||||||
} else {
|
|
||||||
showExpandButton.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set iframe height initially to the smaller value (200px) or full content height if expanded
|
|
||||||
iframeHeight.value = isExpanded.value ? contentHeight : Math.min(contentHeight, 200);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleExpand = () => {
|
|
||||||
isExpanded.value = !isExpanded.value;
|
|
||||||
observeIframeSize();
|
|
||||||
};
|
|
||||||
|
|
||||||
function redirectToContent() {
|
function redirectToContent() {
|
||||||
window.location.href = `/content/${props.content.id}`;
|
window.location.href = `/content/${props.content.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
||||||
|
return `${(bigint >> 16) & 255}, ${(bigint >> 8) & 255}, ${bigint & 255}`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|
||||||
.custom-border {
|
.custom-border {
|
||||||
border-color: #EAEBEC;
|
border-color: #EAEBEC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-active .v-icon {
|
.comment-active .v-icon {
|
||||||
color: #D63DAB;
|
color: #D63DAB;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="shadow-md rounded-md bg-gray-50 border custom-border w-52 h-[300px]"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: branding.colors.surface,
|
||||||
|
boxShadow: '0 10px 10px rgba(0, 0, 0, 0.3)',
|
||||||
|
borderColor: `rgba(${hexToRgb(branding.colors.secondary)}, 0.4)`,
|
||||||
|
borderWidth: '1px',
|
||||||
|
}">
|
||||||
|
|
||||||
|
|
||||||
|
<img v-if="props.content.urls[0]" :src="props.content.urls[0]" class="rounded-t-md w-[260px] h-[160px] object-cover cursor-pointer"
|
||||||
|
alt="Image Content" @click="redirectToContent" />
|
||||||
|
|
||||||
|
|
||||||
|
<div class="p-1">
|
||||||
|
<div class="flex flex-row justify-between items-center">
|
||||||
|
<span class="text-caption mt-1">{{ time_ago(props.content.createdAt) }}</span>
|
||||||
|
<v-menu v-if="creatorIsCurrentUser" :offset-y="true">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn variant="plain" v-bind="props" style="min-width: auto; padding: 0; margin-right: 4px;">
|
||||||
|
<v-icon>mdi-dots-vertical</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item @click="openDeleteConfirmationDialog">
|
||||||
|
<v-list-item-title>{{$t('contentCard.delete')}}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="capitalize p-2">{{ props.content.title }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Dialog -->
|
||||||
|
<v-dialog v-model="openDeleteConfirmationModal" max-width="500">
|
||||||
|
<v-card class="text-center rounded-xl">
|
||||||
|
<div class="flex items-center justify-between py-4 text-2xl font-bold border-b mb-2">
|
||||||
|
<div class="flex-1 text-center">{{$t('contentCard.deletecontenttitle')}}</div>
|
||||||
|
<v-btn icon @click="openDeleteConfirmationModal = false" variant="text">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div>{{$t('contentCard.deeletecontentwarning')}}</div>
|
||||||
|
<div class="py-2 space-x-3">
|
||||||
|
<v-btn variant="flat" @click="deleteContent()" class="mt-5">{{$t('general.yes')}}</v-btn>
|
||||||
|
<v-btn variant="outlined" @click="openDeleteConfirmationModal = false" class="mt-5">{{$t('general.no')}}</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user