Merge branch 'master' of github.com:openvk/openvk

This commit is contained in:
veselcraft 2021-12-27 18:40:44 +03:00
commit 1ba2786f8b
No known key found for this signature in database
GPG key ID: AED66BC1AC628A4E
20 changed files with 320 additions and 100 deletions

View file

@ -698,16 +698,18 @@ class User extends RowModel
]); ]);
} }
function ban(string $reason): void function ban(string $reason, bool $deleteSubscriptions = true): void
{ {
$subs = DatabaseConnection::i()->getContext()->table("subscriptions"); if($deleteSubscriptions) {
$subs = $subs->where( $subs = DatabaseConnection::i()->getContext()->table("subscriptions");
"follower = ? OR (target = ? AND model = ?)", $subs = $subs->where(
$this->getId(), "follower = ? OR (target = ? AND model = ?)",
$this->getId(), $this->getId(),
get_class($this), $this->getId(),
); get_class($this),
$subs->delete(); );
$subs->delete();
}
$this->setBlock_Reason($reason); $this->setBlock_Reason($reason);
$this->save(); $this->save();

View file

@ -219,10 +219,17 @@ final class GroupPresenter extends OpenVKPresenter
if($_FILES["ava"]["error"] === UPLOAD_ERR_OK) { if($_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
$photo = new Photo; $photo = new Photo;
try { try {
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($anon && $this->user->id === $club->getOwner()->getId())
$anon = $club->isOwnerHidden();
else if($anon)
$anon = $club->getManager($this->user->identity)->isHidden();
$photo->setOwner($this->user->id); $photo->setOwner($this->user->id);
$photo->setDescription("Profile image"); $photo->setDescription("Profile image");
$photo->setFile($_FILES["ava"]); $photo->setFile($_FILES["ava"]);
$photo->setCreated(time()); $photo->setCreated(time());
$photo->setAnonymous($anon);
$photo->save(); $photo->save();
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);

View file

@ -39,14 +39,26 @@ abstract class OpenVKPresenter extends SimplePresenter
Session::i()->set("_tempTheme", $theme); Session::i()->set("_tempTheme", $theme);
} }
protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL, bool $json = false): void
{ {
$this->flash($type, $title, $message, $code); if($json) {
$referer = $_SERVER["HTTP_REFERER"] ?? "/"; $this->returnJson([
"success" => $type !== "err",
"flash" => [
"type" => $type,
"title" => $title,
"message" => $message,
"code" => $code,
],
]);
} else {
$this->flash($type, $title, $message, $code);
$referer = $_SERVER["HTTP_REFERER"] ?? "/";
header("HTTP/1.1 302 Found"); header("HTTP/1.1 302 Found");
header("Location: $referer"); header("Location: $referer");
exit; exit;
}
} }
protected function logInUserWithToken(): void protected function logInUserWithToken(): void
@ -120,18 +132,18 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->flashFail("err", tr("captcha_error"), tr("captcha_error_comment")); $this->flashFail("err", tr("captcha_error"), tr("captcha_error_comment"));
} }
protected function willExecuteWriteAction(): void protected function willExecuteWriteAction(bool $json = false): void
{ {
$ip = (new IPs)->get(CONNECTING_IP); $ip = (new IPs)->get(CONNECTING_IP);
$res = $ip->rateLimit(); $res = $ip->rateLimit();
if(!($res === IP::RL_RESET || $res === IP::RL_CANEXEC)) { if(!($res === IP::RL_RESET || $res === IP::RL_CANEXEC)) {
if($res === IP::RL_BANNED && OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["autoban"]) { if($res === IP::RL_BANNED && OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["autoban"]) {
$this->user->identity->ban("Account has possibly been stolen"); $this->user->identity->ban("Account has possibly been stolen", false);
exit("Хакеры? Интересно..."); exit("Хакеры? Интересно...");
} }
$this->flashFail("err", tr("rate_limit_error"), tr("rate_limit_error_comment", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"], $res)); $this->flashFail("err", tr("rate_limit_error"), tr("rate_limit_error_comment", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"], $res), NULL, $json);
} }
} }
@ -241,4 +253,13 @@ abstract class OpenVKPresenter extends SimplePresenter
Session::i()->set("_error", NULL); Session::i()->set("_error", NULL);
} }
} }
protected function returnJson(array $json): void
{
$payload = json_encode($json);
$size = strlen($payload);
header("Content-Type: application/json");
header("Content-Length: $size");
exit($payload);
}
} }

View file

@ -164,9 +164,10 @@ final class SupportPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
$ticketComments = $this->comments->getCommentsById($id); $ticketComments = $this->comments->getCommentsById($id);
$this->template->ticket = $ticket; $this->template->ticket = $ticket;
$this->template->comments = $ticketComments; $this->template->comments = $ticketComments;
$this->template->id = $id; $this->template->id = $id;
$this->template->fastAnswers = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["fastAnswers"];
} }
function renderAnswerTicketReply(int $id): void function renderAnswerTicketReply(int $id): void

View file

@ -101,11 +101,11 @@ final class UserPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс."); $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.", NULL, true);
$isClubPinned = $this->user->identity->isClubPinned($club); $isClubPinned = $this->user->identity->isClubPinned($club);
if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10) if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10)
$this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп"); $this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп", NULL, true);
if($club->getOwner()->getId() === $this->user->identity->getId()) { if($club->getOwner()->getId() === $this->user->identity->getId()) {
$club->setOwner_Club_Pinned(!$isClubPinned); $club->setOwner_Club_Pinned(!$isClubPinned);
@ -118,10 +118,9 @@ final class UserPresenter extends OpenVKPresenter
} }
} }
if($isClubPinned) $this->returnJson([
$this->flashFail("succ", "Операция успешна", "Группа " . $club->getName() . " была успешно удалена из левого меню"); "success" => true
else ]);
$this->flashFail("succ", "Операция успешна", "Группа " . $club->getName() . " была успешно добавлена в левое меню");
} }
function renderEdit(): void function renderEdit(): void
@ -135,7 +134,7 @@ final class UserPresenter extends OpenVKPresenter
$user = $this->users->get($id); $user = $this->users->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction(); $this->willExecuteWriteAction($_GET['act'] === "status");
if($_GET['act'] === "main" || $_GET['act'] == NULL) { if($_GET['act'] === "main" || $_GET['act'] == NULL) {
$user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name")); $user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name"));
@ -197,15 +196,15 @@ final class UserPresenter extends OpenVKPresenter
} elseif($_GET['act'] === "status") { } elseif($_GET['act'] === "status") {
if(mb_strlen($this->postParam("status")) > 255) { if(mb_strlen($this->postParam("status")) > 255) {
$statusLength = (string) mb_strlen($this->postParam("status")); $statusLength = (string) mb_strlen($this->postParam("status"));
$this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)"); $this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)", NULL, true);
} }
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
$user->save(); $user->save();
header("HTTP/1.1 302 Found"); $this->returnJson([
header("Location: /id" . $user->getId()); "success" => true
exit; ]);
} }
try { try {

View file

@ -320,7 +320,7 @@ final class WallPresenter extends OpenVKPresenter
(new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit(); (new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit();
}; };
exit(json_encode(["wall_owner" => $this->user->identity->getId()])); $this->returnJson(["wall_owner" => $this->user->identity->getId()]);
} }
function renderDelete(int $wall, int $post_id): void function renderDelete(int $wall, int $post_id): void

View file

