Merge branch 'notifications'
This commit is contained in:
commit
caf6f721fb
34 changed files with 671 additions and 417 deletions
|
|
@ -6,112 +6,58 @@ Comments:
|
||||||
uri: >
|
uri: >
|
||||||
projects/miss-dior-blooming-bouquet/proposal
|
projects/miss-dior-blooming-bouquet/proposal
|
||||||
title: Offre commerciale
|
title: Offre commerciale
|
||||||
href: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
|
|
||||||
project:
|
project:
|
||||||
title: Miss Dior Blooming Bouquet
|
title: Miss Dior Blooming Bouquet
|
||||||
uri: projects/miss-dior-blooming-bouquet
|
uri: projects/miss-dior-blooming-bouquet
|
||||||
|
dialogUri: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
|
||||||
file:
|
file:
|
||||||
uuid: file://3vTh1tMFeFM2JxaN
|
uuid: file://3vTh1tMFeFM2JxaN
|
||||||
url: 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:
|
position:
|
||||||
pageIndex: 1
|
pageIndex: 1
|
||||||
x: "60.110185093015"
|
x: "57.105131038425"
|
||||||
y: "44.594594594595"
|
y: "13.127413127413"
|
||||||
replies: [ ]
|
text: un commentaire
|
||||||
text: test
|
|
||||||
author:
|
author:
|
||||||
name: Adrien Payet
|
name: Adrien Payet
|
||||||
email: adrien.payet@outlook.com
|
email: adrien.payet@outlook.com
|
||||||
uuid: user://WWjXgPWk
|
uuid: user://WWjXgPWk
|
||||||
role: admin
|
role: admin
|
||||||
date: 2024-11-28T16:30:56+01:00
|
date: 2024-12-19T10:26:05+01:00
|
||||||
id: m41h238q
|
id: e16a5304-330b-4460-acbd-a6e471f4cda2
|
||||||
type: comment
|
type: comment
|
||||||
isRead: false
|
replies:
|
||||||
-
|
-
|
||||||
location:
|
location:
|
||||||
page:
|
page:
|
||||||
uri: >
|
uri: >
|
||||||
projects/miss-dior-blooming-bouquet/proposal
|
projects/miss-dior-blooming-bouquet/proposal
|
||||||
title: Offre commerciale
|
title: Offre commerciale
|
||||||
href: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
|
project:
|
||||||
project:
|
title: Miss Dior Blooming Bouquet
|
||||||
title: Miss Dior Blooming Bouquet
|
uri: projects/miss-dior-blooming-bouquet
|
||||||
uri: projects/miss-dior-blooming-bouquet
|
dialogUri: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
|
||||||
file:
|
file:
|
||||||
uuid: file://3vTh1tMFeFM2JxaN
|
uuid: file://3vTh1tMFeFM2JxaN
|
||||||
url: file://3vTh1tMFeFM2JxaN
|
url: >
|
||||||
position:
|
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
|
||||||
pageIndex: 1
|
parent:
|
||||||
x: "26.098441098196"
|
author:
|
||||||
y: "64.864864864865"
|
name: Adrien Payet
|
||||||
replies: [ ]
|
email: adrien.payet@outlook.com
|
||||||
text: Nouveau commentaire
|
id: e16a5304-330b-4460-acbd-a6e471f4cda2
|
||||||
author:
|
position:
|
||||||
name: Utilisateur Dior
|
pageIndex: 1
|
||||||
email: utilisateur@dior.com
|
text: une réponse
|
||||||
uuid: user://HfuumN8s
|
author:
|
||||||
role: client
|
name: Adrien Payet
|
||||||
date: 2024-12-04T15:00:15+01:00
|
email: adrien.payet@outlook.com
|
||||||
id: m49ygks2
|
uuid: user://WWjXgPWk
|
||||||
type: comment
|
role: admin
|
||||||
isRead: false
|
date: 2024-12-19T10:26:09+01:00
|
||||||
-
|
id: bc64e066-299d-4d80-a651-2c17482cda2f
|
||||||
location:
|
type: comment-reply
|
||||||
page:
|
|
||||||
uri: >
|
|
||||||
projects/miss-dior-blooming-bouquet/proposal
|
|
||||||
title: Offre commerciale
|
|
||||||
href: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
|
|
||||||
project:
|
|
||||||
title: Miss Dior Blooming Bouquet
|
|
||||||
uri: projects/miss-dior-blooming-bouquet
|
|
||||||
file:
|
|
||||||
uuid: file://3vTh1tMFeFM2JxaN
|
|
||||||
url: file://3vTh1tMFeFM2JxaN
|
|
||||||
position:
|
|
||||||
pageIndex: 1
|
|
||||||
x: "64.481172808783"
|
|
||||||
y: "76.447876447876"
|
|
||||||
replies: [ ]
|
|
||||||
text: encore un commentaire
|
|
||||||
author:
|
|
||||||
name: Utilisateur Dior
|
|
||||||
email: utilisateur@dior.com
|
|
||||||
uuid: user://HfuumN8s
|
|
||||||
role: client
|
|
||||||
date: 2024-12-04T15:03:53+01:00
|
|
||||||
id: m49yl8zc
|
|
||||||
type: comment
|
|
||||||
isRead: false
|
|
||||||
-
|
|
||||||
location:
|
|
||||||
page:
|
|
||||||
uri: >
|
|
||||||
projects/miss-dior-blooming-bouquet/proposal
|
|
||||||
title: Offre commerciale
|
|
||||||
href: '/projects/miss-dior-blooming-bouquet?dialog=proposal&fileIndex=0'
|
|
||||||
project:
|
|
||||||
title: Miss Dior Blooming Bouquet
|
|
||||||
uri: projects/miss-dior-blooming-bouquet
|
|
||||||
file:
|
|
||||||
uuid: file://3vTh1tMFeFM2JxaN
|
|
||||||
url: file://3vTh1tMFeFM2JxaN
|
|
||||||
position:
|
|
||||||
pageIndex: 1
|
|
||||||
x: '29.786457814876'
|
|
||||||
y: '83.397683397683'
|
|
||||||
replies: [ ]
|
|
||||||
text: et encore un
|
|
||||||
author:
|
|
||||||
name: Utilisateur Dior
|
|
||||||
email: utilisateur@dior.com
|
|
||||||
uuid: user://HfuumN8s
|
|
||||||
role: client
|
|
||||||
date: 2024-12-04T15:05:10+01:00
|
|
||||||
id: m49ymwuw
|
|
||||||
type: comment
|
|
||||||
isRead: false
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,48 @@ home: /panel/pages/projects
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
notifications:
|
notifications:
|
||||||
type: hidden
|
label: Notifications
|
||||||
|
type: structure
|
||||||
|
fields:
|
||||||
|
type:
|
||||||
|
type: text
|
||||||
|
disabled: true
|
||||||
|
location:
|
||||||
|
type: object
|
||||||
|
fields:
|
||||||
|
page:
|
||||||
|
type: object
|
||||||
|
fields:
|
||||||
|
label: Page d'origine
|
||||||
|
disabled: true
|
||||||
|
title:
|
||||||
|
type: text
|
||||||
|
uri:
|
||||||
|
disabled: true
|
||||||
|
type: text
|
||||||
|
project:
|
||||||
|
label: Projet correspondant
|
||||||
|
type: object
|
||||||
|
fields:
|
||||||
|
title:
|
||||||
|
disabled: true
|
||||||
|
type: text
|
||||||
|
uri:
|
||||||
|
disabled: true
|
||||||
|
type: text
|
||||||
|
author:
|
||||||
|
label: Auteur
|
||||||
|
type: object
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
label: Nom
|
||||||
|
disabled: true
|
||||||
|
type: text
|
||||||
|
email:
|
||||||
|
type: email
|
||||||
|
disabled: true
|
||||||
|
date:
|
||||||
|
type: hidden
|
||||||
|
isread:
|
||||||
|
type: toggle
|
||||||
|
disabled: true
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ return [
|
||||||
$json = file_get_contents('php://input');
|
$json = file_get_contents('php://input');
|
||||||
$data = json_decode($json);
|
$data = json_decode($json);
|
||||||
|
|
||||||
$brief = page($data->briefUri);
|
$page = page($data->briefUri);
|
||||||
$project = $brief->parent();
|
$project = $page->parent();
|
||||||
$href = $data->dialogUri ? $data->dialogUri : $brief->url();
|
$dialogUri = $data->dialogUri ? $data->dialogUri : null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$newPage = $brief->update([
|
$newPage = $page->update([
|
||||||
'isValidated' => 'true'
|
'isValidated' => 'true'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -21,20 +21,13 @@ return [
|
||||||
|
|
||||||
$notification = [
|
$notification = [
|
||||||
'location' => [
|
'location' => [
|
||||||
'href' => (string) $href,
|
'page' => $page,
|
||||||
'project' => [
|
'project' => $project,
|
||||||
'title' => (string) $project->title(),
|
'dialogUri' => (string) $dialogUri,
|
||||||
'uri' => (string) $project->url()
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
'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' => [
|
'author' => kirby()->user(),
|
||||||
'name' => (string) kirby()->user()->name(),
|
|
||||||
'email' => (string) kirby()->user()->email(),
|
|
||||||
'uuid' => (string) kirby()->user()->uuid(),
|
|
||||||
'role' => (string) kirby()->user()->role(),
|
|
||||||
],
|
|
||||||
'id' => Str::uuid(),
|
'id' => Str::uuid(),
|
||||||
'type' => 'content'
|
'type' => 'content'
|
||||||
];
|
];
|
||||||
|
|
@ -46,7 +39,7 @@ return [
|
||||||
];
|
];
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
return [
|
return [
|
||||||
"error" => "Can't validate '" . $brief->title()->value() . "' brief.",
|
"error" => "Can't validate '" . $page->title()->value() . "' brief.",
|
||||||
'details' => $th->getMessage()
|
'details' => $th->getMessage()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
public/site/plugins/classes/Author.php
Normal file
27
public/site/plugins/classes/Author.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\D2P\data;
|
||||||
|
|
||||||
|
class Author
|
||||||
|
{
|
||||||
|
protected string $name;
|
||||||
|
protected string $email;
|
||||||
|
protected string $uuid;
|
||||||
|
protected string $role;
|
||||||
|
|
||||||
|
public function __construct($author) {
|
||||||
|
$this->name = (string) $author->name();
|
||||||
|
$this->email = (string) $author->email();
|
||||||
|
$this->uuid = (string) $author->uuid();
|
||||||
|
$this->role = (string) $author->role();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
"name" => $this->name,
|
||||||
|
"email" => $this->email,
|
||||||
|
"uuid" => $this->uuid,
|
||||||
|
"role" => $this->role,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
32
public/site/plugins/classes/Position.php
Normal file
32
public/site/plugins/classes/Position.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\D2P\data;
|
||||||
|
|
||||||
|
class Position
|
||||||
|
{
|
||||||
|
public int $pageIndex;
|
||||||
|
public ?float $x = null;
|
||||||
|
public ?float $y = null;
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->pageIndex = $data['pageIndex'];
|
||||||
|
if (isset($data["x"])) {
|
||||||
|
$this->x = (float) $data['x'];
|
||||||
|
$this->y = (float) $data['y'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
$array = [
|
||||||
|
"pageIndex" => $this->pageIndex,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->x) {
|
||||||
|
$array["x"] = $this->x;
|
||||||
|
$array["y"] = $this->y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
public/site/plugins/classes/location/FileDetails.php
Normal file
23
public/site/plugins/classes/location/FileDetails.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\D2P\data\location;
|
||||||
|
use Kirby;
|
||||||
|
|
||||||
|
class FileDetails
|
||||||
|
{
|
||||||
|
public string $uuid;
|
||||||
|
public string $url;
|
||||||
|
|
||||||
|
public function __construct(Kirby\Cms\File $data)
|
||||||
|
{
|
||||||
|
$this->uuid = (string) $data->uuid();
|
||||||
|
$this->url = (string) $data->url();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
"uuid" => $this->uuid,
|
||||||
|
"url" => $this->url
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
53
public/site/plugins/classes/location/Location.php
Normal file
53
public/site/plugins/classes/location/Location.php
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\D2P\data\location;
|
||||||
|
|
||||||
|
class Location
|
||||||
|
{
|
||||||
|
protected PageDetails $page;
|
||||||
|
protected ?string $dialogUri = null;
|
||||||
|
protected ProjectDetails $project;
|
||||||
|
protected ?FileDetails $file = null;
|
||||||
|
protected ?array $parent = null;
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->page = new PageDetails($data["page"]);
|
||||||
|
$this->dialogUri = $data["dialogUri"];
|
||||||
|
$this->project = new ProjectDetails($data["project"]);
|
||||||
|
|
||||||
|
if (isset($data['file'])) {
|
||||||
|
$this->file = new FileDetails($data["file"]);
|
||||||
|
}
|
||||||
|
if (isset($data["parent"])) {
|
||||||
|
$this->parent = [
|
||||||
|
"author" => [
|
||||||
|
"name" => $data["parent"]["author"]["name"],
|
||||||
|
"email" => $data["parent"]["author"]["email"],
|
||||||
|
],
|
||||||
|
"id" => $data["parent"]["id"]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
$array = [
|
||||||
|
"page" => $this->page->toArray(),
|
||||||
|
"project" => $this->project->toArray(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->dialogUri) {
|
||||||
|
$array["dialogUri"] = $this->dialogUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->file) {
|
||||||
|
$array["file"] = $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->parent) {
|
||||||
|
$array["parent"] = $this->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
public/site/plugins/classes/location/PageDetails.php
Normal file
21
public/site/plugins/classes/location/PageDetails.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\D2P\data\location;
|
||||||
|
use Kirby\Cms\Page;
|
||||||
|
|
||||||
|
class PageDetails
|
||||||
|
{
|
||||||
|
protected Page $page;
|
||||||
|
|
||||||
|
public function __construct(Page $page)
|
||||||
|
{
|
||||||
|
$this->page = $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
"uri" => (string) $this->page->uri(),
|
||||||
|
"title" => (string) $this->page->title(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
23
public/site/plugins/classes/location/ProjectDetails.php
Normal file
23
public/site/plugins/classes/location/ProjectDetails.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\D2P\data\location;
|
||||||
|
use Kirby;
|
||||||
|
|
||||||
|
class ProjectDetails
|
||||||
|
{
|
||||||
|
public string $title;
|
||||||
|
public string $uri;
|
||||||
|
|
||||||
|
public function __construct(Kirby\Cms\Page $page)
|
||||||
|
{
|
||||||
|
$this->title = (string) $page->title();
|
||||||
|
$this->uri = (string) $page->uri();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
return [
|
||||||
|
"title" => $this->title,
|
||||||
|
"uri" => $this->uri
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,14 @@
|
||||||
F::loadClasses([
|
F::loadClasses([
|
||||||
'adrienpayet\\comments\\BaseComment' => __DIR__ . '/src/BaseComment.php',
|
'adrienpayet\\comments\\BaseComment' => __DIR__ . '/src/BaseComment.php',
|
||||||
'adrienpayet\\comments\\Comment' => __DIR__ . '/src/Comment.php',
|
'adrienpayet\\comments\\Comment' => __DIR__ . '/src/Comment.php',
|
||||||
'adrienpayet\\comments\\Reply' => __DIR__ . '/src/Reply.php'
|
'adrienpayet\\comments\\Reply' => __DIR__ . '/src/Reply.php',
|
||||||
|
|
||||||
|
'adrienpayet\\D2P\\data\\Position' => __DIR__ . '/../classes/Position.php',
|
||||||
|
'adrienpayet\\D2P\data\Author' => __DIR__ . '/../classes/Author.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\Location' => __DIR__ . '/../classes/location/Location.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\PageDetails' => __DIR__ . '/../classes/location/PageDetails.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\ProjectDetails' => __DIR__ . '/../classes/location/ProjectDetails.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\FileDetails' => __DIR__ . '/../classes/location/FileDetails.php',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Kirby::plugin('adrienpayet/kirby4-comments', [
|
Kirby::plugin('adrienpayet/kirby4-comments', [
|
||||||
|
|
|
||||||
|
|
@ -9,49 +9,51 @@ return [
|
||||||
$json = file_get_contents('php://input');
|
$json = file_get_contents('php://input');
|
||||||
$data = json_decode($json);
|
$data = json_decode($json);
|
||||||
|
|
||||||
$parsedUrl = parse_url($data->path);
|
$parsedUrl = parse_url($data->dialogUri);
|
||||||
$query = $parsedUrl['query'] ?? null;
|
$query = $parsedUrl['query'] ?? null;
|
||||||
parse_str($query, $queryParams);
|
parse_str($query, $queryParams);
|
||||||
$stepSlug = $queryParams['dialog'] ?? null;
|
$stepSlug = $queryParams['dialog'] ?? null;
|
||||||
|
|
||||||
$targetPageUri = $stepSlug ? $parsedUrl['path'] . '/' . $stepSlug : $parsedUrl['path'];
|
$targetPageUri = $stepSlug ? $parsedUrl['path'] . '/' . $stepSlug : $parsedUrl['path'];
|
||||||
|
|
||||||
|
$project = page($parsedUrl['path']);
|
||||||
$page = page($targetPageUri);
|
$page = page($targetPageUri);
|
||||||
$file = $page->file($data->fileName);
|
$file = $page->file($data->fileName);
|
||||||
$user = kirby()->user($data->userUuid);
|
$user = kirby()->user($data->userUuid);
|
||||||
|
|
||||||
|
|
||||||
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
|
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
|
||||||
|
|
||||||
$data = [
|
$commentData = [
|
||||||
'href' => $data->path,
|
"location" => [
|
||||||
'page' => $page,
|
"page" => $page,
|
||||||
'file' => $file,
|
"project" => $project,
|
||||||
'position' => [
|
"dialogUri" => $data->dialogUri,
|
||||||
'pageIndex' => $data->position->pageIndex,
|
"file" => $file
|
||||||
'x' => $data->position->x,
|
|
||||||
'y' => $data->position->y
|
|
||||||
],
|
],
|
||||||
'replies' => [],
|
"position" => [
|
||||||
'text' => $data->text,
|
"pageIndex" => $data->position->pageIndex,
|
||||||
'author' => $user,
|
"x" => $data->position->x,
|
||||||
'date' => (string) $data->date,
|
"y" => $data->position->y
|
||||||
'id' => $data->id,
|
],
|
||||||
'type' => 'comment'
|
"date" => (string) $data->date,
|
||||||
|
"text" => $data->text,
|
||||||
|
"author" => kirby()->user(),
|
||||||
|
"id" => Str::uuid(),
|
||||||
|
"type" => "comment",
|
||||||
];
|
];
|
||||||
|
|
||||||
$newComment = new Comment($data);
|
$newComment = new Comment($commentData);
|
||||||
|
|
||||||
$comments[] = $newComment->toArray();
|
$comments[] = $newComment->toArray();
|
||||||
|
|
||||||
$newFile = $file->update([
|
$newFile = $file->update([
|
||||||
'comments' => $comments
|
'comments' => $comments
|
||||||
]);
|
]);
|
||||||
|
|
||||||
echo json_encode(getFileData($newFile));
|
echo json_encode(getFileData($newFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$user->sendNotification($page->parent(), $newComment->toArray());
|
$user->sendNotification($project, $commentData);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"error" => $th->getMessage() . " in " . $th->getFile() . " line " . $th->getLine(),
|
"error" => $th->getMessage() . " in " . $th->getFile() . " line " . $th->getLine(),
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ return [
|
||||||
$page = page($data->location->page->uri);
|
$page = page($data->location->page->uri);
|
||||||
$project = page($data->location->project->uri);
|
$project = page($data->location->project->uri);
|
||||||
$file = $page->file($data->location->file->uuid);
|
$file = $page->file($data->location->file->uuid);
|
||||||
$isReply = $data->parentId ?? false;
|
$isReply = $data->location->parentId ?? false;
|
||||||
|
|
||||||
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
|
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
|
||||||
|
|
||||||
foreach ($comments as $key => &$comment) {
|
foreach ($comments as $key => &$comment) {
|
||||||
if ($isReply) {
|
if ($isReply) {
|
||||||
if ($comment['id'] === $data->parentId) {
|
if ($comment['id'] === $data->location->parentId) {
|
||||||
foreach ($comment['replies'] as $replyKey => $reply) {
|
foreach ($comment['replies'] as $replyKey => $reply) {
|
||||||
if ($reply['id'] === $data->id) {
|
if ($reply['id'] === $data->id) {
|
||||||
unset($comment['replies'][$replyKey]);
|
unset($comment['replies'][$replyKey]);
|
||||||
|
|
|
||||||
|
|
@ -9,39 +9,42 @@ return [
|
||||||
$json = file_get_contents('php://input');
|
$json = file_get_contents('php://input');
|
||||||
$data = json_decode($json);
|
$data = json_decode($json);
|
||||||
|
|
||||||
$parsedUrl = parse_url($data->path);
|
$parsedUrl = parse_url($data->dialogUri);
|
||||||
$query = $parsedUrl['query'] ?? null;
|
$query = $parsedUrl['query'] ?? null;
|
||||||
parse_str($query, $queryParams);
|
parse_str($query, $queryParams);
|
||||||
$stepSlug = $queryParams['dialog'] ?? null;
|
$stepSlug = $queryParams['dialog'] ?? null;
|
||||||
|
|
||||||
$targetPageUri = $stepSlug ? $parsedUrl['path'] . '/' . $stepSlug : $parsedUrl['path'];
|
$targetPageUri = $stepSlug ? $parsedUrl['path'] . '/' . $stepSlug : $parsedUrl['path'];
|
||||||
|
|
||||||
|
$project = page($parsedUrl['path']);
|
||||||
$page = page($targetPageUri);
|
$page = page($targetPageUri);
|
||||||
$file = $page->file($data->fileName);
|
$file = $page->file($data->fileName);
|
||||||
$user = kirby()->user($data->userUuid);
|
$user = kirby()->user($data->userUuid);
|
||||||
|
|
||||||
|
|
||||||
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
|
$comments = $file->comments()->isEmpty() == true ? [] : Yaml::decode($file->comments()->value());
|
||||||
|
|
||||||
$data = [
|
|
||||||
'href' => $data->path,
|
|
||||||
'page' => $page,
|
|
||||||
'parentId' => $data->parentId,
|
|
||||||
'file' => $file,
|
|
||||||
'position' => [
|
|
||||||
'pageIndex' => $data->position->pageIndex,
|
|
||||||
],
|
|
||||||
'text' => $data->text,
|
|
||||||
'author' => $user,
|
|
||||||
'date' => (string) $data->date,
|
|
||||||
'id' => $data->id,
|
|
||||||
'type' => 'comment'
|
|
||||||
];
|
|
||||||
|
|
||||||
$newReply = new Reply($data);
|
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +53,7 @@ return [
|
||||||
'comments' => $comments
|
'comments' => $comments
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user->sendNotification($page->parent(), $newReply->toArray());
|
$user->sendNotification($project, $replyData);
|
||||||
|
|
||||||
return getFileData($newFile);
|
return getFileData($newFile);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,35 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace adrienpayet\comments;
|
namespace adrienpayet\comments;
|
||||||
|
use adrienpayet\D2P\data\location\Location;
|
||||||
|
use adrienpayet\D2P\data\Author;
|
||||||
|
use adrienpayet\D2P\data\Position;
|
||||||
|
|
||||||
class BaseComment {
|
class BaseComment {
|
||||||
protected $href;
|
protected Location $location;
|
||||||
protected $location;
|
protected Author $author;
|
||||||
protected $position;
|
protected string $text;
|
||||||
protected $replies;
|
protected string $date;
|
||||||
protected $text;
|
protected string $id;
|
||||||
protected $author;
|
protected Position $position;
|
||||||
protected $date;
|
|
||||||
protected $id;
|
|
||||||
protected $type;
|
|
||||||
protected $isRead;
|
|
||||||
|
|
||||||
public function __construct($data) {
|
public function __construct($data) {
|
||||||
$page = $data['page'];
|
$this->location = new Location($data["location"]);
|
||||||
$project = $page->template() == 'project' ? $page : $page->parent();
|
$this->text = $data["text"];
|
||||||
$file = $data['file'];
|
$this->author = new Author($data["author"]);
|
||||||
$position = $data['position'];
|
$this->date = $data["date"];
|
||||||
$replies = $data['replies'] ?? [];
|
$this->id = $data["id"];
|
||||||
$text = $data['text'];
|
$this->position = new Position($data['position']);
|
||||||
$author = $data['author'];
|
|
||||||
$date = $data['date'];
|
|
||||||
$id = $data['id'];
|
|
||||||
$type = $data['type'] ?? 'comment';
|
|
||||||
|
|
||||||
$this->location = [
|
|
||||||
'page' => [
|
|
||||||
'uri' => (string) $page->uri(),
|
|
||||||
'title' => (string) $page->title(),
|
|
||||||
],
|
|
||||||
'href' => (string) $data['href'],
|
|
||||||
'project' => [
|
|
||||||
'title' => (string) $project->title(),
|
|
||||||
'uri' => (string) $project->uri(),
|
|
||||||
],
|
|
||||||
'file' => $file ? [
|
|
||||||
'uuid' => (string) $file->uuid(),
|
|
||||||
'url' => (string) $file->uuid()
|
|
||||||
] : false,
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->replies = $replies ?? [];
|
|
||||||
$this->text = $text;
|
|
||||||
$this->author = [
|
|
||||||
'name' => (string) $author->name(),
|
|
||||||
'email' => (string) $author->email(),
|
|
||||||
'uuid' => (string) $author->uuid(),
|
|
||||||
'role' => (string) $author->role(),
|
|
||||||
];
|
|
||||||
$this->date = $date;
|
|
||||||
$this->id = $id;
|
|
||||||
$this->type = $type;
|
|
||||||
$this->isRead = false;
|
|
||||||
$this->position = $position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function location() {
|
|
||||||
return $this->location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function file() {
|
|
||||||
return $this->location['file'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function replies() {
|
|
||||||
return $this->replies;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function text() {
|
|
||||||
return $this->text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function author() {
|
|
||||||
return $this->author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function date() {
|
|
||||||
return $this->date;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function id() {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function type() {
|
|
||||||
return $this->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isRead() {
|
|
||||||
return $this->isRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function read() {
|
|
||||||
$this->isRead = true;
|
|
||||||
return $this->isRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unread() {
|
|
||||||
$this->isRead = false;
|
|
||||||
return $this->isRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function pageIndex() {
|
|
||||||
$this->position['pageIndex'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toArray() {
|
public function toArray() {
|
||||||
return [
|
return [
|
||||||
'location' => $this->location,
|
"location" => $this->location->toArray(),
|
||||||
'position' => $this->position,
|
"position" => $this->position->toArray(),
|
||||||
'replies' => $this->replies,
|
"text" => $this->text,
|
||||||
'text' => $this->text,
|
"author" => $this->author->toArray(),
|
||||||
'author' => $this->author,
|
"date" => $this->date,
|
||||||
'date' => $this->date,
|
"id" => $this->id,
|
||||||
'id' => $this->id,
|
|
||||||
'type' => $this->type,
|
|
||||||
'isRead' => $this->isRead
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,17 @@ namespace adrienpayet\comments;
|
||||||
|
|
||||||
class Comment extends BaseComment
|
class Comment extends BaseComment
|
||||||
{
|
{
|
||||||
protected $position;
|
protected string $type = "comment";
|
||||||
|
protected array $replies = [];
|
||||||
|
|
||||||
public function __construct($data) {
|
public function __construct($data) {
|
||||||
parent::__construct($data);
|
parent::__construct($data);
|
||||||
$this->position = $data['position'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function position() {
|
|
||||||
return $this->position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toArray() {
|
public function toArray() {
|
||||||
$array = parent::toArray();
|
$array = parent::toArray();
|
||||||
$array['position'] = $this->position;
|
$array['type'] = $this->type;
|
||||||
|
$array['replies'] = $this->replies;
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,15 @@ namespace adrienpayet\comments;
|
||||||
|
|
||||||
class Reply extends BaseComment {
|
class Reply extends BaseComment {
|
||||||
|
|
||||||
protected $parentId;
|
protected string $type = "comment-reply";
|
||||||
|
|
||||||
public function __construct($data) {
|
public function __construct($data) {
|
||||||
parent::__construct($data);
|
parent::__construct($data);
|
||||||
$this->parentId = $data['parentId'];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function parentId() {
|
|
||||||
return $this->parentId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toArray() {
|
public function toArray() {
|
||||||
$array = parent::toArray();
|
$array = parent::toArray();
|
||||||
$array['parentId'] = $this->parentId;
|
$array['type'] = $this->type;
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,17 @@ load([
|
||||||
'ProjectPage' => 'models/ProjectPage.php',
|
'ProjectPage' => 'models/ProjectPage.php',
|
||||||
], __DIR__);
|
], __DIR__);
|
||||||
|
|
||||||
|
F::loadClasses([
|
||||||
|
'adrienpayet\\notifications\\Notification' => __DIR__ . '/src/Notification.php',
|
||||||
|
'adrienpayet\\D2P\\data\\Position' => __DIR__ . '/../classes/Position.php',
|
||||||
|
'adrienpayet\\D2P\data\Author' => __DIR__ . '/../classes/Author.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\Location' => __DIR__ . '/../classes/location/Location.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\PageDetails' => __DIR__ . '/../classes/location/PageDetails.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\ProjectDetails' => __DIR__ . '/../classes/location/ProjectDetails.php',
|
||||||
|
'adrienpayet\\D2P\\data\\location\\FileDetails' => __DIR__ . '/../classes/location/FileDetails.php',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
Kirby::plugin('adrienpayet/pdc-notifications', [
|
Kirby::plugin('adrienpayet/pdc-notifications', [
|
||||||
'routes' => [
|
'routes' => [
|
||||||
require(__DIR__ . '/routes/readAll.php'),
|
require(__DIR__ . '/routes/readAll.php'),
|
||||||
|
|
|
||||||
42
public/site/plugins/notifications/src/Notification.php
Normal file
42
public/site/plugins/notifications/src/Notification.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace adrienpayet\notifications;
|
||||||
|
use adrienpayet\D2P\data\location\Location;
|
||||||
|
use adrienpayet\D2P\data\Author;
|
||||||
|
|
||||||
|
class Notification
|
||||||
|
{
|
||||||
|
protected string $type;
|
||||||
|
protected Location $location;
|
||||||
|
protected string $text;
|
||||||
|
protected Author $author;
|
||||||
|
protected string $date;
|
||||||
|
protected string $id;
|
||||||
|
protected string $isread = "false";
|
||||||
|
|
||||||
|
protected ?Position $position = null;
|
||||||
|
|
||||||
|
public function __construct($data) {
|
||||||
|
$this->type = $data["type"];
|
||||||
|
$this->location = new Location($data["location"]);
|
||||||
|
$this->text = $data["text"];
|
||||||
|
$this->author = new Author($data["author"]);
|
||||||
|
$this->date = $data["date"];
|
||||||
|
$this->id = $data["id"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray() {
|
||||||
|
$array = [
|
||||||
|
"type" => $this->type,
|
||||||
|
"location" => $this->location->toArray(),
|
||||||
|
"text" => $this->text,
|
||||||
|
"author" => $this->author->toArray(),
|
||||||
|
"date" => $this->date,
|
||||||
|
"id" => $this->id,
|
||||||
|
"isread" => $this->isread,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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([
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use adrienpayet\notifications\Notification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a notification to all users who manage a specific project, excluding the user sending the notification.
|
* Send a notification to all users who manage a specific project, excluding the user sending the notification.
|
||||||
*
|
*
|
||||||
|
|
@ -14,26 +16,26 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return function ($project, $notificationData) {
|
return function ($project, $notificationData) {
|
||||||
$recipients = $project->managers()->without($this);
|
$recipients = $project->managers()->without($this);
|
||||||
|
|
||||||
if ($recipients->isEmpty()) return;
|
if ($recipients->isEmpty()) return;
|
||||||
|
|
||||||
$notificationData['isRead'] = false;
|
$newNotification = new Notification($notificationData);
|
||||||
|
|
||||||
foreach ($recipients as $otherUser) {
|
foreach ($recipients as $otherUser) {
|
||||||
try {
|
try {
|
||||||
$notifications = $otherUser->notifications()->isNotEmpty()
|
$notifications = $otherUser->notifications()->isNotEmpty()
|
||||||
? Yaml::decode($otherUser->notifications()->value())
|
? Yaml::decode($otherUser->notifications()->value())
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
$notifications[] = $notificationData;
|
$notifications[] = $newNotification->toArray();
|
||||||
|
|
||||||
$otherUser->update([
|
$otherUser->update([
|
||||||
'notifications' => $notifications
|
'notifications' => $notifications
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
error_log("Notification error for user " . $otherUser->email() . ": " . $th->getMessage());
|
error_log("Notification error for user " . $otherUser->email() . ": " . $th->getMessage());
|
||||||
throw new Exception("Error updating notifications: " . $th->getMessage() . ' line ' . $th->getLine(), 1);
|
throw new Exception("Error updating notifications: " . $th->getMessage() . ' line ' . $th->getLine(), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 != "true"
|
||||||
).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)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<article
|
<article
|
||||||
:id="`comment-${comment.id}`"
|
:id="`comment-${comment.id}`"
|
||||||
class="comment | flow"
|
class="comment | flow"
|
||||||
:data-status="status"
|
:data-status="getStatus"
|
||||||
@click="read()"
|
@click="read()"
|
||||||
>
|
>
|
||||||
<header>
|
<header>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -70,11 +70,11 @@ const api = useApiStore();
|
||||||
const dialog = useDialogStore();
|
const dialog = useDialogStore();
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
const status = computed(() => {
|
const getStatus = 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 != "true") {
|
||||||
return "unread";
|
return "unread";
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
@ -101,9 +101,10 @@ function closeAddField() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function read() {
|
async function read() {
|
||||||
if (status.value !== "unread") return;
|
if (getStatus.value !== "unread") return;
|
||||||
try {
|
try {
|
||||||
const newNotification = await api.readNotification(comment.id);
|
const newNotification = await api.readNotification(comment.id);
|
||||||
|
console.log(newNotification);
|
||||||
userStore.readNotification(comment.id);
|
userStore.readNotification(comment.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Erreur lors de la lecture de la notification : ", error);
|
console.log("Erreur lors de la lecture de la notification : ", error);
|
||||||
|
|
@ -112,9 +113,10 @@ async function read() {
|
||||||
|
|
||||||
async function deleteComment(event) {
|
async function deleteComment(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
console.log(comment);
|
||||||
const newFile = await api.deleteComment(comment);
|
const newFile = await api.deleteComment(comment);
|
||||||
dialog.updateFile(newFile);
|
dialog.updateFile(newFile);
|
||||||
if (comment.parentId) {
|
if (comment.type === "comment-reply") {
|
||||||
emits("close:comment");
|
emits("close:comment");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ function handleSubmit(event = null) {
|
||||||
}
|
}
|
||||||
const date = dayjs().format();
|
const date = dayjs().format();
|
||||||
const newComment = {
|
const newComment = {
|
||||||
path: route.fullPath,
|
dialogUri: route.fullPath,
|
||||||
fileName: openedFile ? openedFile.value.name : false,
|
fileName: openedFile ? openedFile.value.name : false,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
text: draftComment.value.text,
|
text: draftComment.value.text,
|
||||||
|
|
|
||||||
46
src/components/notifications/Comment.vue
Normal file
46
src/components/notifications/Comment.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<article
|
||||||
|
class="notification | bg-white rounded-lg | p-16 | flow"
|
||||||
|
:data-status="notification.isread == 'true' ? 'read' : 'unread'"
|
||||||
|
data-type="comment"
|
||||||
|
@click="router.push(notification.location.dialoguri + '&comments=true')"
|
||||||
|
>
|
||||||
|
<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 { useRouter } from "vue-router";
|
||||||
|
import { useNotificationsStore } from "../../stores/notifications";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { notification } = defineProps({ notification: Object });
|
||||||
|
const { formatDate } = useNotificationsStore();
|
||||||
|
</script>
|
||||||
45
src/components/notifications/Content.vue
Normal file
45
src/components/notifications/Content.vue
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<article
|
||||||
|
class="notification | bg-white rounded-lg | p-16 | flow"
|
||||||
|
:data-status="notification.isread == 'true' ? 'read' : 'unread'"
|
||||||
|
data-type="content"
|
||||||
|
@click="
|
||||||
|
read(notification);
|
||||||
|
router.push(notification.location.page.uri);
|
||||||
|
"
|
||||||
|
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>
|
||||||
51
src/components/notifications/Reply.vue
Normal file
51
src/components/notifications/Reply.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<article
|
||||||
|
class="notification | bg-white rounded-lg | p-16 | flow"
|
||||||
|
:data-status="notification.isread == 'true' ? 'read' : 'unread'"
|
||||||
|
data-type="comment"
|
||||||
|
@click="router.push(notification.location.dialoguri + '&comments=true')"
|
||||||
|
>
|
||||||
|
<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 { useRouter } from "vue-router";
|
||||||
|
import { useNotificationsStore } from "../../stores/notifications";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { notification } = defineProps({ notification: Object });
|
||||||
|
const { formatDate } = useNotificationsStore();
|
||||||
|
</script>
|
||||||
|
|
@ -36,6 +36,11 @@ const draftComment = ref(null);
|
||||||
|
|
||||||
const isViewerDisabled = ref(false);
|
const isViewerDisabled = ref(false);
|
||||||
|
|
||||||
|
watch(openedFile, () => {
|
||||||
|
removeCommentMarkers();
|
||||||
|
setCommentMarkers();
|
||||||
|
});
|
||||||
|
|
||||||
watch(isCommentsOpen, (newVal) => {
|
watch(isCommentsOpen, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
setCommentMarkers();
|
setCommentMarkers();
|
||||||
|
|
@ -45,6 +50,8 @@ watch(isCommentsOpen, (newVal) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(openedFile, (newVal) => {
|
watch(openedFile, (newVal) => {
|
||||||
|
if (!location.href.includes("virtual-sample")) return;
|
||||||
|
|
||||||
isViewerDisabled.value = true;
|
isViewerDisabled.value = true;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,6 @@ function getFrontView(track) {
|
||||||
const extension = track.files[0].name.split(".")[1];
|
const extension = track.files[0].name.split(".")[1];
|
||||||
const frontViewName = "0_" + xFrontView + "." + extension;
|
const frontViewName = "0_" + xFrontView + "." + extension;
|
||||||
const frontView = track.files.find((file) => file.name === frontViewName);
|
const frontView = track.files.find((file) => file.name === frontViewName);
|
||||||
console.log(frontView);
|
|
||||||
return frontView;
|
return frontView;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
45
src/stores/notifications.js
Normal file
45
src/stores/notifications.js
Normal 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 };
|
||||||
|
});
|
||||||
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue