Wall: add notes attachments to posts (#907)

* Posts: add notes attachments
This commit is contained in:
lalka2018 2023-07-05 14:54:58 +03:00 committed by GitHub
parent b35b87567b
commit 2e76ca16df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 220 additions and 10 deletions

View file

@ -2,17 +2,19 @@
namespace openvk\ServiceAPI; namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\Post; use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Posts; use openvk\Web\Models\Repositories\{Posts, Notes};
class Wall implements Handler class Wall implements Handler
{ {
protected $user; protected $user;
protected $posts; protected $posts;
protected $notes;
function __construct(?User $user) function __construct(?User $user)
{ {
$this->user = $user; $this->user = $user;
$this->posts = new Posts; $this->posts = new Posts;
$this->notes = new Notes;
} }
function getPost(int $id, callable $resolve, callable $reject): void function getPost(int $id, callable $resolve, callable $reject): void
@ -71,4 +73,25 @@ class Wall implements Handler
$resolve($post->getId()); $resolve($post->getId());
} }
function getMyNotes(callable $resolve, callable $reject)
{
$myNotes = $this->notes->getUserNotes($this->user, 1, $this->notes->getUserNotesCount($this->user));
$arr = [
"count" => sizeof($myNotes),
"closed" => $this->user->getPrivacySetting("notes.read"),
"items" => [],
];
foreach($myNotes as $note) {
$arr["items"][] = [
"id" => $note->getId(),
"name" => ovk_proc_strtr($note->getName(), 30),
#"preview" => $note->getPreview()
];
}
$resolve($arr);
}
} }

View file