@ -39,7 +39,7 @@
<span>{_"surname"}: </span> <span>{_"surname"}: </span>
</td> </td>
<td> <td>
<input type="text" name="last_name" required /> <input type="text" name="last_name" />
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -91,13 +91,18 @@
{presenter "openvk!Wall->wallEmbedded", -$club->getId()} {presenter "openvk!Wall->wallEmbedded", -$club->getId()}
</div> </div>
<div class="right_small_block"> <div class="right_small_block">
<a href="{$club->getAvatarLink()|nocheck}"> {var avatarPhoto = $club->getAvatarPhoto()}
{var avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())}
<a href="{$avatarLink|nocheck}">
<img src="{$club->getAvatarUrl()}" style="width: 100%; image-rendering: -webkit-optimize-contrast;" /> <img src="{$club->getAvatarUrl()}" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
</a> </a>
<div n:ifset="$thisUser" id="profile_links"> <div n:ifset="$thisUser" id="profile_links">
{if $club->canBeModifiedBy($thisUser)} {if $club->canBeModifiedBy($thisUser)}
<a href="/club{$club->getId()}/edit" id="profile_link">{_"edit_group"}</a> <a href="/club{$club->getId()}/edit" id="profile_link">{_"edit_group"}</a>
{/if} {/if}
{if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
<a href="/admin/clubs/id{$club->getId()}" id="profile_link">{_"manage_group_action"}</a>
{/if}
{if $club->getSubscriptionStatus($thisUser) == false} {if $club->getSubscriptionStatus($thisUser) == false}
<form action="/setSub/club" method="post"> <form action="/setSub/club" method="post">
<input type="hidden" name="act" value="add" /> <input type="hidden" name="act" value="add" />

View file

@ -24,18 +24,24 @@
</div><br/> </div><br/>
<div> <div>
<form action="/al_comments.pl/create/support/reply/{$id}" method="post" style="margin:0;"> <form action="/al_comments.pl/create/support/reply/{$id}" method="post" style="margin:0;">
<textarea name="text" style="width: 100%;resize: vertical;"></textarea> <textarea name="text" id="answer_text" style="width: 100%;resize: vertical;"></textarea>
<div> <div>
<!-- padding to fix <br/> bug --> <!-- padding to fix <br/> bug -->
</div> </div>
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<br> <br />
<input type="submit" value="{_write}" class="button"> <div style="float: left;">
<select name="status" style="width: unset;"> <input type="submit" value="{_write}" class="button">
<option value="1">{_support_status_1}</option> <select name="status" style="width: unset;">
<option value="2">{_support_status_2}</option> <option value="1">{_support_status_1}</option>
<option value="0">{_support_status_0}</option> <option value="2">{_support_status_2}</option>
</select> <option value="0">{_support_status_0}</option>
</select>
</div>
<div n:if="!is_null($fastAnswers)" style="float: right;">
<a class="button" href="javascript:showSupportFastAnswerDialog(fastAnswers)">{_fast_answers}</a>
</div>
<br />
</form> </form>
</div> </div>
<br/> <br/>
@ -126,4 +132,12 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<script>
const fastAnswers = [
{foreach $fastAnswers as $answer}
{$answer},
{/foreach}
];
</script>
{/block} {/block}

View file

@ -348,16 +348,40 @@
{elseif $isFinanceTU} {elseif $isFinanceTU}
<p>{_voucher_explanation} {_voucher_explanation_ex}</p> <p>{_voucher_explanation} {_voucher_explanation_ex}</p>
<form action="/settings?act=finance.top-up" method="POST" enctype="multipart/form-data"> <form name="vouncher_form" action="/settings?act=finance.top-up" method="POST" enctype="multipart/form-data">
<input type="text" name="key0" size="6" placeholder="123456" required="required" style="display: inline-block; width: 50px; text-align: center;" /> - <input type="text" name="key0" class="vouncher_input" size="6" maxlength="6" placeholder="123456" required="required" oninput="autoTab(this, document.vouncher_form.key1, undefined)" style="display: inline-block; width: 50px; text-align: center;" /> -
<input type="text" name="key1" size="6" placeholder="789012" required="required" style="display: inline-block; width: 50px; text-align: center;" /> - <input type="text" name="key1" class="vouncher_input" size="6" maxlength="6" placeholder="789012" required="required" oninput="autoTab(this, document.vouncher_form.key2, document.vouncher_form.key0)" style="display: inline-block; width: 50px; text-align: center;" /> -
<input type="text" name="key2" size="6" placeholder="345678" required="required" style="display: inline-block; width: 50px; text-align: center;" /> - <input type="text" name="key2" class="vouncher_input" size="6" maxlength="6" placeholder="345678" required="required" oninput="autoTab(this, document.vouncher_form.key3, document.vouncher_form.key1)" style="display: inline-block; width: 50px; text-align: center;" /> -
<input type="text" name="key3" size="6" placeholder="90ABCD" required="required" style="display: inline-block; width: 50px; text-align: center;" /> <input type="text" name="key3" class="vouncher_input" size="6" maxlength="6" placeholder="90ABCD" required="required" oninput="autoTab(this, undefined, document.vouncher_form.key2)" style="display: inline-block; width: 50px; text-align: center;" />
<br/><br/> <br/><br/>
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_redeem}" class="button" /> <input type="submit" value="{_redeem}" class="button" />
</form> </form>
<script>
u(".vouncher_input").on("paste", function(event) {
const vouncher = event.clipboardData.getData("text");
let segments;
if(vouncher.length === 27) {
segments = vouncher.split("-");
if(segments.length !== 4)
segments = undefined;
} else if(vouncher.length === 24) {
segments = chunkSubstr(vouncher, 6);
}
if(segments !== undefined) {
document.vouncher_form.key0.value = segments[0];
document.vouncher_form.key1.value = segments[1];
document.vouncher_form.key2.value = segments[2];
document.vouncher_form.key3.value = segments[3];
document.vouncher_form.key3.focus();
}
event.preventDefault();
});
</script>
{elseif $isInterface} {elseif $isInterface}

