designtopack/src/components/comments/Comment.vue

320 lines
7.9 KiB
Vue
Raw Normal View History

2024-10-29 16:13:07 +01:00
<template>
2024-11-11 17:12:26 +01:00
<article
:id="`comment-${comment.id}`"
class="comment | flow"
:data-status="getStatus"
2025-01-08 12:12:10 +01:00
@click="handleClick()"
@mouseenter="hightlightCorrespondingMarker()"
@mouseleave="unhightlightCorrespondingMarker()"
ref="comment-node"
2024-11-11 17:12:26 +01:00
>
2024-10-29 16:13:07 +01:00
<header>
<p>
<strong>{{ comment.author.name ?? comment.author.email }}</strong>
2024-10-29 16:13:07 +01:00
<template v-if="commentIndex">
<span class="comment__id">#{{ commentIndex }}</span>
</template>
<span class="comment__page">Page {{ comment.position.pageIndex }}</span>
2024-10-30 16:32:13 +01:00
<time
class="comment__date"
:datetime="dayjs(comment.date).format('YYYY-MM-DD')"
2024-12-19 10:32:48 +01:00
>{{ formatDate(comment.date) }}</time
2024-10-30 16:32:13 +01:00
>
2024-10-29 16:13:07 +01:00
</p>
</header>
2025-01-07 16:05:10 +01:00
<p v-if="!comment.isEditMode" class="comment__body">
2024-10-29 16:13:07 +01:00
{{ comment.text }}
</p>
2025-01-07 16:12:33 +01:00
<textarea
v-else
class="comment__body"
v-model="draftText"
:rows="Math.ceil(draftText.length / 32)"
ref="editField"
></textarea>
2025-01-07 16:05:10 +01:00
<footer v-if="!comment.isEditMode" class="comment__replies">
2024-10-30 16:32:13 +01:00
<p v-if="comment.replies?.length > 0">
{{ comment.replies.length }} réponse{{
comment.replies.length > 1 ? "s" : ""
}}
</p>
<div
2025-01-07 11:33:15 +01:00
v-if="userStore.canEditComment(comment)"
2024-10-30 16:32:13 +01:00
class="comment__ctas | mt-8"
>
<button
class="btn btn--transparent btn--icon btn--sm"
data-icon="edit"
2025-01-07 16:12:33 +01:00
@click="editComment($event)"
>
2024-10-30 16:32:13 +01:00
<span class="sr-only">Éditer</span>
</button>
<button
class="btn btn--transparent btn--icon btn--sm"
data-icon="delete"
2024-11-18 09:36:15 +01:00
@click="deleteComment($event)"
2024-10-30 16:32:13 +01:00
>
<span class="sr-only">Supprimer</span>
</button>
</div>
2024-10-29 16:13:07 +01:00
</footer>
<footer v-else class="comment__edit-ctas">
<input
type="submit"
class="btn btn--tranparent"
value="Sauvegarder"
@click="saveEditedComment($event)"
/>
<button class="btn btn--white-10" @click="cancelEditComment($event)">
Annuler
</button>
</footer>
2024-10-29 16:13:07 +01:00
</article>
</template>
<script setup>
import dayjs from "dayjs";
import "dayjs/locale/fr";
2024-10-30 16:32:13 +01:00
import { useUserStore } from "../../stores/user";
import { useApiStore } from "../../stores/api";
2024-11-16 12:16:03 +01:00
import { useDialogStore } from "../../stores/dialog";
2025-01-08 12:12:10 +01:00
import { computed, onMounted, ref, useTemplateRef } from "vue";
2025-01-07 14:33:02 +01:00
import { storeToRefs } from "pinia";
2025-01-15 14:18:48 +01:00
import { usePageStore } from "../../stores/page";
2025-01-15 14:56:17 +01:00
import { useRoute } from "vue-router";
2024-10-29 16:13:07 +01:00
dayjs.locale("fr");
const { comment, commentIndex } = defineProps({
comment: Object,
commentIndex: Number,
});
2024-10-30 16:32:13 +01:00
const emits = defineEmits(["update:file", "close:comment"]);
2025-01-15 14:56:17 +01:00
const route = useRoute();
2024-11-18 09:36:15 +01:00
const userStore = useUserStore();
2024-10-30 16:32:13 +01:00
const api = useApiStore();
2024-11-16 12:16:03 +01:00
const dialog = useDialogStore();
2025-01-08 12:12:10 +01:00
const { activeTracks, openedComment } = storeToRefs(useDialogStore());
const draftText = ref(comment.text);
2025-01-07 16:12:33 +01:00
const editField = ref(null);
2025-01-08 12:12:10 +01:00
const commentNode = useTemplateRef("comment-node");
2025-01-15 14:18:48 +01:00
const { page } = storeToRefs(usePageStore());
2025-01-08 12:12:10 +01:00
let correspondingMarker = null;
2024-10-30 16:32:13 +01:00
2024-10-29 16:13:07 +01:00
// Functions
const getStatus = computed(() => {
2024-11-18 09:36:15 +01:00
const correspondingNotification = userStore.notifications.find(
(notification) => notification.id === comment.id
);
2025-01-10 17:40:45 +01:00
if (correspondingNotification && !correspondingNotification.isRead) {
2024-11-18 09:36:15 +01:00
return "unread";
}
2024-11-18 09:47:42 +01:00
return undefined;
});
2024-10-29 16:13:07 +01:00
2024-11-18 09:36:15 +01:00
function formatDate() {
2024-10-29 16:13:07 +01:00
const todayNumber = parseInt(dayjs().format("YYMMD"));
2024-11-18 09:36:15 +01:00
const dateNumber = parseInt(dayjs(comment.date).format("YYMMD"));
2024-10-29 16:13:07 +01:00
if (dateNumber === todayNumber) {
return "Aujourd'hui";
}
if (dateNumber === todayNumber - 1) {
return "hier";
}
2024-11-20 08:49:46 +01:00
return dayjs(comment.date).format("D MMM YY");
2024-10-29 16:13:07 +01:00
}
function closeAddField() {
isAddOpen.value = false;
newCommentText.value = "";
}
2025-01-08 12:12:10 +01:00
function handleClick() {
read();
scrollTo();
}
2024-11-18 09:36:15 +01:00
async function read() {
if (getStatus.value !== "unread") return;
2024-11-18 09:36:15 +01:00
try {
2025-01-15 15:36:43 +01:00
const newNotification = await api.readNotification(
comment.id,
page.value.uri
);
2024-11-18 09:36:15 +01:00
} catch (error) {
console.log("Erreur lors de la lecture de la notification : ", error);
}
2024-10-29 16:13:07 +01:00
}
2024-10-30 16:32:13 +01:00
2025-01-08 12:12:10 +01:00
function scrollTo() {
const correspondingMarker = document.querySelector(
`.comment-marker[href="#comment-${comment.id}"]`
);
if (!correspondingMarker) return;
correspondingMarker.scrollIntoView();
}
2024-11-18 09:36:15 +01:00
async function deleteComment(event) {
2024-10-30 16:32:13 +01:00
event.stopPropagation();
const newFile = await api.deleteComment(comment);
2025-01-07 14:35:50 +01:00
2025-01-08 12:12:10 +01:00
// If there is an active track, we are not in PDF mode with a single file.
// Thus, it's not the opened file that should be updated
// but the corresponding file in the active track.
2025-01-07 15:58:52 +01:00
if (activeTracks.value?.length > 0) {
2025-01-07 14:33:02 +01:00
activeTracks.value[0].files = activeTracks.value[0].files.map((file) => {
if (file.uuid !== newFile.uuid) return file;
return newFile;
});
} else {
dialog.updateFile(newFile);
}
2024-12-18 18:39:45 +01:00
if (comment.type === "comment-reply") {
2024-10-30 16:32:13 +01:00
emits("close:comment");
}
}
function saveEditedComment(event) {
event.stopImmediatePropagation();
comment.text = draftText.value;
comment.date = dayjs().format();
api.updateComment(comment);
2025-01-07 16:05:10 +01:00
comment.isEditMode = false;
}
function cancelEditComment(event) {
event.stopImmediatePropagation();
2025-01-07 16:05:10 +01:00
comment.isEditMode = false;
draftText.value = comment.text;
}
2025-01-07 16:12:33 +01:00
function editComment(event) {
comment.isEditMode = true;
setTimeout(() => {
editField.value.focus();
}, 100);
}
2025-01-08 12:12:10 +01:00
function hightlightCorrespondingMarker() {
if (comment.type === "comment-reply") return;
const correspondingMarker = document.querySelector(
`.comment-marker[href="#comment-${comment.id}"]`
);
if (!correspondingMarker) return;
commentNode.value.classList.add("highlight");
correspondingMarker.classList.add("active");
correspondingMarker.classList.add("big");
}
function unhightlightCorrespondingMarker() {
if (comment.type === "comment-reply") return;
const correspondingMarker = document.querySelector(
`.comment-marker[href="#comment-${comment.id}"]`
);
if (!correspondingMarker) return;
if (openedComment.value) return;
commentNode.value.classList.remove("highlight");
correspondingMarker.classList.remove("active");
correspondingMarker.classList.remove("big");
}
2024-10-29 16:13:07 +01:00
</script>
<style scoped>
.comments > .comment:not([data-opened="true"]) {
cursor: pointer;
}
.comment {
--flow-space: var(--space-12);
font-size: var(--text-sm);
border: var(--border);
border-width: 2px;
border-radius: var(--rounded-lg);
padding: var(--space-12);
color: var(--color-grey-400);
2024-11-11 17:12:26 +01:00
transition: border-color 0.1s ease-in-out;
}
2024-11-11 17:12:26 +01:00
.comment.highlight {
border-color: #fff;
}
.comment header p {
display: flex;
gap: var(--space-8);
}
.comment header strong,
.comment footer {
font-weight: 500;
color: var(--color-white);
}
.comment header time {
color: var(--color-primary);
font-weight: 500;
margin-left: auto;
}
.comment[data-status="unread"] {
background: var(--color-white);
border-color: var(--color-white);
color: var(--color-grey-700);
}
.comment[data-status="unread"] header p > :first-child::before {
content: "";
display: inline-block;
width: 0.375rem;
height: 0.375rem;
border-radius: 50%;
background: var(--color-primary);
margin-right: var(--space-8);
margin-bottom: 0.075em;
}
.comment[data-status="unread"] header strong,
.comment[data-status="unread"] footer {
color: var(--color-black);
}
.comment[data-status="unread"] header time {
color: var(--color-primary);
}
.comment[data-opened="true"] {
border-color: transparent;
}
.comment[data-opened="true"] .comment__replies {
color: var(--color-primary);
}
.comment__id,
.comment__page,
.comment__date {
flex-shrink: 0;
}
.comment__body {
width: 100%;
padding: 0;
margin-bottom: 0;
color: var(--color-grey-400);
font: inherit;
letter-spacing: inherit;
}
.comment__ctas > * {
--border-color: transparent;
margin-right: var(--space-4);
}
.comment__edit-ctas {
display: flex;
gap: var(--space-12);
}
.comment__edit-ctas > * {
flex-grow: 1;
}
</style>