@ -40,6 +40,9 @@ final class Notes extends VKAPIRequestHandler
if($note->getOwner()->isDeleted()) if($note->getOwner()->isDeleted())
$this->fail(403, "Owner is deleted"); $this->fail(403, "Owner is deleted");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "No access");
if(empty($message) && empty($attachments)) if(empty($message) && empty($attachments))
$this->fail(100, "Required parameter 'message' missing."); $this->fail(100, "Required parameter 'message' missing.");
@ -183,6 +186,9 @@ final class Notes extends VKAPIRequestHandler
if(!$user || $user->isDeleted()) if(!$user || $user->isDeleted())
$this->fail(15, "Invalid user"); $this->fail(15, "Invalid user");
if(!$user->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "Access denied: this user chose to hide his notes");
if(empty($note_ids)) { if(empty($note_ids)) {
$notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset); $notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset);
$nodez = (object) [ $nodez = (object) [
@ -225,10 +231,13 @@ final class Notes extends VKAPIRequestHandler
if($note->isDeleted()) if($note->isDeleted())
$this->fail(189, "Note is deleted"); $this->fail(189, "Note is deleted");
if(!$note->getOwner() || $note->getOwner()->isDeleted()) if(!$note->getOwner() || $note->getOwner()->isDeleted())
$this->fail(177, "Owner does not exists"); $this->fail(177, "Owner does not exists");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(40, "Access denied: this user chose to hide his notes");
return $note->toVkApiStruct(); return $note->toVkApiStruct();
} }
@ -246,6 +255,9 @@ final class Notes extends VKAPIRequestHandler
if(!$note->getOwner()) if(!$note->getOwner())
$this->fail(177, "Owner does not exists"); $this->fail(177, "Owner does not exists");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(14, "No access");
$arr = (object) [ $arr = (object) [
"count" => $note->getCommentsCount(), "count" => $note->getCommentsCount(),

View file

@ -13,6 +13,8 @@ use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo; use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Entities\Video; use openvk\Web\Models\Entities\Video;
use openvk\Web\Models\Repositories\Videos as VideosRepo; use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Entities\Note;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
final class Wall extends VKAPIRequestHandler final class Wall extends VKAPIRequestHandler
{ {
@ -54,6 +56,8 @@ final class Wall extends VKAPIRequestHandler
$attachments[] = $this->getApiPoll($attachment, $this->getUser()); $attachments[] = $this->getApiPoll($attachment, $this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure(); $attachments[] = $attachment->getApiStructure();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
$repostAttachments = []; $repostAttachments = [];
@ -226,6 +230,8 @@ final class Wall extends VKAPIRequestHandler
$attachments[] = $this->getApiPoll($attachment, $user); $attachments[] = $this->getApiPoll($attachment, $user);
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure(); $attachments[] = $attachment->getApiStructure();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
$repostAttachments = []; $repostAttachments = [];
@ -440,6 +446,8 @@ final class Wall extends VKAPIRequestHandler
$attachmentType = "photo"; $attachmentType = "photo";
elseif(str_contains($attac, "video")) elseif(str_contains($attac, "video"))
$attachmentType = "video"; $attachmentType = "video";
elseif(str_contains($attac, "note"))
$attachmentType = "note";
else else
$this->fail(205, "Unknown attachment type"); $this->fail(205, "Unknown attachment type");
@ -465,6 +473,17 @@ final class Wall extends VKAPIRequestHandler
if($attacc->getOwner()->getId() != $this->getUser()->getId()) if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video"); $this->fail(43, "You do not have access to this video");
$post->attach($attacc);
} elseif($attachmentType == "note") {
$attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Note does not exist");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this note");
if($attacc->getOwner()->getPrivacySetting("notes.read") < 1)
$this->fail(11, "You can't attach note to post, because your notes list is closed. Change it in privacy settings in web-version.");
$post->attach($attacc); $post->attach($attacc);
} }
} }
@ -542,6 +561,8 @@ final class Wall extends VKAPIRequestHandler
foreach($comment->getChildren() as $attachment) { foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) { if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment); $attachments[] = $this->getApiPhoto($attachment);
} elseif($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} }
} }
@ -599,8 +620,8 @@ final class Wall extends VKAPIRequestHandler
function getComment(int $owner_id, int $comment_id, bool $extended = false, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online") { function getComment(int $owner_id, int $comment_id, bool $extended = false, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online") {
$this->requireUser(); $this->requireUser();
$comment = (new CommentsRepo)->get($comment_id); // один хуй айди всех комментов общий $comment = (new CommentsRepo)->get($comment_id); # один хуй айди всех комментов общий
$profiles = []; $profiles = [];
$attachments = []; $attachments = [];

View file

@ -123,6 +123,7 @@ class Note extends Postable
{ {
$res = (object) []; $res = (object) [];
$res->type = "note";
$res->id = $this->getId(); $res->id = $this->getId();
$res->owner_id = $this->getOwner()->getId(); $res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName(); $res->title = $this->getName();

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Exceptions\TooMuchOptionsException; use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User}; use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification}; use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums}; use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item; use Bhaktaraz\RSSGenerator\Item;
@ -278,8 +278,22 @@ final class WallPresenter extends OpenVKPresenter
} catch(\UnexpectedValueException $e) { } catch(\UnexpectedValueException $e) {
$this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid"); $this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid");
} }
$note = NULL;
if(!is_null($this->postParam("note")) && $this->postParam("note") != "none") {
$note = (new Notes)->get((int)$this->postParam("note"));
if(!$note || $note->isDeleted() || $note->getOwner()->getId() != $this->user->id) {
$this->flashFail("err", tr("error"), tr("error_attaching_note"));
}
if($note->getOwner()->getPrivacySetting("notes.read") < 1) {
$this->flashFail("err", " ");
}
}
if(empty($this->postParam("text")) && !$photo && !$video && !$poll) if(empty($this->postParam("text")) && !$photo && !$video && !$poll && !$note)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big")); $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
try { try {
@ -304,6 +318,9 @@ final class WallPresenter extends OpenVKPresenter
if(!is_null($poll)) if(!is_null($poll))
$post->attach($poll); $post->attach($poll);
if(!is_null($note))
$post->attach($note);
if($wall > 0 && $wall !== $this->user->identity->getId()) if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();

View file

@ -16,7 +16,7 @@
</div> </div>
<div n:class="postFeedWrapper, $thisUser->hasMicroblogEnabled() ? postFeedWrapperMicroblog"> <div n:class="postFeedWrapper, $thisUser->hasMicroblogEnabled() ? postFeedWrapperMicroblog">
{include "../components/textArea.xml", route => "/wall" . $thisUser->getId() . "/makePost", graffiti => true, polls => true} {include "../components/textArea.xml", route => "/wall" . $thisUser->getId() . "/makePost", graffiti => true, polls => true, notes => true}
</div> </div>
{foreach $posts as $post} {foreach $posts as $post}

View file

@ -28,6 +28,20 @@
</div> </div>
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll} {elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
{presenter "openvk!Poll->view", $attachment->getId()} {presenter "openvk!Poll->view", $attachment->getId()}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Note}
{if !$attachment->isDeleted()}
<div class="attachment_note">
<img class="attachment_note_icon" src="/assets/packages/static/openvk/img/note.svg">
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name"><a href="/note{$attachment->getPrettyId()}">{ovk_proc_strtr($attachment->getName(), 66)}</a></span>
</div>
{else}
<div class="attachment_note">
<img class="attachment_note_icon" src="/assets/packages/static/openvk/img/note.svg">
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name">{_deleted}</span>
</div>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post} {elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
{php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1} {php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1}
{if $GLOBALS["_nesAttGloCou"] > 2} {if $GLOBALS["_nesAttGloCou"] > 2}

View file

@ -13,6 +13,9 @@
</div> </div>
<div class="post-has-poll"> <div class="post-has-poll">
{_poll} {_poll}
</div>
<div class="post-has-note">
</div> </div>
<div n:if="$postOpts ?? true" class="post-opts"> <div n:if="$postOpts ?? true" class="post-opts">
{var $anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']} {var $anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']}
@ -54,6 +57,7 @@
<input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display:none;" /> <input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display:none;" />
<input n:if="!OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']" type="file" class="postFileSel" id="postFileVid" name="_vid_attachment" accept="video/*" style="display:none;" /> <input n:if="!OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']" type="file" class="postFileSel" id="postFileVid" name="_vid_attachment" accept="video/*" style="display:none;" />
<input type="hidden" name="poll" value="none" /> <input type="hidden" name="poll" value="none" />
<input type="hidden" id="note" name="note" value="none" />
<input type="hidden" name="type" value="1" /> <input type="hidden" name="type" value="1" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<br/> <br/>
@ -75,6 +79,10 @@
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" /> <img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
{_video} {_video}
</a> </a>
<a n:if="$notes ?? false" href="javascript:attachNote({$textAreaId})">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-srt.png" />
{_note}
</a>
<a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});"> <a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" /> <img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
{_graffiti} {_graffiti}