View file

@ -316,12 +316,12 @@
<div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{$alert}</div> <div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{$alert}</div>
{var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()} {var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
<div n:if="$thatIsThisUser" class="page_status_popup" id="status_editor" style="display: none;"> <div n:if="$thatIsThisUser" class="page_status_popup" id="status_editor" style="display: none;">
<form method="post" action="/edit?act=status"> <form name="status_popup_form" onsubmit="changeStatus(); return false;">
<div style="margin-bottom: 10px;"> <div style="margin-bottom: 10px;">
<input type="text" name="status" size="50" value="{$user->getStatus()}" /> <input type="text" name="status" size="50" value="{$user->getStatus()}" />
</div> </div>
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="button" value="{_'save'}" /> <button type="submit" name="submit" class="button" style="height: 22px;">{_send}</button>
</form> </form>
</div> </div>
<div class="accountInfo clearFix"> <div class="accountInfo clearFix">
@ -543,9 +543,11 @@
} }
</script> </script>
<script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()"> <script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()" n:syntax="off">
function setStatusEditorShown(shown) { function setStatusEditorShown(shown) {
document.getElementById("status_editor").style.display = shown ? "block" : "none"; document.getElementById("status_editor").style.display = shown ? "block" : "none";
if(!document.status_popup_form.submit.style.width)
document.status_popup_form.submit.style.width = document.status_popup_form.submit.offsetWidth + 4 + "px"
} }
document.addEventListener("click", event => { document.addEventListener("click", event => {
@ -554,6 +556,36 @@
}); });
document.getElementById("page_status_text").onclick = setStatusEditorShown.bind(this, true); document.getElementById("page_status_text").onclick = setStatusEditorShown.bind(this, true);
async function changeStatus() {
const status = document.status_popup_form.status.value;
document.status_popup_form.submit.innerHTML = "<div class=\"button-loading\"></div>";
document.status_popup_form.submit.disabled = true;
const formData = new FormData();
formData.append("status", status);
formData.append("hash", document.status_popup_form.hash.value);
const response = await ky.post("/edit?act=status", {body: formData});
if(!parseAjaxResponse(await response.text())) {
document.status_popup_form.submit.innerHTML = tr("send");
document.status_popup_form.submit.disabled = false;
return;
}
if(document.status_popup_form.status.value === "") {
document.querySelector("#page_status_text").innerHTML = `[ ${tr("change_status")} ]`;
document.querySelector("#page_status_text").className = "edit_link page_status_edit_button";
} else {
document.querySelector("#page_status_text").innerHTML = status;
document.querySelector("#page_status_text").className = "page_status page_status_edit_button";
}
setStatusEditorShown(false);
document.status_popup_form.submit.innerHTML = tr("send");
document.status_popup_form.submit.disabled = false;
}
</script> </script>
</div> </div>

