content notification read working

This commit is contained in:
isUnknown 2024-12-19 10:32:48 +01:00
parent ffb8252808
commit 94c5737245
21 changed files with 317 additions and 163 deletions

View file

@ -1,5 +1,64 @@
Comments: Comments:
-
location:
page:
uri: >
projects/miss-dior-blooming-bouquet/proposal
title: Offre commerciale
project:
title: Miss Dior Blooming Bouquet
uri: projects/miss-dior-blooming-bouquet
dialogUri: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
file:
uuid: file://3vTh1tMFeFM2JxaN
url: >
http://localhost:8888/media/pages/projects/miss-dior-blooming-bouquet/proposal/788ddebfe3-1731941917/des-textos-revelent-comment-bfm-sest-mise-au-service-de-sarkozy-le-boss.pdf
position:
pageIndex: 1
x: "57.105131038425"
y: "13.127413127413"
text: un commentaire
author:
name: Adrien Payet
email: adrien.payet@outlook.com
uuid: user://WWjXgPWk
role: admin
date: 2024-12-19T10:26:05+01:00
id: e16a5304-330b-4460-acbd-a6e471f4cda2
type: comment
replies:
-
location:
page:
uri: >
projects/miss-dior-blooming-bouquet/proposal
title: Offre commerciale
project:
title: Miss Dior Blooming Bouquet
uri: projects/miss-dior-blooming-bouquet
dialogUri: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
file:
uuid: file://3vTh1tMFeFM2JxaN
url: >
http://localhost:8888/media/pages/projects/miss-dior-blooming-bouquet/proposal/788ddebfe3-1731941917/des-textos-revelent-comment-bfm-sest-mise-au-service-de-sarkozy-le-boss.pdf
parent:
author:
name: Adrien Payet
email: adrien.payet@outlook.com
id: e16a5304-330b-4460-acbd-a6e471f4cda2
position:
pageIndex: 1
text: une réponse
author:
name: Adrien Payet
email: adrien.payet@outlook.com
uuid: user://WWjXgPWk
role: admin
date: 2024-12-19T10:26:09+01:00
id: bc64e066-299d-4d80-a651-2c17482cda2f
type: comment-reply
---- ----
Cover: - file://wS6ILTHIDQb1DHJN Cover: - file://wS6ILTHIDQb1DHJN

View file

@ -26,7 +26,7 @@ Comments:
date: 2024-12-03T13:42:18+01:00 date: 2024-12-03T13:42:18+01:00
id: m48g8hrb id: m48g8hrb
type: comment type: comment
isRead: false isread: false
- -
location: location:
page: page:
@ -53,7 +53,7 @@ Comments:
date: 2024-12-03T13:43:08+01:00 date: 2024-12-03T13:43:08+01:00
id: m48g9kau id: m48g9kau
type: comment type: comment
isRead: false isread: false
---- ----

View file

@ -45,9 +45,7 @@ fields:
type: email type: email
disabled: true disabled: true
date: date:
type: date type: hidden
display: DD-MM-YY isread:
disabled: true
isRead:
type: toggle type: toggle
disabled: true disabled: true

View file

@ -26,7 +26,7 @@ return [
'dialogUri' => (string) $dialogUri, 'dialogUri' => (string) $dialogUri,
], ],
'date' => $dateTime->format('Y-m-d\TH:i:sP'), 'date' => $dateTime->format('Y-m-d\TH:i:sP'),
'text' => "Brief (" . $project->title()->value() . ")", 'text' => "Nouveau brief",
'author' => kirby()->user(), 'author' => kirby()->user(),
'id' => Str::uuid(), 'id' => Str::uuid(),
'type' => 'content' 'type' => 'content'

View file