View file

@ -8,7 +8,7 @@
</div> </div>
<div> <div>
<div n:if="$canPost" class="content_subtitle"> <div n:if="$canPost" class="content_subtitle">
{include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true, polls => true} {include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true, polls => true, notes => true}
</div> </div>
<div class="content"> <div class="content">

View file

@ -1453,14 +1453,14 @@ body.scrolled .toTop:hover {
font-weight: bold; font-weight: bold;
} }
.post-upload, .post-has-poll { .post-upload, .post-has-poll, .post-has-note {
margin-top: 11px; margin-top: 11px;
margin-left: 3px; margin-left: 3px;
color: #3c3c3c; color: #3c3c3c;
display: none; display: none;
} }
.post-upload::before, .post-has-poll::before { .post-upload::before, .post-has-poll::before, .post-has-note::before {
content: " "; content: " ";
width: 8px; width: 8px;
height: 8px; height: 8px;
@ -2533,3 +2533,35 @@ a.poll-retract-vote {
{ {
background: rgb(236, 235, 235); background: rgb(236, 235, 235);
} }
.attachment_note_icon {
max-width: 9px;
}
.attachment_note_text {
color: #605F63;
margin-left: 2px;
}
.attachment_note {
user-select: none;
}
#notesList
{
overflow-y: scroll;
max-height: 130px;
margin-top: 5px;
}
.ntSelect
{
cursor: pointer;
padding: 6px;
}
.ntSelect:hover
{
background-color: rgb(233, 232, 232);
}

1
Web/static/img/note.svg Normal file
View file

@ -0,0 +1 @@
<svg id="note" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 40"><defs><style>.cls-1{fill:#a0a0a0;}</style></defs><title>note</title><polygon class="cls-1" points="0 0 0 40 35 40 35 20 17.5 20 17.5 0 0 0"/><polygon id="block" class="cls-1" points="20.26 1 35 18 20.26 18 20.26 1"/></svg>

After

Width:  |  Height:  |  Size: 292 B

View file

@ -189,3 +189,64 @@ tippy(".client_app", {
} }
}); });
function addNote(textareaId, nid)
{
if(nid > 0) {
note.value = nid
let noteObj = document.querySelector("#nd"+nid)
let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note");
nortd.style.display = "block"
nortd.innerHTML = `${tr("note")} ${escapeHtml(noteObj.dataset.name)}`
} else {
note.value = "none"
let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note");
nortd.style.display = "none"
nortd.innerHTML = ""
}
u("body").removeClass("dimmed");
u(".ovk-diag-cont").remove();
}
async function attachNote(id)
{
let notes = await API.Wall.getMyNotes()
let body = ``
if(notes.closed < 1) {
body = `${tr("notes_closed")}`
} else {
if(notes.items.length < 1) {
body = `${tr("no_notes")}`
} else {
body = `
${tr("select_or_create_new")}
<div id="notesList">`
if(note.value != "none") {
body += `
<div class="ntSelect" onclick="addNote(${id}, 0)">
<span>${tr("do_not_attach_note")}</span>
</div>`
}
for(const note of notes.items) {
body += `
<div data-name="${note.name}" class="ntSelect" id="nd${note.id}" onclick="addNote(${id}, ${note.id})">
<span>${escapeHtml(note.name)}</span>
</div>
`
}
body += `</div>`
}
}
let frame = MessageBox(tr("select_note"), body, [tr("cancel")], [Function.noop]);
document.querySelector(".ovk-diag-body").style.padding = "10px"
}