View file

@ -99,34 +99,40 @@
{/if} {/if}
<div class="like_wrap"> <div class="like_wrap">
<a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')"> {if !($forceNoShareLink ?? false)}
<div class="repost-icon" style="opacity: 0.4;"></div> <a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
<span class="likeCnt">{if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if}</span> <div class="repost-icon" style="opacity: 0.4;"></div>
</a> <span class="likeCnt">{if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if}</span>
</a>
{/if}
{var liked = $post->hasLikeFrom($thisUser)} {if !($forceNoLike ?? false)}
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" {var liked = $post->hasLikeFrom($thisUser)}
class="post-like-button" <a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}"
data-liked="{(int) $liked}" class="post-like-button"
data-likes="{$post->getLikesCount()}"> data-liked="{(int) $liked}"
<div class="heart" id="{if $liked}liked{/if}"></div> data-likes="{$post->getLikesCount()}">
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span> <div class="heart" id="{if $liked}liked{/if}"></div>
</a> <span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
</a>
{/if}
</div> </div>
{/if} {/if}
</div> </div>
<div n:if="$commentSection == true && $compact == false" class="post-menu-s"> {if !($forceNoCommentsLink ?? false)}
{if $commentsCount > 3} <div n:if="$commentSection == true && $compact == false" class="post-menu-s">
<a href="/wall{$post->getPrettyId()}" class="expand_button">{_view_other_comments}</a> {if $commentsCount > 3}
{/if} <a href="/wall{$post->getPrettyId()}" class="expand_button">{_view_other_comments}</a>
{foreach $comments as $comment} {/if}
{include "../comment.xml", comment => $comment, $compact => true} {foreach $comments as $comment}
{/foreach} {include "../comment.xml", comment => $comment, $compact => true}
<div n:ifset="$thisUser" id="commentTextArea{$commentTextAreaId}" n:attr="style => ($commentsCount == 0 ? 'display: none;')" class="commentsTextFieldWrap"> {/foreach}
{var commentsURL = "/al_comments.pl/create/posts/" . $post->getId()} <div n:ifset="$thisUser" id="commentTextArea{$commentTextAreaId}" n:attr="style => ($commentsCount == 0 ? 'display: none;')" class="commentsTextFieldWrap">
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post} {var commentsURL = "/al_comments.pl/create/posts/" . $post->getId()}
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post}
</div>
</div> </div>
</div> {/if}
</td> </td>
</tr> </tr>
</tbody> </tbody>

View file

@ -91,29 +91,35 @@
{_"comments"} {_"comments"}
{/if} {/if}
</a> </a>
{/if}
{if !($forceNoCommentsLink ?? false) && !($forceNoShareLink ?? false)}
&nbsp;|&nbsp; &nbsp;|&nbsp;
{/if} {/if}
<a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')"> {if !($forceNoShareLink ?? false)}
{if $post->getRepostCount() > 0} <a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
{_"share"} {if $post->getRepostCount() > 0}
(<b>{$post->getRepostCount()}</b>) {_"share"}
{else} (<b>{$post->getRepostCount()}</b>)
{_"share"} {else}
{/if} {_"share"}
</a> {/if}
<div class="like_wrap">
{var liked = $post->hasLikeFrom($thisUser)}
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}"
class="post-like-button"
data-liked="{(int) $liked}"
data-likes="{$post->getLikesCount()}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
</a> </a>
</div> {/if}
{if !($forceNoLike ?? false)}
<div class="like_wrap">
{var liked = $post->hasLikeFrom($thisUser)}
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}"
class="post-like-button"
data-liked="{(int) $liked}"
data-likes="{$post->getLikesCount()}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
</a>
</div>
{/if}
</div> </div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu-s"> <div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu-s">
<!-- kosfurler --> <!-- kosfurler -->