@ -8,7 +8,7 @@ class Location
protected ?string $dialogUri = null; protected ?string $dialogUri = null;
protected ProjectDetails $project; protected ProjectDetails $project;
protected ?FileDetails $file = null; protected ?FileDetails $file = null;
protected ?string $parentCommentId = null; protected ?array $parent = null;
public function __construct(array $data) public function __construct(array $data)
{ {
@ -19,19 +19,17 @@ class Location
if (isset($data['file'])) { if (isset($data['file'])) {
$this->file = new FileDetails($data["file"]); $this->file = new FileDetails($data["file"]);
} }
if (isset($data["parentCommentId"])) { if (isset($data["parent"])) {
$this->parentCommentId(); $this->parent = [
"author" => [
"name" => $data["parent"]["author"]["name"],
"email" => $data["parent"]["author"]["email"],
],
"id" => $data["parent"]["id"]
];
} }
} }
public function setParentCommentId($id) {
$this->parentCommentId = $id;
}
public function parentId() {
return $this->parentCommentId;
}
public function toArray() { public function toArray() {
$array = [ $array = [
"page" => $this->page->toArray(), "page" => $this->page->toArray(),
@ -46,8 +44,8 @@ class Location
$array["file"] = $this->file; $array["file"] = $this->file;
} }
if ($this->parentCommentId) { if ($this->parent) {
$array["parentId"] = $this->parentCommentId; $array["parent"] = $this->parent;
} }
return $array; return $array;

View file

@ -24,28 +24,27 @@ return [
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value()); $comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
$replyData = [
"location" => [
"page" => $page,
"project" => $project,
"dialogUri" => $data->dialogUri,
"file" => $file,
],
"parentId" => $data->parentId,
"position" => [
"pageIndex" => $data->position->pageIndex,
],
"date" => (string) $data->date,
"text" => $data->text,
"author" => kirby()->user(),
"id" => Str::uuid(),
"type" => "comment",
];
$newReply = new Reply($replyData);
foreach ($comments as &$comment) { foreach ($comments as &$comment) {
if ($comment['id'] === $newReply->parentId()) { $isParentComment = $comment['id'] === $data->parentId;
if ($isParentComment) {
$replyData = [
"location" => [
"page" => $page,
"project" => $project,
"dialogUri" => $data->dialogUri,
"file" => $file,
"parent" => $comment
],
"position" => [
"pageIndex" => $data->position->pageIndex,
],
"date" => (string) $data->date,
"text" => $data->text,
"author" => kirby()->user(),
"id" => Str::uuid(),
"type" => "comment-reply",
];
$newReply = new Reply($replyData);
$comment['replies'][] = $newReply->toArray(); $comment['replies'][] = $newReply->toArray();
} }
} }

View file

@ -6,7 +6,6 @@ use adrienpayet\D2P\data\Author;
use adrienpayet\D2P\data\Position; use adrienpayet\D2P\data\Position;
class BaseComment { class BaseComment {
protected string $type;
protected Location $location; protected Location $location;
protected Author $author; protected Author $author;
protected string $text; protected string $text;

View file

@ -7,11 +7,6 @@ class Reply extends BaseComment {
public function __construct($data) { public function __construct($data) {
parent::__construct($data); parent::__construct($data);
$this->location->setParentCommentId($data["parentId"]);
}
public function parentId() {
return $this->location->parentId();
} }
public function toArray() { public function toArray() {

View file

@ -12,7 +12,7 @@ class Notification
protected Author $author; protected Author $author;
protected string $date; protected string $date;
protected string $id; protected string $id;
protected string $isRead = "false"; protected string $isread = "false";
protected ?Position $position = null; protected ?Position $position = null;
@ -33,7 +33,7 @@ class Notification
"author" => $this->author->toArray(), "author" => $this->author->toArray(),
"date" => $this->date, "date" => $this->date,
"id" => $this->id, "id" => $this->id,
"isRead" => $this->isRead, "isread" => $this->isread,
]; ];
return $array; return $array;

View file

@ -10,7 +10,7 @@ return function($notificationId) {
foreach ($notifications as $key => $notification) { foreach ($notifications as $key => $notification) {
if (!isset($notification['id'])) continue; if (!isset($notification['id'])) continue;
if ($notification['id'] === $notificationId) { if ($notification['id'] === $notificationId) {
$notifications[$key]['isRead'] = true; $notifications[$key]['isread'] = "true";
$newNotification = $notifications[$key]; $newNotification = $notifications[$key];
} }
} }

View file

@ -8,7 +8,7 @@ return function() {
$newNotification = null; $newNotification = null;
foreach ($notifications as $key => $notification) { foreach ($notifications as $key => $notification) {
$notifications[$key]['isRead'] = true; $notifications[$key]['isread'] = true;
} }
$updatedUser = $this->update([ $updatedUser = $this->update([

View file

@ -117,7 +117,7 @@ const { page } = storeToRefs(usePageStore());
const unreadNotificationsCount = computed(() => { const unreadNotificationsCount = computed(() => {
if (!user.value) return undefined; if (!user.value) return undefined;
const count = user.value.notifications.filter( const count = user.value.notifications.filter(
(notification) => notification.isRead (notification) => notification.isread
).length; ).length;
if (count === 0) return undefined; if (count === 0) return undefined;
return count; return count;
@ -164,7 +164,7 @@ function hasUnreadNotification(project) {
if (!user.value) return false; if (!user.value) return false;
return user.value.notifications.some((notification) => { return user.value.notifications.some((notification) => {
return ( return (
notification.isRead != true && notification.isread != true &&
project.uri.includes(notification.location.project.uri) project.uri.includes(notification.location.project.uri)
); );
}); });

View file

@ -16,7 +16,7 @@
<time <time
class="comment__date" class="comment__date"
:datetime="dayjs(comment.date).format('YYYY-MM-DD')" :datetime="dayjs(comment.date).format('YYYY-MM-DD')"
>{{ formatDate() }}</time >{{ formatDate(comment.date) }}</time
> >
</p> </p>
</header> </header>
@ -74,7 +74,7 @@ const status = computed(() => {
const correspondingNotification = userStore.notifications.find( const correspondingNotification = userStore.notifications.find(
(notification) => notification.id === comment.id (notification) => notification.id === comment.id
); );
if (correspondingNotification && !correspondingNotification.isRead) { if (correspondingNotification && !correspondingNotification.isread) {
return "unread"; return "unread";
} }
return undefined; return undefined;

View file

@ -0,0 +1,44 @@
<template>
<article
class="notification | bg-white rounded-lg | p-16 | flow"
:data-status="notification.isread == true ? 'read' : 'unread'"
data-type="comment"
@click="read(notification)"
>
<header>
<p class="flex">
<strong
class="notification__type | font-medium text-primary"
data-icon="comment"
>Commentaire</strong
>
<span class="notification__client | text-grey-700">{{
notification.location.project.title
}}</span>
<time
datetime=""
class="notification__date | text-grey-700 | ml-auto"
>{{ formatDate(notification) }}</time
>
</p>
</header>
<p
v-if="notification.type"
class="notification__body | text-md font-medium | line-clamp"
>
{{
notification.author.name
? notification.author.name
: notification.author.email
}}
: {{ notification.text }}
</p>
</article>
</template>
<script setup>
import { useNotificationsStore } from "../../stores/notifications";
const { notification } = defineProps({ notification: Object });
const { formatDate } = useNotificationsStore();
</script>

View file

@ -0,0 +1,42 @@
<template>
<article
class="notification | bg-white rounded-lg | p-16 | flow"
:data-status="notification.isread == 'true' ? 'read' : 'unread'"
data-type="content"
@click="read(notification)"
title="Aller au contenu"
>
<header>
<p class="flex">
<strong
class="notification__type | font-medium text-primary"
data-icon="content"
>Contenu</strong
>
<span class="notification__client | text-grey-700">{{
notification.location.project.title
}}</span>
<time
datetime=""
class="notification__date | text-grey-700 | ml-auto"
>{{ formatDate(notification) }}</time
>
</p>
</header>
<p
v-if="notification.type"
class="notification__body | text-md font-medium | line-clamp"
>
{{ notification.text }}
</p>
</article>
</template>
<script setup>
import { useRouter } from "vue-router";
import { useNotificationsStore } from "../../stores/notifications";
const router = useRouter();
const { notification } = defineProps({ notification: Object });
const { formatDate, read } = useNotificationsStore();
</script>

View file

@ -0,0 +1,49 @@
<template>
<article
class="notification | bg-white rounded-lg | p-16 | flow"
:data-status="notification.isread == true ? 'read' : 'unread'"
data-type="comment"
@click="read(notification)"
>
<header>
<p class="flex">
<strong
class="notification__type | font-medium text-primary"
data-icon="comment"
>Réponse à
{{
notification.location.parent.author.name
? notification.location.parent.author.name
: notification.location.parent.author.email
}}</strong
>
<span class="notification__client | text-grey-700">{{
notification.location.project.title
}}</span>
<time
datetime=""
class="notification__date | text-grey-700 | ml-auto"
>{{ formatDate(notification) }}</time
>
</p>
</header>
<p
v-if="notification.type"
class="notification__body | text-md font-medium | line-clamp"
>
{{
notification.author.name
? notification.author.name
: notification.author.email
}}
: {{ notification.text }}
</p>
</article>
</template>
<script setup>
import { useNotificationsStore } from "../../stores/notifications";
const { notification } = defineProps({ notification: Object });
const { formatDate } = useNotificationsStore();
</script>

View file

@ -0,0 +1,45 @@
import { defineStore } from "pinia";
import dayjs from "dayjs";
import "dayjs/locale/fr";
import { useRouter } from "vue-router";
import { useApiStore } from "./api";
export const useNotificationsStore = defineStore("notifications", () => {
dayjs.locale("fr");
const router = useRouter();
const api = useApiStore();
function formatDate(notification) {
const notificationDigitalDate = parseInt(
dayjs(notification.date).format("YYYYMMDD")
);
const todayDigitalDate = parseInt(dayjs().format("YYYYMMDD"));
const isToday = notificationDigitalDate === todayDigitalDate;
if (isToday) {
return dayjs(notification.date).format("HH:mm");
} else if (todayDigitalDate === notificationDigitalDate + 1) {
return "hier";
} else {
return dayjs(notification.date).format("DD MMM YY");
}
}
function read(notification) {
if (!notification.id) {
console.error(
"Couldn't change notification status because it has no id."
);
} else if (notification.isread != true) {
api
.readNotification(notification.id)
.then((res) => {
console.log(res);
})
.catch((err) => console.error("Notification could not be read.", err));
}
}
return { formatDate, read };
});

View file

@ -13,14 +13,14 @@ export const useUserStore = defineStore("user", () => {
function readNotification(notificationId) { function readNotification(notificationId) {
user.value.notifications.forEach((notification) => { user.value.notifications.forEach((notification) => {
if (notification.id === notificationId) { if (notification.id === notificationId) {
notification.isRead = true; notification.isread = true;
} }
}); });
} }
function readAllNotifications(notificationId) { function readAllNotifications(notificationId) {
user.value.notifications.forEach((notification) => { user.value.notifications.forEach((notification) => {
notification.isRead = true; notification.isread = true;
}); });
} }

View file

@ -6,6 +6,10 @@ function toPascalCase(string) {
}); });
} }
const StringUtils = { toPascalCase }; function urlToPath(url) {
return url.replace(window.location.origin, "");
}
const StringUtils = { toPascalCase, urlToPath };
export default StringUtils; export default StringUtils;

View file

@ -58,8 +58,7 @@ function setInitialStep() {
function validate() { function validate() {
api.validateBrief(page.value.uri).then((res) => { api.validateBrief(page.value.uri).then((res) => {
console.log(res); location.href = "/" + page.value.parent;
// location.href = "/" + page.value.parent;
}); });
} }
</script> </script>

View file

@ -38,64 +38,11 @@
v-for="notification in sortedNotifications" v-for="notification in sortedNotifications"
:key="notification.id" :key="notification.id"
> >
<article <component
class="notification | bg-white rounded-lg | p-16 | flow" :is="notificationComponents[notification.type]"
:data-status="notification.isRead ? 'read' : 'unread'" :notification="notification"
:data-type="notification.type" />
@click="read(notification)"
>
<header>
<p class="flex">
<strong
class="notification__type | font-medium text-primary"
:data-icon="notification.type"
>Nouveau
{{
notification.type === "comment" ? "commentaire" : "contenu"
}}</strong
>
<span class="notification__client | text-grey-700">{{
notification.location.project.title
}}</span>
<time
datetime=""
class="notification__date | text-grey-700 | ml-auto"
>{{ formatDate(notification) }}</time
>
</p>
</header>
<p
v-if="notification.type"
class="notification__body | text-md font-medium | line-clamp"
>
{{ notification.text }}
</p>
</article>
</template> </template>
<!-- <article
class="notification | bg-white rounded-lg | p-16 | flow"
data-status="read"
data-type="content"
>
<header>
<p class="flex">
<strong
class="notification__type | font-medium text-primary"
data-icon="content"
>Nouveau contenu</strong
>
<span class="notification__client | text-grey-700"></span>
<time
datetime=""
class="notification__date | text-grey-700 | ml-auto"
>Hier</time
>
</p>
</header>
<p class="notification__body | text-md font-medium | line-clamp">
Proposition 2
</p>
</article> -->
</section> </section>
</main> </main>
</template> </template>
@ -108,7 +55,9 @@ import { useUserStore } from "../stores/user";
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useApiStore } from "../stores/api"; import { useApiStore } from "../stores/api";
import { useRouter } from "vue-router"; import Comment from "../components/notifications/Comment.vue";
import Reply from "../components/notifications/Reply.vue";
import Content from "../components/notifications/Content.vue";
dayjs.locale("fr"); dayjs.locale("fr");
@ -135,7 +84,11 @@ const tabs = computed(() => {
]; ];
}); });
const router = useRouter(); const notificationComponents = {
comment: Comment,
"comment-reply": Reply,
content: Content,
};
const sortedNotifications = computed(() => { const sortedNotifications = computed(() => {
return [...notifications.value].sort((a, b) => { return [...notifications.value].sort((a, b) => {
@ -147,22 +100,6 @@ function changeTab(newValue) {
currentTab.value = newValue; currentTab.value = newValue;
} }
function formatDate(notification) {
const notificationDigitalDate = parseInt(
dayjs(notification.date).format("YYYYMMDD")
);
const todayDigitalDate = parseInt(dayjs().format("YYYYMMDD"));
const isToday = notificationDigitalDate === todayDigitalDate;
if (isToday) {
return dayjs(notification.date).format("HH:mm");
} else if (todayDigitalDate === notificationDigitalDate + 1) {
return "hier";
} else {
return dayjs(notification.date).format("DD MMM YY");
}
}
function readAll() { function readAll() {
try { try {
api.readAllNotifications(); api.readAllNotifications();
@ -171,24 +108,6 @@ function readAll() {
console.log("Could not read all notifications : ", error); console.log("Could not read all notifications : ", error);
} }
} }
function read(notification) {
if (!notification.id) {
console.error("Couldn't change notification status because it has no id.");
} else if (!notification.isRead) {
api
.readNotification(notification.id)
.then((res) => {
router.push(toPath(notification.location.href));
})
.catch((err) => console.error("Notification could not be read.", err));
}
router.push(toPath(notification.location.href));
}
function toPath(string) {
return string.replace(window.location.origin, "");
}
</script> </script>
<style scoped> <style scoped>
@ -207,13 +126,17 @@ main > header [role="tablist"] {
.notifications { .notifications {
--flow-space: var(--space-16); --flow-space: var(--space-16);
} }
.notification { </style>
<!-- WARNING : below styles are not scoped so as to be applied to the children components -->
<style>
.notifications .notification {
--flow-space: var(--space-12); --flow-space: var(--space-12);
font-size: var(--text-sm); font-size: var(--text-sm);
position: relative; position: relative;
cursor: pointer; cursor: pointer;
} }
.notification[data-status="unread"]::after { .notifications .notification[data-status="unread"]::after {
content: ""; content: "";
display: inline-block; display: inline-block;
width: 0.5rem; width: 0.5rem;
@ -225,22 +148,22 @@ main > header [role="tablist"] {
top: 0; top: 0;
right: 0; right: 0;
} }
.notification p { .notifications .notification p {
max-width: 100%; max-width: 100%;
} }
.notification header p { .notifications .notification header p {
column-gap: 0; column-gap: 0;
} }
.notification__type { .notifications .notification__type {
display: flex; display: flex;
} }
.notification__type::before { .notifications .notification__type::before {
margin-right: var(--space-8); margin-right: var(--space-8);
} }
.notification__client { .notifications .notification__client {
display: inline-block; display: inline-block;
} }
.notification__client::before { .notifications .notification__client::before {
content: ""; content: "";
display: inline-block; display: inline-block;
width: 3px; width: 3px;
@ -249,7 +172,7 @@ main > header [role="tablist"] {
border-radius: 50%; border-radius: 50%;
margin: 0.2em 0.5em; margin: 0.2em 0.5em;
} }
.notification__body { .notifications .notification__body {
--line-clamp: 2; --line-clamp: 2;
} }
</style> </style>