View file

@ -404,6 +404,16 @@
"notes_list_one" = "$1 note found"; "notes_list_one" = "$1 note found";
"notes_list_other" = "$1 notes found"; "notes_list_other" = "$1 notes found";
"select_note" = "Selecting note";
"no_notes" = "You don't have any notes";
"error_attaching_note" = "Error when attaching note";
"select_or_create_new" = "Select existing note or <a href='/notes/create'>create new one</a>";
"notes_closed" = "You can't attach note to post, because only you can see them.<br> You can change it in <a href=\"/settings?act=privacy\">settings</a>.";
"do_not_attach_note" = "Do not attach note";
/* Menus */ /* Menus */
/* Note that is string need to fit into the "My Page" link */ /* Note that is string need to fit into the "My Page" link */

View file

@ -389,6 +389,16 @@
"notes_list_many" = "Найдено $1 заметок"; "notes_list_many" = "Найдено $1 заметок";
"notes_list_other" = "Найдено $1 заметок"; "notes_list_other" = "Найдено $1 заметок";
"select_note" = "Выбор заметки";
"no_notes" = "У вас нет ни одной заметки";
"error_attaching_note" = "Не удалось прикрепить заметку";
"select_or_create_new" = "Выберите существующую заметку или <a href='/notes/create'>создайте новую</a>";
"notes_closed" = "Вы не можете прикрепить заметку к записи, так как ваши заметки видны только вам.<br><br> Вы можете поменять это в <a href=\"/settings?act=privacy\">настройках</a>.";
"do_not_attach_note" = "Не прикреплять заметку";
/* Menus */ /* Menus */
"edit_button" = "ред."; "edit_button" = "ред.";