View file

@ -440,7 +440,15 @@ table {
} }
.button:hover { .button:hover {
color: #e8e8e8; color: #e8e8e8;
}
.button-loading {
display: inline-block;
background-image: url('/assets/packages/static/openvk/img/loading_mini.gif');
width: 30px;
height: 7px;
margin-left: -3px;
} }
input[class=button] { input[class=button] {
@ -1768,3 +1776,16 @@ body.scrolled .toTop:hover {
margin: -20px; margin: -20px;
padding: 10px; padding: 10px;
} }
.hover-box {
background-color: white;
padding: 10px;
margin: 5px;
border: 1px solid #C0CAD5;
cursor: pointer;
user-select: none;
}
.hover-box:hover {
background-color: #C0CAD5;
}

View file

@ -38,6 +38,22 @@ function hidePanel(panel, count = 0)
} }
function parseAjaxResponse(responseString) {
try {
const response = JSON.parse(responseString);
if(response.flash)
NewNotification(response.flash.title, response.flash.message || "", null);
return response.success || false;
} catch(error) {
if(responseString === "Хакеры? Интересно...") {
location.reload();
return false;
} else {
throw error;
}
}
}
document.addEventListener("DOMContentLoaded", function() { //BEGIN document.addEventListener("DOMContentLoaded", function() { //BEGIN
@ -100,6 +116,15 @@ document.addEventListener("DOMContentLoaded", function() { //BEGIN
let req = await ky(link); let req = await ky(link);
if(req.ok == false) { if(req.ok == false) {
NewNotification(tr('error'), tr('error_1'), null); NewNotification(tr('error'), tr('error_1'), null);
thisButton.nodes[0].classList.remove('loading');
thisButton.nodes[0].classList.remove('disable');
return;
}
if(!parseAjaxResponse(await req.text())) {
thisButton.nodes[0].classList.remove('loading');
thisButton.nodes[0].classList.remove('disable');
return;
} }
// Adding a divider if not already there // Adding a divider if not already there
@ -145,7 +170,7 @@ function repostPost(id, hash) {
xhr.open("POST", "/wall"+id+"/repost?hash="+hash, true); xhr.open("POST", "/wall"+id+"/repost?hash="+hash, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = (function() { xhr.onload = (function() {
if(xhr.responseText.indexOf("wall_owner") === -1) if(xhr.responseText.indexOf("wall_owner") === -1)
MessageBox(tr('error'), tr('error_repost_fail'), tr('ok'), [Function.noop]); MessageBox(tr('error'), tr('error_repost_fail'), tr('ok'), [Function.noop]);
else { else {
let jsonR = JSON.parse(xhr.responseText); let jsonR = JSON.parse(xhr.responseText);
@ -223,3 +248,44 @@ function showCoinsTransferDialog(coinsCount, hash) {
]); ]);
} }
function chunkSubstr(string, size) {
const numChunks = Math.ceil(string.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = string.substr(o, size);
}
return chunks;
}
function autoTab(original, next, previous) {
if(original.getAttribute && original.value.length == original.getAttribute("maxlength") && next !== undefined)
next.focus();
else if(original.value.length == 0 && previous !== undefined)
previous.focus();
}
function showSupportFastAnswerDialog(answers) {
let html = "";
for(const [index, answer] of Object.entries(answers)) {
html += `
<div class="hover-box" onclick="supportFastAnswerDialogOnClick(fastAnswers[${index}])">
${answer.replace(/\n/g, "<br />")}
</div>
`;
}
MessageBox(tr("fast_answers"), html, [tr("close")], [
Function.noop
]);
}
function supportFastAnswerDialogOnClick(answer) {
u("body").removeClass("dimmed");
u(".ovk-diag-cont").remove();
const answerInput = document.querySelector("#answer_text");
answerInput.value = answer;
answerInput.focus();
}

View file

@ -2,6 +2,10 @@
Based on [@rem-pai](https://github.com/rem-pai)'s way to install OpenVK modified using my experience. Based on [@rem-pai](https://github.com/rem-pai)'s way to install OpenVK modified using my experience.
!!WARNING!!
CentOS 8 is reaching it's end-of-life soon. There are other supported similar distributions like Rocky Linux or AlmaLinux.
## SELinux ## SELinux
🖥Run the command: 🖥Run the command:

View file

@ -1 +1 @@
ALTER TABLE `profiles` CHANGE `coins` `coins` REAL(20) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `profiles` CHANGE `coins` `coins` REAL UNSIGNED NOT NULL DEFAULT '0';

View file

@ -610,6 +610,8 @@
"support_rated_bad" = "You left a negative feedback about the answer."; "support_rated_bad" = "You left a negative feedback about the answer.";
"wrong_parameters" = "Invalid request parameters."; "wrong_parameters" = "Invalid request parameters.";
"fast_answers" = "Fast answers";
"comment" = "Comment"; "comment" = "Comment";
"sender" = "Sender"; "sender" = "Sender";
@ -743,6 +745,7 @@
"login_as" = "Login as $1"; "login_as" = "Login as $1";
"manage_user_action" = "Manage user"; "manage_user_action" = "Manage user";
"manage_group_action" = "Manage group";
"ban_user_action" = "Ban user"; "ban_user_action" = "Ban user";
"warn_user_action" = "Warn user"; "warn_user_action" = "Warn user";
@ -764,6 +767,7 @@
"cancel" = "Cancel"; "cancel" = "Cancel";
"edit_action" = "Change"; "edit_action" = "Change";
"transfer" = "Transfer"; "transfer" = "Transfer";
"close" = "Close";
"warning" = "Warning"; "warning" = "Warning";
"question_confirm" = "This action can't be undone. Do you really wanna do it?"; "question_confirm" = "This action can't be undone. Do you really wanna do it?";

View file

@ -639,6 +639,8 @@
"support_rated_bad" = "Вы оставили негативный отзыв об ответе."; "support_rated_bad" = "Вы оставили негативный отзыв об ответе.";
"wrong_parameters" = "Неверные параметры запроса."; "wrong_parameters" = "Неверные параметры запроса.";
"fast_answers" = "Быстрые ответы";
"comment" = "Комментарий"; "comment" = "Комментарий";
"sender" = "Отправитель"; "sender" = "Отправитель";
@ -778,6 +780,7 @@
"login_as" = "Войти как $1"; "login_as" = "Войти как $1";
"manage_user_action" = "Управление пользователем"; "manage_user_action" = "Управление пользователем";
"manage_group_action" = "Управление группой";
"ban_user_action" = "Заблокировать пользователя"; "ban_user_action" = "Заблокировать пользователя";
"warn_user_action" = "Предупредить пользователя"; "warn_user_action" = "Предупредить пользователя";
@ -799,6 +802,7 @@
"cancel" = "Отмена"; "cancel" = "Отмена";
"edit_action" = "Изменить"; "edit_action" = "Изменить";
"transfer" = "Передать"; "transfer" = "Передать";
"close" = "Закрыть";
"warning" = "Внимание"; "warning" = "Внимание";
"question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?"; "question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?";

View file

@ -31,6 +31,10 @@ openvk:
support: support:
supportName: "Moderator" supportName: "Moderator"
adminAccount: 1 # Change this ok adminAccount: 1 # Change this ok
fastAnswers:
- "This is a list of quick answers to common questions for support. Post your responses here and agents can send it quickly with just 3 clicks"
- "There can be as many answers as you want, but it is best to have a maximum of 10.\n\nYou can also remove all answers from the list to disable this feature"
- "Good luck filling! If you are a regular support agent, inform the administrator that he forgot to fill the config"
messages: messages:
strict: false strict: false
wall: wall: