designtopack/src/components/comments/Comments.vue

361 lines
9.5 KiB
Vue
Raw Normal View History

2024-10-23 09:48:27 +02:00
<template>
<aside id="comments-container" aria-labelledby="comments-label">
<h2 id="comments-label" class="sr-only">Commentaires</h2>
2024-11-17 12:01:20 +01:00
<div
class="comments | flow"
:class="{ empty: !comments || comments.length === 0 }"
>
2024-10-29 16:13:07 +01:00
<template v-if="comments">
<template v-if="!openedComment">
<Comment
v-for="(comment, commentIndex) in sortedComments"
2024-10-29 16:13:07 +01:00
:comment="comment"
:commentIndex="comments.length - commentIndex"
2024-10-29 16:13:07 +01:00
:key="comment.id"
2024-11-18 09:36:15 +01:00
@click="openComment(comment)"
2024-10-30 16:32:13 +01:00
@update:file="changeFile"
@close:comment="closeComment"
2024-10-29 16:13:07 +01:00
/>
</template>
<template v-else>
<button
class="btn | justify-start w-full | bg-white-10 text-white | px-8"
data-icon="chevron-single-left"
2024-10-29 16:51:31 +01:00
@click="
openedComment = null;
isAddOpen = false;
"
>
2024-10-29 16:13:07 +01:00
<span>Retour à la liste</span>
</button>
2024-10-30 16:32:13 +01:00
<Comment
:comment="openedComment"
data-opened="true"
@update:file="changeFile"
@close:comment="closeComment"
/>
<div v-if="sortedReplies.length > 0" class="replies | flow">
2024-10-29 16:13:07 +01:00
<Comment
2024-10-30 13:29:26 +01:00
v-for="(reply, commentIndex) in sortedReplies"
2024-10-29 16:13:07 +01:00
:comment="reply"
2024-10-30 13:29:26 +01:00
:commentIndex="sortedReplies.length - commentIndex"
2024-10-29 16:13:07 +01:00
:key="reply.id"
2024-10-30 16:32:13 +01:00
@update:file="changeFile"
@close:comment="closeComment"
2024-10-29 16:13:07 +01:00
/>
</div>
</template>
2024-10-29 11:18:17 +01:00
</template>
2024-10-23 09:48:27 +02:00
</div>
<button
2024-10-29 17:26:23 +01:00
v-if="!openedComment && !isAddOpen"
id="create-comment"
2024-10-23 09:48:27 +02:00
class="btn btn--white-20 | w-full"
@click="toggleCommentPositionMode(true)"
2024-10-23 09:48:27 +02:00
>
Ajouter un commentaire
</button>
<button
2024-10-29 16:51:31 +01:00
v-else-if="openedComment && !isAddOpen"
id="reply-comment"
class="btn btn--white-20 | justify-start w-full | text-white-50"
2024-10-29 16:51:31 +01:00
@click="isAddOpen = true"
>
Répondre
</button>
<!-- TODO: afficher #new-comment une fois le bouton Ajouter un commentaire cliqué -->
<div
2024-11-19 15:38:39 +01:00
v-if="waitingForCommentPosition"
id="new-comment"
class="bg-primary | text-sm text-white | rounded-lg | p-12"
>
2024-11-08 12:25:00 +01:00
<p class="flex justify-start | mb-12" data-icon="comment">
<strong>Nouveau commentaire</strong>
</p>
<p>
Dans la zone du contenu, cliquez vous souhaitez positionner le
commentaire
</p>
</div>
2024-10-23 09:48:27 +02:00
<form
v-if="isAddOpen"
action=""
method="post"
class="flow | bg-white-20 | p-12 | rounded-xl"
2024-10-29 16:51:31 +01:00
@submit="handleSubmit"
2024-10-23 09:48:27 +02:00
>
<label class="sr-only" for="comment">Votre commentaire</label>
<textarea
name="comment"
id="comment"
placeholder="Ajouter un commentaire…"
rows="5"
2024-10-23 09:48:27 +02:00
class="text-sm | rounded-lg bg-black p-12"
v-model="newCommentText"
></textarea>
<footer class="flex">
<input type="submit" class="btn btn--tranparent" />
2024-10-29 16:51:31 +01:00
<button class="btn btn--white-10" @click="isAddOpen = false">
Annuler
</button>
2024-10-23 09:48:27 +02:00
</footer>
</form>
</aside>
</template>
<script setup>
2024-10-23 11:32:51 +02:00
import dayjs from "dayjs";
import "dayjs/locale/fr";
import uniqid from "uniqid";
2024-11-16 12:05:37 +01:00
import { watch, ref, computed } from "vue";
2024-10-23 09:48:27 +02:00
import { useUserStore } from "../../stores/user";
2024-10-23 11:32:51 +02:00
import { usePageStore } from "../../stores/page";
2024-10-28 17:50:40 +01:00
import { useApiStore } from "../../stores/api";
import { useDialogStore } from "../../stores/dialog";
2024-10-29 16:13:07 +01:00
import Comment from "./Comment.vue";
2024-11-16 12:05:37 +01:00
import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
2024-10-23 11:32:51 +02:00
dayjs.locale("fr");
2024-10-23 09:48:27 +02:00
const { user } = useUserStore();
2024-10-23 11:32:51 +02:00
const { page } = usePageStore();
const dialog = useDialogStore();
2024-11-16 12:05:37 +01:00
const { comments, openedFile } = storeToRefs(useDialogStore());
2024-10-28 17:50:40 +01:00
const api = useApiStore();
2024-10-23 09:48:27 +02:00
2024-10-29 11:18:17 +01:00
const openedComment = ref(null);
const newCommentPageIndex = ref(null);
const newCommentPosition = ref(null);
2024-10-23 09:48:27 +02:00
const newCommentText = ref("");
const isAddOpen = ref(false);
const route = useRoute();
2024-11-19 15:38:39 +01:00
const waitingForCommentPosition = ref(false);
2024-11-08 12:25:00 +01:00
2024-11-16 12:05:37 +01:00
const sortedComments = computed(() => comments.value.reverse());
2024-11-08 12:25:00 +01:00
const sortedReplies = ref(null);
watch(openedComment, (newVal) => {
sortedReplies.value = newVal ? newVal.replies.slice().reverse() : null;
});
2024-11-11 17:12:26 +01:00
watch(isAddOpen, (newVal) => {
if (newVal) {
setTimeout(() => {
document.querySelector("textarea#comment").focus();
}, 100);
}
});
const viewContainer = document.querySelector(".vpv-pages-inner-container");
2024-10-23 09:48:27 +02:00
2024-11-11 17:12:26 +01:00
window.addEventListener("keydown", (event) => {
if (
isAddOpen.value &&
event.key === "Enter" &&
(event.metaKey || event.ctrlKey)
) {
handleSubmit();
}
});
2024-10-23 09:48:27 +02:00
// Functions
2024-11-11 17:12:26 +01:00
function handleSubmit(event = null) {
if (event) {
event.preventDefault();
}
2024-10-23 11:32:51 +02:00
const date = dayjs().format();
2024-10-29 16:51:31 +01:00
const newComment = {
path: route.fullPath,
fileName: openedFile ? openedFile.value.name : false,
2024-10-23 09:48:27 +02:00
userUuid: user.uuid,
text: newCommentText.value,
2024-10-23 11:32:51 +02:00
date,
position:
{
pageIndex: newCommentPageIndex.value,
2024-11-11 17:12:26 +01:00
x: newCommentPosition.value?.x,
y: newCommentPosition.value?.y,
} ?? false,
2024-10-23 11:32:51 +02:00
id: uniqid(),
};
2024-10-29 16:51:31 +01:00
if (openedComment.value) {
replyComment(newComment);
} else {
addComment(newComment);
}
}
async function replyComment(newComment) {
newComment.parentId = openedComment.value.id;
const newFile = await api.replyComment(newComment);
newCommentText.value = "";
isAddOpen.value = false;
2024-11-16 12:16:03 +01:00
dialog.updateFile(newFile);
2024-10-30 13:29:26 +01:00
openedComment.value = newFile.comments.find(
(item) => item.id === openedComment.value.id
);
2024-10-29 16:51:31 +01:00
}
async function addComment(newComment) {
const newFile = await api.addComment(newComment);
2024-10-28 17:50:40 +01:00
newCommentText.value = "";
isAddOpen.value = false;
2024-10-30 16:32:13 +01:00
2024-11-16 12:05:37 +01:00
dialog.updateFile(newFile);
2024-10-30 16:32:13 +01:00
}
function closeComment() {
openedComment.value = null;
}
function toggleCommentPositionMode(enable) {
if (enable) {
2024-11-19 15:38:39 +01:00
waitingForCommentPosition.value = true;
viewContainer.classList.add("waiting-comment");
viewContainer.addEventListener("click", handleCommentPositionClick);
} else {
2024-11-19 15:38:39 +01:00
waitingForCommentPosition.value = false;
viewContainer.classList.remove("waiting-comment");
viewContainer.removeEventListener("click", handleCommentPositionClick);
}
}
function handleCommentPositionClick(event) {
const pageContainer = event.target.closest(".page-inner-container");
if (!pageContainer) return;
const pageLabel = pageContainer
.closest(".vpv-page-inner-container")
.getAttribute("aria-label");
const pageIndex = pageLabel.charAt(pageLabel.length - 1);
newCommentPageIndex.value = parseInt(pageIndex);
const viewRect = viewContainer.getBoundingClientRect();
const pageRect = pageContainer.getBoundingClientRect();
const pageScroll = viewRect.top - pageRect.top;
const mouseTop = event.clientY;
const y = mouseTop - viewRect.top + pageScroll;
const x = event.clientX - pageRect.left;
2024-11-11 17:12:26 +01:00
const relativeX = (x / pageRect.width) * 100;
const relativeY = (y / pageRect.height) * 100;
newCommentPosition.value = {
2024-11-11 17:12:26 +01:00
x: relativeX,
y: relativeY,
};
isAddOpen.value = true;
toggleCommentPositionMode(false);
}
function openComment(comment) {
if (comment.replies.length) {
openedComment.value = comment;
}
}
2024-10-23 09:48:27 +02:00
</script>
<style>
/* Comments */
#toggle-comments {
position: absolute;
right: var(--space-16);
bottom: var(--space-16);
padding: 0.625rem;
}
#comments-container {
background-color: black;
position: absolute;
top: 0;
right: 0;
bottom: 4.5rem;
2024-10-30 17:16:48 +01:00
width: var(--dialog-comments-w);
padding: var(--space-24) var(--space-32);
}
.comments {
2024-11-11 17:12:26 +01:00
scroll-behavior: smooth;
overflow-y: auto;
height: calc(100% - 3.5rem);
margin-bottom: 1rem;
margin-right: -2rem;
padding-right: 2rem;
}
.comments.empty::after {
2024-11-08 12:25:00 +01:00
content: "Partagez vos idées en ajoutant des commentaires";
height: 100%;
display: grid;
place-items: center;
text-align: center;
max-width: 24ch;
margin: auto;
font-size: var(--text-sm);
color: var(--color-grey-400);
background-image: var(--icon-comment);
background-position: center;
background-repeat: no-repeat;
}
.comments.empty::before {
--icon-size: 1.25rem;
--icon-color: var(--color-white);
content: "";
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, calc(-50% - 4.5rem));
width: var(--icon-size);
height: var(--icon-size);
background: var(--icon-color, currentColor);
mask-repeat: no-repeat;
mask-position: center;
mask-size: var(--icon-size);
mask-image: var(--icon-comment);
}
#new-comment {
position: absolute;
bottom: var(--space-24);
left: var(--space-32);
right: var(--space-32);
}
#new-comment [data-icon] {
--column-gap: var(--space-12);
font-weight: 500;
}
#comments-container form {
--flow-space: 0.5rem;
flex-direction: column;
position: -webkit-sticky;
position: sticky;
bottom: 5.5rem;
}
#comments-container textarea {
position: sticky;
bottom: 0;
margin: 0;
resize: none;
background: none;
padding: 0;
color: var(--color-white);
}
#comments-container textarea:focus {
outline: none;
}
::placeholder {
color: var(--color-white-50);
}
#comments-container form footer {
gap: var(--space-12);
}
#comments-container form footer > * {
flex-grow: 1;
}
.vpv-pages-inner-container.waiting-comment .page-inner-container,
.vpv-pages-inner-container.waiting-comment .vpv-text-layer-text,
.vpv-pages-inner-container.waiting-comment .vpv-text-layer-wrapper {
cursor: cell !important;
}
2024-10-30 16:32:13 +01:00
</style>