mirror of
https://github.com/openvk/openvk
synced 2024-11-11 01:19:53 +03:00
Редактирование постов только покруче (#979)
* Add editing posts * Add checkboxes * Add ctrl+enter + fix empty posts * Fix funny bug
This commit is contained in:
parent
14d5caaf9f
commit
97a176c261
14 changed files with 270 additions and 21 deletions
|
@ -90,4 +90,12 @@ class Comment extends Post
|
|||
{
|
||||
return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId();
|
||||
}
|
||||
|
||||
function canBeEditedBy(?User $user = NULL): bool
|
||||
{
|
||||
if(!$user)
|
||||
return false;
|
||||
|
||||
return $user->getId() == $this->getOwner(false)->getId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,5 +246,16 @@ class Post extends Postable
|
|||
$this->save();
|
||||
}
|
||||
|
||||
function canBeEditedBy(?User $user = NULL): bool
|
||||
{
|
||||
if(!$user)
|
||||
return false;
|
||||
|
||||
if($this->isDeactivationMessage() || $this->isUpdateAvatarMessage())
|
||||
return false;
|
||||
|
||||
return $user->getId() == $this->getOwner(false)->getId();
|
||||
}
|
||||
|
||||
use Traits\TRichText;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
|
|||
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
|
||||
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
|
||||
use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification};
|
||||
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes};
|
||||
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Comments};
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use Nette\InvalidStateException as ISE;
|
||||
use Bhaktaraz\RSSGenerator\Item;
|
||||
|
@ -498,4 +498,63 @@ final class WallPresenter extends OpenVKPresenter
|
|||
# TODO localize message based on language and ?act=(un)pin
|
||||
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
|
||||
}
|
||||
|
||||
function renderEdit()
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST")
|
||||
$this->redirect("/id0");
|
||||
|
||||
if($this->postParam("type") == "post")
|
||||
$post = $this->posts->get((int)$this->postParam("postid"));
|
||||
else
|
||||
$post = (new Comments)->get((int)$this->postParam("postid"));
|
||||
|
||||
if(!$post || $post->isDeleted())
|
||||
$this->returnJson(["error" => "Invalid post"]);
|
||||
|
||||
if(!$post->canBeEditedBy($this->user->identity))
|
||||
$this->returnJson(["error" => "Access denied"]);
|
||||
|
||||
$attachmentsCount = sizeof(iterator_to_array($post->getChildren()));
|
||||
|
||||
if(empty($this->postParam("newContent")) && $attachmentsCount < 1)
|
||||
$this->returnJson(["error" => "Empty post"]);
|
||||
|
||||
$post->setEdited(time());
|
||||
|
||||
try {
|
||||
$post->setContent($this->postParam("newContent"));
|
||||
} catch(\LengthException $e) {
|
||||
$this->returnJson(["error" => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if($this->postParam("type") === "post") {
|
||||
$post->setNsfw($this->postParam("nsfw") == "true");
|
||||
$flags = 0;
|
||||
|
||||
if($post->getTargetWall() < 0 && $post->getWallOwner()->canBeModifiedBy($this->user->identity)) {
|
||||
if($this->postParam("fromgroup") == "true") {
|
||||
$flags |= 0b10000000;
|
||||
$post->setFlags($flags);
|
||||
} else
|
||||
$post->setFlags($flags);
|
||||
}
|
||||
}
|
||||
|
||||
$post->save(true);
|
||||
|
||||
$this->returnJson(["error" => "no",
|
||||
"new_content" => $post->getText(),
|
||||
"new_edited" => (string)$post->getEditTime(),
|
||||
"nsfw" => $this->postParam("type") === "post" ? (int)$post->isExplicit() : 0,
|
||||
"from_group" => $this->postParam("type") === "post" && $post->getTargetWall() < 0 ?
|
||||
((int)$post->isPostedOnBehalfOfGroup()) : "false",
|
||||
"author" => [
|
||||
"name" => $post->getOwner()->getCanonicalName(),
|
||||
"avatar" => $post->getOwner()->getAvatarUrl()
|
||||
]]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,14 @@
|
|||
{/if}
|
||||
|
||||
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
|
||||
<a
|
||||
n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) AND $post->getEditTime()"
|
||||
style="display:block;width:96%;"
|
||||
class="profile_link"
|
||||
href="/admin/logs?type=1&obj_type=Post&obj_id={$post->getId()}"
|
||||
>
|
||||
{_changes_history}
|
||||
</a>
|
||||
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportPost()">{_report}</a>
|
||||
</div>
|
||||
<script n:if="$canReport ?? false">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<div class="post-content" id="{$comment->getId()}">
|
||||
<div class="text" id="text{$comment->getId()}">
|
||||
{$comment->getText()|noescape}
|
||||
<span class="really_text">{$comment->getText()|noescape}</span>
|
||||
|
||||
<div n:ifcontent class="attachments_b">
|
||||
<div class="attachment" n:foreach="$comment->getChildren() as $attachment" data-localized-nsfw-text="{_nsfw_warning}">
|
||||
|
@ -29,17 +29,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
|
||||
<a
|
||||
href="{=$linkWithPost && get_class($comment->getTarget()) == 'openvk\Web\Models\Entities\Post' ? '/wall' . $comment->getTarget()->getPrettyId() : ''}#_comment{$comment->getId()}"
|
||||
class="date"
|
||||
>
|
||||
{$comment->getPublicationTime()}
|
||||
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}
|
||||
<span n:if="$comment->getEditTime()" class="edited editedMark">({_edited_short})</span>
|
||||
</a>
|
||||
{if !$timeOnly}
|
||||
|
|
||||
{if $comment->canBeDeletedBy($thisUser)}
|
||||
<a href="/comment{$comment->getId()}/delete">{_delete}</a> |
|
||||
{/if}
|
||||
{if $comment->canBeEditedBy($thisUser)}
|
||||
<a id="editPost" data-id="{$comment->getId()}">{_edit}</a> |
|
||||
{/if}
|
||||
<a class="comment-reply">{_reply}</a>
|
||||
{if $thisUser->getId() != $comment->getOwner()->getId()}
|
||||
{var $canReport = true}
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
<tr>
|
||||
<td width="54" valign="top">
|
||||
<a href="{$author->getURL()}">
|
||||
<img src="{$author->getAvatarURL('miniscule')}" width="{if $compact}25{else}50{/if}" {if $compact}class="cCompactAvatars"{/if} />
|
||||
<img src="{$author->getAvatarURL('miniscule')}" width="{if $compact}25{else}50{/if}" class="post-avatar {if $compact}cCompactAvatars{/if}" />
|
||||
<span n:if="!$post->isPostedOnBehalfOfGroup() && !$compact && $author->isOnline()" class="post-online">{_online}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td width="100%" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{$author->getURL()}"><b>{$author->getCanonicalName()}</b></a>
|
||||
<a href="{$author->getURL()}"><b class="post-author-name">{$author->getCanonicalName()}</b></a>
|
||||
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">
|
||||
{$post->isDeactivationMessage() ? ($author->isFemale() ? tr($deac . "_f") : tr($deac . "_m"))}
|
||||
{$post->isUpdateAvatarMessage() && !$post->isPostedOnBehalfOfGroup() ? ($author->isFemale() ? tr("upd_f") : tr("upd_m"))}
|
||||
|
@ -62,10 +62,17 @@
|
|||
<a class="pin" href="/wall{$post->getPrettyId()}/pin?act=pin&hash={rawurlencode($csrfToken)}"></a>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{if $post->canBeEditedBy($thisUser) && !($forceNoEditLink ?? false) && $compact == false}
|
||||
<a class="edit" id="editPost"
|
||||
data-id="{$post->getId()}"
|
||||
data-nsfw="{(int)$post->isExplicit()}"
|
||||
{if $post->getTargetWall() < 0 && $post->getWallOwner()->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}></a>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="post-content" id="{$post->getPrettyId()}">
|
||||
<div class="text" id="text{$post->getPrettyId()}">
|
||||
{$post->getText()|noescape}
|
||||
<div class="text">
|
||||
<span class="really_text">{$post->getText()|noescape}</span>
|
||||
|
||||
<div n:ifcontent class="attachments_b">
|
||||
<div class="attachment" n:foreach="$post->getChildren() as $attachment" data-localized-nsfw-text="{_nsfw_warning}">
|
||||
|
@ -88,7 +95,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="post-menu" n:if="$compact == false">
|
||||
<a href="/wall{$post->getPrettyId()}" class="date">{$post->getPublicationTime()}</a>
|
||||
<a href="/wall{$post->getPrettyId()}" class="date">{$post->getPublicationTime()}
|
||||
<span n:if="$post->getEditTime()" class="edited editedMark">({_edited_short})</span>
|
||||
</a>
|
||||
<a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}">
|
||||
<img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg">
|
||||
</a>
|
||||
|
|
|
@ -7,18 +7,20 @@
|
|||
{var $deac = "post_deact_silent"}
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
<table border="0" style="font-size: 11px;" n:class="post, $post->isExplicit() ? post-nsfw">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
<a href="{$author->getURL()}">
|
||||
<img src="{$author->getAvatarURL('miniscule')}" width="50" />
|
||||
<img src="{$author->getAvatarURL('miniscule')}" class="post-avatar" width="50" />
|
||||
<span n:if="!$post->isPostedOnBehalfOfGroup() && !($compact ?? false) && $author->isOnline()" class="post-online">{_online}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td width="100%" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{$author->getURL()}"><b>{$author->getCanonicalName()}</b></a>
|
||||
<a href="{$author->getURL()}"><b class="post-author-name">{$author->getCanonicalName()}</b></a>
|
||||
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">
|
||||
{if $post->isDeactivationMessage()}
|
||||
{$author->isFemale() ? tr($deac . "_f") : tr($deac . "_m")}
|
||||
|
@ -51,15 +53,17 @@
|
|||
{/if}
|
||||
<br/>
|
||||
<a href="/wall{$post->getPrettyId()}" class="date">
|
||||
{$post->getPublicationTime()}{if $post->isPinned()}, {_pinned}{/if}
|
||||
{$post->getPublicationTime()} <span n:if="$post->getEditTime()" class="editedMark">({_edited_short})</span>{if $post->isPinned()}, {_pinned}{/if}
|
||||
<a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}">
|
||||
<img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg">
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
<div class="post-content" id="{$post->getPrettyId()}">
|
||||
<div class="text" id="text{$post->getPrettyId()}">
|
||||
{$post->getText()|noescape}
|
||||
<div class="text">
|
||||
{var $owner = $author->getId()}
|
||||
|
||||
<span class="really_text">{$post->getText()|noescape}</span>
|
||||
|
||||
<div n:ifcontent class="attachments_b">
|
||||
<div class="attachment" n:foreach="$post->getChildren() as $attachment" data-localized-nsfw-text="{_nsfw_warning}">
|
||||
|
@ -87,6 +91,13 @@
|
|||
{var $forceNoPinLink = true}
|
||||
{/if}
|
||||
|
||||
{if !($forceNoEditLink ?? false) && $post->canBeEditedBy($thisUser)}
|
||||
<a id="editPost"
|
||||
data-id="{$post->getId()}"
|
||||
data-nsfw="{(int)$post->isExplicit()}"
|
||||
{if $post->getTargetWall() < 0 && $post->getWallOwner()->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}>{_edit}</a> |
|
||||
{/if}
|
||||
|
||||
{if !($forceNoDeleteLink ?? false) && $post->canBeDeletedBy($thisUser)}
|
||||
<a href="/wall{$post->getPrettyId()}/delete">{_delete}</a> |
|
||||
{/if}
|
||||
|
|
|
@ -129,6 +129,8 @@ routes:
|
|||
handler: "Wall->rss"
|
||||
- url: "/wall{num}/makePost"
|
||||
handler: "Wall->makePost"
|
||||
- url: "/wall/edit"
|
||||
handler: "Wall->edit"
|
||||
- url: "/wall{num}_{num}"
|
||||
handler: "Wall->post"
|
||||
- url: "/wall{num}_{num}/like"
|
||||
|
|
|
@ -2702,6 +2702,10 @@ body.article .floating_sidebar, body.article .page_content {
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.edited {
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.uploadedImage img {
|
||||
max-height: 76px;
|
||||
object-fit: cover;
|
||||
|
@ -2713,6 +2717,16 @@ body.article .floating_sidebar, body.article .page_content {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.editMenu.loading {
|
||||
filter: opacity(0.5);
|
||||
cursor: progress;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.editMenu.loading * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lagged * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -2797,3 +2811,4 @@ body.article .floating_sidebar, body.article .page_content {
|
|||
.smallFrame:hover {
|
||||
background: #E9F0F1 !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,10 +110,24 @@
|
|||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.post-author .edit {
|
||||
float: right;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
overflow: auto;
|
||||
background: url("/assets/packages/static/openvk/img/edit.png") no-repeat 0 0;
|
||||
opacity: 0.1;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.post-author .pin:hover {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.post-author .edit:hover {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.expand_button {
|
||||
background-color: #eee;
|
||||
width: 100%;
|
||||
|
|
BIN
Web/static/img/edit.png
Normal file
BIN
Web/static/img/edit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 571 B |
|
@ -263,3 +263,110 @@ async function showArticle(note_id) {
|
|||
u("body").removeClass("dimmed");
|
||||
u("body").addClass("article");
|
||||
}
|
||||
|
||||
$(document).on("click", "#editPost", (e) => {
|
||||
let post = e.currentTarget.closest("table")
|
||||
let content = post.querySelector(".text")
|
||||
let text = content.querySelector(".really_text")
|
||||
|
||||
if(content.querySelector("textarea") == null) {
|
||||
content.insertAdjacentHTML("afterbegin", `
|
||||
<div class="editMenu">
|
||||
<div id="wall-post-input999">
|
||||
<textarea id="new_content">${text.innerHTML.replace(/(<([^>]+)>)/gi, '')}</textarea>
|
||||
<input type="button" class="button" value="${tr("save")}" id="endEditing">
|
||||
<input type="button" class="button" value="${tr("cancel")}" id="cancelEditing">
|
||||
</div>
|
||||
${e.currentTarget.dataset.nsfw != null ? `
|
||||
<div class="postOptions">
|
||||
<label><input type="checkbox" id="nswfw" ${e.currentTarget.dataset.nsfw == 1 ? `checked` : ``}>${tr("contains_nsfw")}</label>
|
||||
</div>
|
||||
` : ``}
|
||||
${e.currentTarget.dataset.fromgroup != null ? `
|
||||
<div class="postOptions">
|
||||
<label><input type="checkbox" id="fromgroup" ${e.currentTarget.dataset.fromgroup == 1 ? `checked` : ``}>${tr("post_as_group")}</label>
|
||||
</div>
|
||||
` : ``}
|
||||
</div>
|
||||
`)
|
||||
|
||||
u(content.querySelector("#cancelEditing")).on("click", () => {post.querySelector("#editPost").click()})
|
||||
u(content.querySelector("#endEditing")).on("click", () => {
|
||||
let nwcntnt = content.querySelector("#new_content").value
|
||||
let type = "post"
|
||||
|
||||
if(post.classList.contains("comment")) {
|
||||
type = "comment"
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", "/wall/edit")
|
||||
|
||||
xhr.onloadstart = () => {
|
||||
content.querySelector(".editMenu").classList.add("loading")
|
||||
}
|
||||
|
||||
xhr.onerror = () => {
|
||||
MessageBox(tr("error"), "unknown error occured", [tr("ok")], [() => {Function.noop}])
|
||||
}
|
||||
|
||||
xhr.ontimeout = () => {
|
||||
MessageBox(tr("error"), "Try to refresh page", [tr("ok")], [() => {Function.noop}])
|
||||
}
|
||||
|
||||
xhr.onload = () => {
|
||||
let result = JSON.parse(xhr.responseText)
|
||||
|
||||
if(result.error == "no") {
|
||||
post.querySelector("#editPost").click()
|
||||
content.querySelector(".really_text").innerHTML = result.new_content
|
||||
|
||||
if(post.querySelector(".editedMark") == null) {
|
||||
post.querySelector(".date").insertAdjacentHTML("beforeend", `
|
||||
<span class="edited editedMark">(${tr("edited_short")})</span>
|
||||
`)
|
||||
}
|
||||
|
||||
if(e.currentTarget.dataset.nsfw != null) {
|
||||
e.currentTarget.setAttribute("data-nsfw", result.nsfw)
|
||||
|
||||
if(result.nsfw == 0) {
|
||||
post.classList.remove("post-nsfw")
|
||||
} else {
|
||||
post.classList.add("post-nsfw")
|
||||
}
|
||||
}
|
||||
|
||||
if(e.currentTarget.dataset.fromgroup != null) {
|
||||
e.currentTarget.setAttribute("data-fromgroup", result.from_group)
|
||||
}
|
||||
|
||||
post.querySelector(".post-avatar").setAttribute("src", result.author.avatar)
|
||||
post.querySelector(".post-author-name").innerHTML = result.author.name
|
||||
} else {
|
||||
MessageBox(tr("error"), result.error, [tr("ok")], [Function.noop])
|
||||
post.querySelector("#editPost").click()
|
||||
}
|
||||
}
|
||||
|
||||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhr.send("postid="+e.currentTarget.dataset.id+
|
||||
"&newContent="+nwcntnt+
|
||||
"&hash="+encodeURIComponent(u("meta[name=csrf]").attr("value"))+
|
||||
"&type="+type+
|
||||
"&nsfw="+(content.querySelector("#nswfw") != null ? content.querySelector("#nswfw").checked : 0)+
|
||||
"&fromgroup="+(content.querySelector("#fromgroup") != null ? content.querySelector("#fromgroup").checked : 0))
|
||||
})
|
||||
|
||||
u(".editMenu").on("keydown", (e) => {
|
||||
if(e.ctrlKey && e.keyCode === 13)
|
||||
content.querySelector("#endEditing").click()
|
||||
});
|
||||
|
||||
text.style.display = "none"
|
||||
setupWallPostInputHandlers(999)
|
||||
} else {
|
||||
u(content.querySelector(".editMenu")).remove()
|
||||
text.style.display = "block"
|
||||
}
|
||||
})
|
||||
|
|
|
@ -214,6 +214,8 @@
|
|||
|
||||
"reply" = "Reply";
|
||||
|
||||
"edited_short" = "edited";
|
||||
|
||||
/* Friends */
|
||||
|
||||
"friends" = "Friends";
|
||||
|
@ -1149,6 +1151,7 @@
|
|||
"warn_user_action" = "Warn user";
|
||||
"ban_in_support_user_action" = "Ban in support";
|
||||
"unban_in_support_user_action" = "Unban in support";
|
||||
"changes_history" = "Editing history";
|
||||
|
||||
/* Admin panel */
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@
|
|||
"version_incompatibility" = "Не удалось отобразить это вложение. Возможно, база данных несовместима с текущей версией OpenVK.";
|
||||
"graffiti" = "Граффити";
|
||||
"reply" = "Ответить";
|
||||
"edited_short" = "ред.";
|
||||
|
||||
/* Friends */
|
||||
|
||||
|
@ -1049,6 +1050,7 @@
|
|||
"warn_user_action" = "Предупредить пользователя";
|
||||
"ban_in_support_user_action" = "Заблокировать в поддержке";
|
||||
"unban_in_support_user_action" = "Разблокировать в поддержке";
|
||||
"changes_history" = "История редактирования";
|
||||
|
||||
/* Admin panel */
|
||||
|
||||
|
|
Loading…
Reference in a new issue