Merge branch 'master' into infinityscroll2

This commit is contained in:
mrilyew 2024-11-02 14:16:42 +03:00
commit a4d5bb8088
27 changed files with 474 additions and 149 deletions

View file

@ -2,7 +2,8 @@ name: Build base images
on:
schedule:
- cron: '0 0 * * *'
- cron: "0 0 * * *"
workflow_dispatch:
env:
BASE_IMAGE_NAME: php
@ -16,10 +17,10 @@ jobs:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
@ -38,7 +39,7 @@ jobs:
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME:$BASE_IMAGE_VERSION-cli
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/base-php-cli.Dockerfile --build-arg VERSION=$BASE_IMAGE_VERSION
build-apache:
runs-on: ubuntu-latest
@ -46,14 +47,14 @@ jobs:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- name: Change repository string to lowercase
id: repositorystring
uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0

View file

@ -74,7 +74,7 @@ final class Video extends VKAPIRequestHandler
$return_items = [];
$profiles = [];
$groups = [];
foreach($items as $item)
foreach($items as $item) {
$return_item = $item->getApiStructure($this->getUser());
$return_item = $return_item->video;
$return_items[] = $return_item;
@ -85,6 +85,7 @@ final class Video extends VKAPIRequestHandler
else
$groups[] = abs($return_item['owner_id']);
}
}
if($extended) {
$profiles = array_unique($profiles);

View file

@ -102,7 +102,14 @@ final class Wall extends VKAPIRequestHandler
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure($this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
if(VKAPI_DECL_VER === '4.100') {
$attachments[] = $attachment->toVkApiStruct();
} else {
$attachments[] = [
'type' => 'note',
'note' => $attachment->toVkApiStruct()
];
}
} else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) {
$attachments[] = [
"type" => "audio",
@ -188,6 +195,9 @@ final class Wall extends VKAPIRequestHandler
]
];
if($post->hasSource())
$post_temp_obj->copyright = $post->getVkApiCopyright();
if($signerId)
$post_temp_obj->signer_id = $signerId;
@ -291,7 +301,14 @@ final class Wall extends VKAPIRequestHandler
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure($this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
if(VKAPI_DECL_VER === '4.100') {
$attachments[] = $attachment->toVkApiStruct();
} else {
$attachments[] = [
'type' => 'note',
'note' => $attachment->toVkApiStruct()
];
}
} else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) {
$attachments[] = [
"type" => "audio",
@ -373,6 +390,9 @@ final class Wall extends VKAPIRequestHandler
]
];
if($post->hasSource())
$post_temp_obj->copyright = $post->getVkApiCopyright();
if($signerId)
$post_temp_obj->signer_id = $signerId;
@ -543,6 +563,12 @@ final class Wall extends VKAPIRequestHandler
$post->setFlags($flags);
$post->setApi_Source_Name($this->getPlatform());
if(!is_null($copyright) && !empty($copyright)) {
try {
$post->setSource($copyright);
} catch(\Throwable) {}
}
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2)
$post->setSuggested(1);
@ -558,11 +584,11 @@ final class Wall extends VKAPIRequestHandler
# Пример: photo1_1
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
$this->fail(50, "Too many attachments");
preg_match_all("/poll/m", $attachments, $matches, PREG_SET_ORDER, 0);
if(sizeof($matches) > 1)
$this->fail(85, "Error: too many polls");
$this->fail(85, "Too many polls");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
@ -993,7 +1019,7 @@ final class Wall extends VKAPIRequestHandler
}
}
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "") {
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "", string $copyright = NULL) {
$this->requireUser();
$this->willExecuteWriteAction();
@ -1002,9 +1028,6 @@ final class Wall extends VKAPIRequestHandler
if(!$post || $post->isDeleted())
$this->fail(102, "Invalid post");
if(empty($message) && empty($attachments))
$this->fail(100, "Required parameter 'message' missing.");
if(!$post->canBeEditedBy($this->getUser()))
$this->fail(7, "Access to editing denied");
@ -1012,6 +1035,12 @@ final class Wall extends VKAPIRequestHandler
$post->setContent($message);
$post->setEdited(time());
if(!is_null($copyright) && !empty($copyright)) {
try {
$post->setSource($copyright);
} catch(\Throwable) {}
}
$post->save(true);
# todo добавить такое в веб версию
@ -1082,6 +1111,63 @@ final class Wall extends VKAPIRequestHandler
return 1;
}
function checkCopyrightLink(string $link): int
{
$this->requireUser();
try {
$result = check_copyright_link($link);
} catch(\InvalidArgumentException $e) {
$this->fail(3102, "Specified link is incorrect (can't find source)");
} catch(\LengthException $e) {
$this->fail(3103, "Specified link is incorrect (too long)");
} catch(\LogicException $e) {
$this->fail(3104, "Link is suspicious");
} catch(\Throwable $e) {
$this->fail(3102, "Specified link is incorrect");
}
return 1;
}
function pin(int $owner_id, int $post_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
if(!$post || $post->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: post_id is undefined");
if(!$post->canBePinnedBy($this->getUser()))
return 0;
if($post->isPinned())
return 1;
$post->pin();
return 1;
}
function unpin(int $owner_id, int $post_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
if(!$post || $post->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: post_id is undefined");
if(!$post->canBePinnedBy($this->getUser()))
return 0;
if(!$post->isPinned())
return 1;
$post->unpin();
return 1;
}
private function getApiPhoto($attachment) {
return [
"type" => "photo",

View file

@ -78,6 +78,40 @@ class Post extends Postable
{
return (bool) $this->getRecord()->pinned;
}
function hasSource(): bool
{
return $this->getRecord()->source != NULL;
}
function getSource(bool $format = false)
{
$orig_source = $this->getRecord()->source;
if(!str_contains($orig_source, "https://") && !str_contains($orig_source, "http://"))
$orig_source = "https://" . $orig_source;
if(!$format)
return $orig_source;
return $this->formatLinks($orig_source);
}
function setSource(string $source)
{
$result = check_copyright_link($source);
$this->stateChanges("source", $source);
}
function getVkApiCopyright(): object
{
return (object)[
'id' => 0,
'link' => $this->getSource(false),
'name' => $this->getSource(false),
'type' => 'link',
];
}
function isAd(): bool
{

View file

@ -38,8 +38,19 @@ trait TRichText
$href = str_replace("#", "&num;", $matches[1]);
$href = rawurlencode(str_replace(";", "&#59;", $href));
$link = str_replace("#", "&num;", $matches[3]);
# this string breaks ampersands
$link = str_replace(";", "&#59;", $link);
$rel = $this->isAd() ? "sponsored" : "ugc";
$server_domain = str_replace(':' . $_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']);
if(str_contains($link, $server_domain)) {
$replaced_link = str_replace(':' . $_SERVER['SERVER_PORT'], '', $link);
$replaced_link = str_replace($server_domain, '', $replaced_link);
return "<a href='$replaced_link' rel='$rel'>$link</a>" . htmlentities($matches[4]);
}
$link = htmlentities(urldecode($link));
return "<a href='/away.php?to=$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);
}),

View file

@ -367,6 +367,12 @@ final class WallPresenter extends OpenVKPresenter
$post->setFlags($flags);
$post->setNsfw($this->postParam("nsfw") === "on");
if(!empty($this->postParam("source")) && $this->postParam("source") != 'none') {
try {
$post->setSource($this->postParam("source"));
} catch(\Throwable) {}
}
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2)
$post->setSuggested(1);

View file

@ -18,7 +18,7 @@
{else}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
<a href="/playlists{$ownerId}">{_playlists}</a>
<a href="/playlists{$owner->getRealId()}">{_playlists}</a>
»
<a href="/playlist{$playlist->getPrettyId()}">{_playlist}</a>
{/if}

View file

@ -17,7 +17,7 @@
<a n:if="!$isMy" n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a>
<a href="/player/upload?gid={abs($ownerId)}" n:if="isset($thisUser) && isset($club) && $club->canUploadAudio($thisUser)">{_upload_audio}</a>
<a n:attr="id => $mode === 'playlists' && $ownerId != $thisUser->getId() ? 'used' : 'ki'" href="/playlists{$ownerId}" n:if="isset($thisUser) && isset($ownerId) && !$isMy">{if $ownerId > 0}{_playlists_user}{else}{_playlists_club}{/if}</a>
<a n:attr="id => $mode === 'playlists' && $ownerId != $thisUser->getId() ? 'used' : 'ki'" class='noOverflow' href="/playlists{$ownerId}" n:if="isset($thisUser) && isset($ownerId) && !$isMy">{if $ownerId > 0}{_playlists_user}{else}{_playlists_club}{/if}</a>
<a href="/audios/newPlaylist{if $isMyClub}?gid={abs($ownerId)}{/if}" n:if="isset($thisUser) && $isMyClub">{_new_playlist}</a>
{/if}
</div>

View file

@ -202,7 +202,7 @@
{if isset($thisUser) && $user->getId() === $thisUser->getId() && sizeof($completeness->unfilled) > 0}
<br/>
<a n:if="in_array('interests', $completeness->unfilled)" href="/edit">
<a n:if="in_array('interests', $completeness->unfilled)" href="/edit?act=interests">
<img src="/assets/packages/static/openvk/img/icon1.gif" />
{_interests} (+20%)
</a>

View file

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

View file

@ -28,7 +28,7 @@
</div>
<div n:if="$canPost && $type == 'all'" class="content_subtitle">
{include "../components/textArea.xml", route => "/wall$owner/makePost"}
{include "../components/textArea.xml", route => "/wall$owner/makePost", hasSource => true}
</div>
<div class="content">

View file

@ -1,9 +1,11 @@
{var $author = $comment->getOwner()}
{var $Club = openvk\Web\Models\Entities\Club::class}
{var $postId = $comment->getTarget() instanceof \openvk\Web\Models\Entities\Post ? $comment->getTarget()->getId() : NULL}
{var $likesCount = $comment->getLikesCount()}
{var $target = $comment->getTarget()}
{var $postId = $target instanceof \openvk\Web\Models\Entities\Post ? $target->getId() : NULL}
<a name="cid={$comment->getId()}"></a>
<table border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$comment->getOwner() instanceof $Club}" n:attr="data-post-id => $postId">
<table border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$author instanceof $Club}" n:attr="data-post-id => $postId">
<tbody>
<tr>
<td width="30" valign="top">
@ -40,8 +42,8 @@
<span n:if="$comment->getEditTime()" class="edited editedMark">({_edited_short})</span>
</a>
{if !$timeOnly}
&nbsp;|
{if $comment->canBeDeletedBy($thisUser)}
|
<a href="/comment{$comment->getId()}/delete">{_delete}</a>
{/if}
{if $comment->canBeEditedBy($thisUser)}
@ -60,7 +62,7 @@
<div style="float: right; font-size: .7rem;">
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div>
<span class="likeCnt">{if $comment->getLikesCount() > 0}{$comment->getLikesCount()}{/if}</span>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
</div>
{/if}
@ -101,4 +103,4 @@
Function.noop
]);
}
</script>
</script>

View file

@ -3,6 +3,11 @@
{var $commentsCount = $post->getCommentsCount()}
{var $platform = $post->getPlatform()}
{var $platformDetails = $post->getPlatformDetails()}
{var $likesCount = $post->getLikesCount()}
{var $repostsCount = $post->getRepostCount()}
{var $canBePinned = $post->canBePinnedBy($thisUser ?? NULL)}
{var $canBeDeleted = $post->canBeDeletedBy($thisUser)}
{var $wallOwner = $post->getWallOwner()}
{if $post->isDeactivationMessage() && $post->getText()}
{var $deac = "post_deact"}
{else}
@ -30,7 +35,6 @@
{$post->isUpdateAvatarMessage() && !$post->isPostedOnBehalfOfGroup() ? ($author->isFemale() ? tr("upd_f") : ($author->isNeutral() ? tr("upd_n") : tr("upd_m")))}
{$post->isUpdateAvatarMessage() && $post->isPostedOnBehalfOfGroup() ? tr("upd_g") : ""}
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
{var $wallOwner = $post->getWallOwner()}
<a href="{$wallOwner->getURL()}" class="mention" data-mention-ref="{$post->getTargetWall()}">
<b>
{if isset($thisUser) && $thisUser->getId() === $post->getTargetWall()}
@ -53,9 +57,9 @@
<span n:if="$post->isPinned()" class="nobold">{_pinned}</span>
<a n:if="$post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false) && $compact == false" class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
<a n:if="$canBeDeleted && !($forceNoDeleteLink ?? false) && $compact == false" class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
{if $post->canBePinnedBy($thisUser) && !($forceNoPinLink ?? false) && $compact == false}
{if $canBePinned && !($forceNoPinLink ?? false) && $compact == false}
{if $post->isPinned()}
<a class="pin" href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}"></a>
{else}
@ -67,7 +71,7 @@
<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 $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}></a>
{/if}
</div>
<div class="post-content" id="{$post->getPrettyId()}">
@ -95,6 +99,9 @@
<br/>
&nbsp;! {_post_is_ad}
</div>
<div n:if="$post->hasSource()" class="sourceDiv">
<span>{_source}: {$post->getSource(true)|noescape}</span>
</div>
<div n:if="$post->isSigned()" class="post-signature">
{var $actualAuthor = $post->getOwner(false)}
<span>
@ -121,14 +128,14 @@
<div class="like_wrap">
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
<div class="repost-icon" style="opacity: 0.4;"></div>
<span class="likeCnt" id="repostsCount{$post->getPrettyId()}">{if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if}</span>
<span class="likeCnt" id="repostsCount{$post->getPrettyId()}">{if $repostsCount > 0}{$repostsCount}{/if}</span>
</a>
{if !($forceNoLike ?? false)}
{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()}">
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$likesCount}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
{/if}
</div>
@ -145,7 +152,7 @@
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club}
</div>
</div>
<div n:if="$suggestion && $post->canBePinnedBy($thisUser ?? NULL)" class="suggestionControls">
<div n:if="$suggestion && $canBePinned" class="suggestionControls">
<input type="button" class="button" id="publish_post" data-id="{$post->getId()}" value="{_publish_suggested}">
<input type="button" class="button" id="decline_post" data-id="{$post->getId()}" value="{_decline_suggested}">
</div>

View file

@ -1,6 +1,12 @@
{var $author = $post->getOwner()}
{var $platform = $post->getPlatform()}
{var $platformDetails = $post->getPlatformDetails()}
{var $wallOwner = $post->getWallOwner()}
{var $likesCount = $post->getLikesCount()}
{var $repostsCount = $post->getRepostCount()}
{var $commentsCount = $post->getCommentsCount()}
{var $canBePinned = $post->canBePinnedBy($thisUser ?? NULL)}
{var $canBeDeleted = $post->canBeDeletedBy($thisUser)}
{if $post->isDeactivationMessage() && $post->getText()}
{var $deac = "post_deact"}
{else}
@ -40,7 +46,6 @@
{/if}
{/if}
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
{var $wallOwner = $post->getWallOwner()}
<a href="{$wallOwner->getURL()}" class="mention" data-mention-ref="{$post->getTargetWall()}">
<b>
{if isset($thisUser) && $thisUser->getId() === $post->getTargetWall()}
@ -85,7 +90,7 @@
</div>
</div>
</div>
<div n:if="$suggestion && $post->canBePinnedBy($thisUser ?? NULL)" class="suggestionControls" style="margin-bottom: 7px;">
<div n:if="$suggestion && $canBePinned" class="suggestionControls" style="margin-bottom: 7px;">
<input type="button" class="button" id="publish_post" data-id="{$post->getId()}" value="{_publish_suggested}">
<input type="button" class="button" id="decline_post" data-id="{$post->getId()}" value="{_decline_suggested}">
</div>
@ -93,6 +98,9 @@
<br/>
&nbsp;! {_post_is_ad}
</div>
<div n:if="$post->hasSource()" class="sourceDiv">
<span>{_source}: {$post->getSource(true)|noescape}</span>
</div>
<div n:if="$post->isSigned()" class="post-signature">
{var $actualAuthor = $post->getOwner(false)}
<span>
@ -113,14 +121,14 @@
<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> &nbsp;|&nbsp;
{if $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}>{_edit}</a> &nbsp;|&nbsp;
{/if}
{if !($forceNoDeleteLink ?? false) && $post->canBeDeletedBy($thisUser)}
{if !($forceNoDeleteLink ?? false) && $canBeDeleted}
<a href="/wall{$post->getPrettyId()}/delete">{_delete}</a> &nbsp;|&nbsp;
{/if}
{if !($forceNoPinLink ?? false) && $post->canBePinnedBy($thisUser)}
{if !($forceNoPinLink ?? false) && $canBePinned}
{if $post->isPinned()}
<a href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}">{_unpin}</a>
{else}
@ -131,8 +139,8 @@
<a n:if="!($forceNoCommentsLink ?? false)" href="/wall{$post->getPrettyId()}#comments">
{_comments}
{if $post->getCommentsCount() > 0}
(<b>{$post->getCommentsCount()}</b>)
{if $commentsCount > 0}
(<b>{$commentsCount}</b>)
{/if}
</a>
@ -142,22 +150,22 @@
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" {ifset $thisUser} href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')"{/ifset}>
{_share}
{if $post->getRepostCount() > 0}
(<b id="repostsCount{$post->getPrettyId()}">{$post->getRepostCount()}</b>)
{if $repostsCount > 0}
(<b id="repostsCount{$post->getPrettyId()}">{$repostsCount}</b>)
{/if}
</a>
<div n:if="!($forceNoLike ?? false)" class="like_wrap">
{ifset $thisUser}
{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()}">
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$likesCount}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
{else}
<a n:if="$post->getLikesCount() > 0" class="post-like-button">
<a n:if="$likesCount > 0" class="post-like-button">
<div class="heart"></div>
<span class="likeCnt">{$post->getLikesCount()}</span>
<span class="likeCnt">{$likesCount}</span>
</a>
{/ifset}
</div>

View file

@ -18,14 +18,15 @@
<div class="post-has-poll">
{_poll}
</div>
<div class="post-has-note">
</div>
<div class="post-has-note"></div>
<div class="post-has-videos"></div>
<div class="post-has-audios"></div>
<div class="post-source"></div>
<div n:if="$postOpts ?? true" class="post-opts">
{var $anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']}
{if !is_null($thisUser) && !is_null($club ?? NULL) && $owner < 0}
{if $club->canBeModifiedBy($thisUser)}
<script>
@ -66,6 +67,7 @@
<input type="hidden" name="audios" value="" />
<input type="hidden" name="poll" value="none" />
<input type="hidden" id="note" name="note" value="none" />
<input type="hidden" id="source" name="source" value="none" />
<input type="hidden" name="type" value="1" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<br/>
@ -91,10 +93,6 @@
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/audio-ac3.png" />
{_audio}
</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});">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
{_graffiti}
@ -103,6 +101,10 @@
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/office-chart-bar-stacked.png" />
{_poll}
</a>
<a n:if="$hasSource ?? false" id='__sourceAttacher'>
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/insert-link.png" />
{_source}
</a>
</div>
</div>
</div>

View file

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

View file

@ -70,7 +70,7 @@ class Makima
$result->colSizes = [1];
$result->rowSizes = [1, 1];
$result->width = ceil($maxWidth);
$result->height = $computedHeight;
$result->height = $computedHeight * 2;
$result->tiles = [new ThumbTile(1, 1, $maxWidth, $computedHeight), new ThumbTile(1, 1, $maxWidth, $computedHeight)];
} else if(
$orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]

View file

@ -9,7 +9,7 @@
}
.musicIcon {
background-image: url('/assets/packages/static/openvk/img/audios_controls.png');
background-image: url('/assets/packages/static/openvk/img/audios_controls.png?v=2');
background-repeat: no-repeat;
cursor: pointer;
}
@ -185,6 +185,7 @@
width: 81%;
height: 13px;
display: inline-block;
line-height: 14px;
}
.bigPlayer .paddingLayer .trackInfo .timer span {
@ -345,6 +346,7 @@
.audioEntry .status .mediaInfo {
cursor: pointer;
display: flex;
line-height: 14px;
width: 100%;
}

View file

@ -44,7 +44,7 @@
._add_image::before {
margin-top: 2px;
content: ' ';
background: url('/assets/packages/static/openvk/img/upload.png');
background: url('/assets/packages/static/openvk/img/upload.png?v=2');
width: 10px;
height: 10px;
display: inline-block;
@ -65,7 +65,7 @@
.avatarDelete::before {
content: ' ';
background: url('/assets/packages/static/openvk/img/upload.png');
background: url('/assets/packages/static/openvk/img/upload.png?v=2');
background-position: -10px 2px;
background-repeat: no-repeat;
width: 12px;

View file

@ -1479,6 +1479,24 @@ body.scrolled .toTop:hover, .toTop.has_down:hover {
color: #3c3c3c;
}
.post-source #remove_source_button {
display: inline-block;
background-repeat: no-repeat;
background: url('/assets/packages/static/openvk/img/arrows.png?v=2');
margin-bottom: -2px;
background-position: -18px 0px;
height: 11px;
width: 11px;
opacity: 0.6;
transition-duration: 0.3s;
cursor: pointer;
}
.post-source #remove_source_button:hover {
opacity: 0.8;
}
.post-upload::before, .post-has-poll::before, .post-has-note::before {
content: " ";
width: 8px;
@ -3044,6 +3062,26 @@ body.article .floating_sidebar, body.article .page_content {
font-size: 12px;
}
.post .sourceDiv {
margin-top: 3px;
margin-left: 4px;
}
.post .sourceDiv span {
color: grey;
font-size: 10px;
}
.post .sourceDiv a:hover {
text-decoration: underline;
}
#source_flex_kunteynir {
display: flex;
flex-direction: column;
gap: 22px;
}
.sugglist {
padding-bottom: 5px;
padding-top: 5px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 316 B

View file

@ -66,16 +66,16 @@ u(`#search_box input[type='search']`).on('input', async (e) => {
switch(section) {
case 'users':
results = await fetch(`/method/users.search?auth_mechanism=roaming&q=${query}&count=10&sort=4&fields=photo_50,status,nickname`)
results = await fetch(`/method/users.search?auth_mechanism=roaming&q=${encodeURIComponent(query)}&count=10&sort=4&fields=photo_50,status,nickname`)
break
case 'groups':
results = await fetch(`/method/groups.search?auth_mechanism=roaming&q=${query}&count=10&sort=4&fields=photo_50,description`)
results = await fetch(`/method/groups.search?auth_mechanism=roaming&q=${encodeURIComponent(query)}&count=10&sort=4&fields=photo_50,description`)
break
case 'videos':
results = await fetch(`/method/video.search?auth_mechanism=roaming&q=${query}&count=10&sort=4&extended=1`)
results = await fetch(`/method/video.search?auth_mechanism=roaming&q=${encodeURIComponent(query)}&count=10&sort=4&extended=1`)
break
case 'audios_playlists':
results = await fetch(`/method/audio.searchAlbums?auth_mechanism=roaming&query=${query}&limit=10`)
results = await fetch(`/method/audio.searchAlbums?auth_mechanism=roaming&query=${encodeURIComponent(query)}&limit=10`)
break
}

View file

@ -1145,7 +1145,7 @@ $(document).on("click", "#editPost", (e) => {
}
post.querySelector(".post-avatar").setAttribute("src", result.author.avatar)
post.querySelector(".post-author-name").innerHTML = result.author.name
post.querySelector(".post-author-name").innerHTML = result.author.name.escapeHtml()
post.querySelector(".really_text").setAttribute("data-text", result.new_text)
} else {
MessageBox(tr("error"), result.error, [tr("ok")], [Function.noop])
@ -1626,7 +1626,7 @@ $(document).on("click", ".avatarDelete", (e) => {
u("body").removeClass("dimmed");
document.querySelector("html").style.overflowY = "scroll"
u(".ovk-diag-cont").remove();
u(".ovk-diag-cont").remove()
document.querySelector("#bigAvatar").src = response.url
document.querySelector("#bigAvatar").parentNode.href = response.new_photo ? ("/photo" + response.new_photo) : "javascript:void(0)"
@ -1716,3 +1716,77 @@ const showMoreObserver = new IntersectionObserver(entries => {
if(u('.paginator:not(.paginator-at-top)').length > 0) {
showMoreObserver.observe(u('.paginator:not(.paginator-at-top)').nodes[0])
}
u(document).on('click', '#__sourceAttacher', (e) => {
MessageBox(tr('add_source'), `
<div id='source_flex_kunteynir'>
<span>${tr('set_source_tip')}</span>
<!-- давай, копируй ссылку и переходи по ней -->
<input type='text' maxlength='400' placeholder='https://www.youtube.com/watch?v=lkWuk_nzzVA'>
</div>
`, [tr('cancel')], [
() => {Function.noop}
])
__removeDialog = () => {
u("body").removeClass("dimmed");
document.querySelector("html").style.overflowY = "scroll"
u(".ovk-diag-cont").remove()
}
u('.ovk-diag-action').append(`
<button class='button' id='__setsrcbutton'>${tr('set_source')}</button>
`)
u('.ovk-diag-action #__setsrcbutton').on('click', async (ev) => {
// Consts
const _u_target = u(e.target)
const nearest_textarea = _u_target.closest('#write')
const source_output = nearest_textarea.find(`input[name='source']`)
const source_input = u(`#source_flex_kunteynir input[type='text']`)
const source_value = source_input.nodes[0].value ?? ''
if(source_value.length < 1) {
return
}
ev.target.classList.add('lagged')
// Checking link
const __checkCopyrightLinkRes = await fetch(`/method/wall.checkCopyrightLink?auth_mechanism=roaming&link=${encodeURIComponent(source_value)}`)
const checkCopyrightLink = await __checkCopyrightLinkRes.json()
// todo переписать блять мессенджбоксы чтоб они классами были
if(checkCopyrightLink.error_code) {
__removeDialog()
switch(checkCopyrightLink.error_code) {
default:
case 3102:
fastError(tr('error_adding_source_regex'))
return
case 3103:
fastError(tr('error_adding_source_long'))
return
case 3104:
fastError(tr('error_adding_source_sus'))
return
}
}
// Making indicator
__removeDialog()
source_output.attr('value', source_value)
nearest_textarea.find('.post-source').html(`
<span>${tr('source')}: <a target='_blank' href='${source_value.escapeHtml()}'>${ovk_proc_strtr(source_value.escapeHtml(), 50)}</a></span>
<div id='remove_source_button'></div>
`)
nearest_textarea.find('.post-source #remove_source_button').on('click', () => {
nearest_textarea.find('.post-source').html('')
source_output.attr('value', 'none')
})
})
u('.ovk-diag-body').attr('style', `padding:8px;`)
u('.ovk-diag-cont').attr('style', 'width: 325px;')
u('#source_flex_kunteynir input').nodes[0].focus()
})

View file

@ -286,6 +286,31 @@ function ovk_scheme(bool $with_slashes = false): string
return $scheme;
}
function check_copyright_link(string $link = ''): bool
{
if(!str_contains($link, "https://") && !str_contains($link, "http://"))
$link = "https://" . $link;
# Existability
if(is_null($link) || empty($link))
throw new \InvalidArgumentException("Empty link");
# Length
if(iconv_strlen($link) < 2 || iconv_strlen($link) > 400)
throw new \LengthException("Link is too long");
# Match URL regex
# stolen from http://urlregex.com/
if (!preg_match("%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+|xn--[a-z\d-]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.(?:xn--[a-z\d-]+|[a-z\x{00a1}-\x{ffff}]{2,6})))(?::\d+)?(?:[^\s]*)?$%iu", $link))
throw new \InvalidArgumentException("Invalid link format");
$banEntries = (new openvk\Web\Models\Repositories\BannedLinks)->check($link);
if(sizeof($banEntries) > 0)
throw new \LogicException("Suspicious link");
return true;
}
return (function() {
_ovk_check_environment();
require __DIR__ . "/vendor/autoload.php";

View file

@ -0,0 +1,2 @@
ALTER TABLE `posts`
ADD COLUMN `source` TEXT NULL DEFAULT NULL AFTER `api_source_name`;

View file

@ -111,7 +111,7 @@
"relationship_4" = "Married";
"relationship_5" = "In a civil marriage";
"relationship_6" = "In love";
"relationship_7" = "Everything is complicated";
"relationship_7" = "It's complicated";
"relationship_8" = "Actively searching";
/* xd */
@ -122,7 +122,7 @@
"relationship_6_prefix" = "with";
"relationship_7_prefix" = "with";
"politViews" = "Polit. Views";
"politViews" = "Political views";
"politViews_0" = "Not Selected";
"politViews_1" = "Indifferent";
@ -156,7 +156,7 @@
"updated_at" = "Updated at $1";
"user_banned" = "Unfortunately, we had to block the <b>$1</b> user page.";
"user_banned" = "Unfortunately, we had to block <b>$1's</b> user page.";
"user_banned_comment" = "Moderator's comment:";
"verified_page" = "Verified page";
"user_is_blocked" = "User is blocked";
@ -201,11 +201,11 @@
"pin" = "Pin";
"unpin" = "Unpin";
"pinned" = "pinned";
"comments_tip" = "Be first, who leaves a comment at this post!";
"comments_tip" = "Be the first to leave a comment on this post!";
"your_comment" = "Your comment";
"auditory" = "Auditory";
"in_wall" = "to user's wall";
"in_group" = "to group";
"in_wall" = "to my wall";
"in_group" = "to a group";
"shown" = "Shown";
"x_out_of" = "$1 of";
"wall_zero" = "no posts";
@ -223,6 +223,12 @@
"attachment" = "Attachment";
"post_as_group" = "Post as group";
"comment_as_group" = "Comment as group";
"add_source" = "Add source";
"set_source" = "Apply source";
"source" = "Source";
"set_source_tip" = "If you are using content from other authors, it is important to provide a source to the original.<br>You can do it below.";
"add_signature" = "Add signature";
/* ^ can be translated as "author's signature". ^ */
"contains_nsfw" = "Contains NSFW content";
@ -236,7 +242,7 @@
"no_posts_abstract" = "Nobody wrote anything here... So far.";
"attach_no_longer_available" = "This attachment is no longer available.";
"open_post" = "Open post";
"version_incompatibility" = "This attachment could not be displayed. Probably the database is incompatible with the current version of OpenVK.";
"version_incompatibility" = "This attachment could not be displayed. Probably because the database is incompatible with the current version of OpenVK.";
"graffiti" = "Graffiti";
@ -317,8 +323,8 @@
"create_group" = "Create group";
"group_managers" = "Managers";
"group_type" = "Group type";
"group_type_open" = "This is an open group, anyone can enter it.";
"group_type_closed" = "This is an closed group. To enter, you must submit an request.";
"group_type_open" = "This is an open group. Anyone can enter it.";
"group_type_closed" = "This is a closed group. To enter, you must submit a request.";
"creator" = "Creator";
"administrators" = "Administrators";
"add_to_left_menu" = "Add to left menu";
@ -357,12 +363,12 @@
"suggested_by_everyone_many" = "$1 suggested posts";
"suggested_by_everyone_other" = "$1 suggested posts";
"group_hide_from_global_feed" = "Don't display posts in the global feed";
"suggested_posts_by_you" = "Suggested posts by you";
"group_hide_from_global_feed" = "Don't display posts in global feed";
"suggested_posts_by_you" = "Your suggested posts";
"suggested_posts_by_everyone" = "Suggested posts";
"suggested" = "Suggested";
"suggested_posts_everyone" = "Suggested by users posts";
"no_suggested_posts_by_you" = "You haven't suggested posts to this group yet.";
"suggested_posts_everyone" = "Posts suggested by users";
"no_suggested_posts_by_you" = "You haven't suggested any post to this group yet.";
"no_suggested_posts_by_people" = "No posts have been suggested to this group yet.";
"publish_suggested" = "Accept";
@ -377,23 +383,24 @@
"suggested_posts_in_group_many" = "This group has $1 suggested posts";
"suggested_posts_in_group_other" = "This group has $1 suggested posts";
"suggested_posts_in_group_by_you_zero" = "You haven't suggested any posts to this group";
"suggested_posts_in_group_by_you_zero" = "You haven't suggested any post to this group";
"suggested_posts_in_group_by_you_one" = "You suggested one post to this group";
"suggested_posts_in_group_by_you_few" = "You suggested $1 posts to this group";
"suggested_posts_in_group_by_you_many" = "You suggested $1 posts to this group";
"suggested_posts_in_group_by_you_other" = "You suggested $1 posts to this group";
"suggestion_succefully_published" = "Post successfully published";
"suggestion_succefully_declined" = "Post successfully declined";
"suggestion_press_to_go" = "Click to show it";
"suggestion_successfully_published" = "Post successfully published";
"suggestion_successfully_declined" = "Post successfully declined";
"suggestion_press_to_go" = "Go to post";
"error_declining_invalid_post" = "Error when declining post: post does not exists";
"error_declining_not_suggested_post" = "Error when declining post: post is not suggested";
"error_declining_declined_post" = "Error when declining post: post is already declined";
"error_declining_invalid_post" = "The suggested post you attempted to decline is invalid";
"error_declining_not_suggested_post" = "The post you attempted to decline is not suggested";
"error_declining_declined_post" = "This suggested post has already been declined";
"error_accepting_invalid_post" = "The suggested post you attempted to accept is invalid";
"error_accepting_not_suggested_post" = "The post you attempted to accept is not suggested";
"error_accepting_declined_post" = "This suggested post has already been declined";
"error_accepting_invalid_post" = "Error when accepting post: post does not exists";
"error_accepting_not_suggested_post" = "Error when accepting post: post is not suggested";
"error_accepting_declined_post" = "Error when accepting post: cant accept declined post";
"statistics" = "Statistics";
"group_administrators_list" = "Admins list";
"group_display_only_creator" = "Display only group creator";
@ -483,6 +490,7 @@
"deleting_avatar" = "Deleting photo";
"deleting_avatar_sure" = "Do you sure you want to delete avatar?";
"deleted_avatar_notification" = "Picture deleted successfully";
"save_changes" = "Save changes";
"upd_m" = "updated his profile picture";
@ -493,7 +501,7 @@
"add_photos" = "Add photos";
"upload_picts" = "Upload photos";
"end_uploading" = "Finish uploading";
"photos_successfully_uploaded" = "Photos successfully uploaded";
"photos_successfully_uploaded" = "Photos uploaded successfully";
"click_to_go_to_album" = "Click here to go to album.";
"error_uploading_photo" = "Error when uploading photo";
"too_many_pictures" = "No more than 10 pictures";
@ -553,10 +561,10 @@
"error_attaching_note" = "Error when attaching note";
"select_or_create_new" = "Select existing note or <a href='/notes/create'>create new one</a>";
"select_or_create_new" = "Select an existing note or <a href='/notes/create'>create a 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";
"notes_closed" = "You can't attach a note to the post because only you can see them.<br> You can change this in <a href=\"/settings?act=privacy\">settings</a>.";
"do_not_attach_note" = "Do not attach a note";
"something" = "Something";
"supports_xhtml" = "from (X)HTML supported.";
@ -579,15 +587,15 @@
"my_feed" = "My Feed";
"my_feedback" = "My Feedback";
"my_settings" = "My Settings";
"bug_tracker" = "Bug-tracker";
"bug_tracker" = "Bug Tracker";
"menu_settings" = "Settings";
"menu_login" = "Login";
"menu_registration" = "Registration";
"menu_registration" = "Register";
"menu_help" = "Help";
"menu_logout" = "Logout";
"menu_logout" = "Log out";
"menu_support" = "Support";
"header_home" = "home";
@ -834,6 +842,7 @@
/* Audios */
"my" = "My";
"audios" = "Audios";
"audio" = "Audio";
"playlist" = "Playlist";
@ -849,16 +858,16 @@
"limits" = "Limits";
"select_audio" = "Select audio from your computer";
"audio_requirements" = "Audio must be between $1 seconds to $2 minutes, weights to $3 MB and contain audio stream.";
"audio_requirements_2" = "Audio must not infringe copyright and related rights";
"you_can_also_add_audio_using" = "You can also add audio from among the files you have already downloaded using";
"audio_requirements" = "Audio must be between $1 seconds and $2 minutes, with a file size up to $3 MB, and contain an audio stream.";
"audio_requirements_2" = "Audio must not infringe copyright and related rights.";
"you_can_also_add_audio_using" = "You can also add audio from files you have already downloaded using";
"search_audio_inst" = "audios search";
"audio_embed_not_found" = "Audio not found";
"audio_embed_deleted" = "Audio was deleted";
"audio_embed_withdrawn" = "The audio was withdrawn at the request of the copyright holder";
"audio_embed_deleted" = "Audio has been deleted";
"audio_embed_withdrawn" = "The audio has been withdrawn at the request of the copyright holder";
"audio_embed_forbidden" = "The user's privacy settings do not allow this audio to be embedded";
"audio_embed_processing" = "Audio is still being processed, or has not been processed correctly.";
"audio_embed_processing" = "Audio is still being processed or has not been processed correctly.";
"audios_count_zero" = "No audios";
"audios_count_one" = "One audio";
@ -871,7 +880,7 @@
"my_music" = "My music";
"music_user" = "User's music";
"music_club" = "Club's music";
"music_club" = "Group's music";
"audio_new" = "New";
"audio_popular" = "Popular";
"audio_search" = "Search";
@ -914,19 +923,19 @@
"delete_playlist" = "Delete playlist";
"playlist_cover" = "Playlist cover";
"playlists_user" = "Users playlists";
"playlists_club" = "Groups playlists";
"playlists_user" = "User's playlists";
"playlists_club" = "Group's playlists";
"change_cover" = "Change cover";
"playlist_cover" = "Playlist's cover";
"minutes_count_zero" = "lasts no minutes";
"minutes_count_one" = "lasts one minute";
"minutes_count_few" = "lasts $1 minutes";
"minutes_count_many" = "lasts $1 minutes";
"minutes_count_other" = "lasts $1 minutes";
"minutes_count_zero" = "Lasts no minutes";
"minutes_count_one" = "Lasts one minute";
"minutes_count_few" = "Lasts $1 minutes";
"minutes_count_many" = "Lasts $1 minutes";
"minutes_count_other" = "Lasts $1 minutes";
"listens_count_zero" = "no listens";
"listens_count_one" = "one listen";
"listens_count_zero" = "No listens";
"listens_count_one" = "One listen";
"listens_count_few" = "$1 listens";
"listens_count_many" = "$1 listens";
"listens_count_other" = "$1 listens";
@ -949,7 +958,7 @@
"audio_successfully_uploaded" = "Audio has been successfully uploaded and is currently being processed.";
"broadcast_audio" = "Broadcast audio to status";
"sure_delete_playlist" = "Do you sure want to delete this playlist?";
"sure_delete_playlist" = "Are you sure you want to delete this playlist?";
"edit_audio" = "Edit audio";
"audios_group" = "Audios from group";
"playlists_group" = "Playlists from group";
@ -1057,7 +1066,7 @@
"coins_count" = "Number of votes";
"message" = "Message";
"failed_to_tranfer_points" = "Failed to transfer votes";
"failed_to_transfer_points" = "Failed to transfer votes";
"points_transfer_successful" = "You have successfully transferred <b>$1</b> to <b><a href=\"$2\">$3</a></b>.";
"not_all_information_has_been_entered" = "Not all information has been entered.";
@ -1076,8 +1085,8 @@
"apply_voucher" = "Apply voucher";
"failed_to_increase_rating" = "Failed to increase rating";
"rating_increase_successful" = "You have successfully increased rating of <b><a href=\"$1\">$2</a></b> by <b>$3%</b>.";
"negative_rating_value" = "We cannot steal rating from another person, sorry.";
"rating_increase_successful" = "You have successfully increased the rating of <b><a href=\"$1\">$2</a></b> by <b>$3%</b>.";
"negative_rating_value" = "You can't steal ratings from another person.";
"increased_your_rating_by" = "increased your rating by";
@ -1138,43 +1147,43 @@
"app_withdrawal_empty" = "Sorry, withdrawal of emptiness is not possible.";
"app_withdrawal_created" = "A request to withdraw $1 votes has been created. Awaiting crediting.";
"appjs_payment" = "Purchase payment";
"appjs_payment" = "Purchase Payment";
"appjs_payment_intro" = "You are about to pay for an order in the application";
"appjs_order_items" = "Order items";
"appjs_payment_total" = "Total amount payable";
"appjs_payment_confirm" = "Pay";
"appjs_err_funds" = "Failed to pay: insufficient funds.";
"appjs_wall_post" = "Publish a post";
"appjs_wall_post" = "Publish a Post";
"appjs_wall_post_desc" = "wants to publish a post on your wall";
"appjs_act_friends" = "your Friends";
"appjs_act_friends" = "Your Friends";
"appjs_act_friends_desc" = "add users as friends and read your friends list";
"appjs_act_wall" = "your Wall";
"appjs_act_wall" = "Your Wall";
"appjs_act_wall_desc" = "see your news, your wall and create posts on it";
"appjs_act_messages" = "your Messages";
"appjs_act_messages" = "Your Messages";
"appjs_act_messages_desc" = "read and write messages on your behalf";
"appjs_act_groups" = "your Groups";
"appjs_act_groups_desc" = "see a list of your groups and subscribe you to other";
"appjs_act_likes" = "Likes feature";
"appjs_act_groups" = "Your Groups";
"appjs_act_groups_desc" = "see a list of your groups and subscribe you to others";
"appjs_act_likes" = "Likes Feature";
"appjs_act_likes_desc" = "give and take away likes to posts";
"appjs_act_request" = "Access request";
"appjs_act_request" = "Access Request";
"appjs_act_requests" = "requests access to";
"appjs_act_can" = "The app will be able to";
"appjs_act_allow" = "Allow";
"appjs_act_disallow" = "Disallow";
"app_uninstalled" = "Application is disabled";
"app_uninstalled" = "Application Disabled";
"app_uninstalled_desc" = "It will no longer be able to perform actions on your behalf.";
"app_err_not_found" = "Application not found";
"app_err_not_found" = "Application Not Found";
"app_err_not_found_desc" = "Incorrect identifier or it has been disabled.";
"app_err_forbidden_desc" = "This application is not yours.";
"app_err_url" = "Incorrect address";
"app_err_url_desc" = "The address of the application did not pass the check, make sure it is correct.";
"app_err_ava" = "Unable to upload an avatar";
"app_err_ava_desc" = "Avatar too big or wrong: general error #$res.";
"app_err_note" = "Failed to attach a news note";
"app_err_forbidden_desc" = "This application does not belong to you.";
"app_err_url" = "Incorrect Address";
"app_err_url_desc" = "The address of the application did not pass the check; make sure it is correct.";
"app_err_ava" = "Unable to Upload an Avatar";
"app_err_ava_desc" = "Avatar is too big or incorrect: general error #$res.";
"app_err_note" = "Failed to Attach a News Note";
"app_err_note_desc" = "Make sure the link is correct and the note belongs to you.";
"learn_more" = "Learn more";
@ -1182,7 +1191,7 @@
/* Support */
"support_opened" = "Opened";
"support_answered" = "With a response";
"support_answered" = "Has a response";
"support_closed" = "Closed";
"support_ticket" = "Ticket";
"support_tickets" = "Tickets";
@ -1190,28 +1199,28 @@
"support_status_1" = "There's a response";
"support_status_2" = "Closed";
"support_greeting_hi" = "Greetings, $1!";
"support_greeting_regards" = "Best regards,<br/>$1 support team.";
"support_greeting_regards" = "Best regards,<br/>$1 Support Team.";
"support_faq" = "Frequently Asked Questions";
"support_list" = "List of tickets";
"support_new" = "New ticket";
"support_list" = "List of Tickets";
"support_new" = "New Ticket";
"support_new_title" = "Enter the topic of your ticket";
"support_new_content" = "Describe the issue or suggestion";
"reports" = "Reports";
"support_rate_good_answer" = "This is good answer";
"support_rate_bad_answer" = "This is bad answer";
"support_good_answer_user" = "You left a positive feedback.";
"support_bad_answer_user" = "You left a negative feedback.";
"support_good_answer_agent" = "User left a positive feedback.";
"support_bad_answer_agent" = "User left a negative feedback.";
"support_rated_good" = "You left a positive feedback about the answer.";
"support_rated_bad" = "You left a negative feedback about the answer.";
"support_rate_good_answer" = "This is a good answer";
"support_rate_bad_answer" = "This is a bad answer";
"support_good_answer_user" = "You left positive feedback.";
"support_bad_answer_user" = "You left negative feedback.";
"support_good_answer_agent" = "User left positive feedback.";
"support_bad_answer_agent" = "User left negative feedback.";
"support_rated_good" = "You left positive feedback about the answer.";
"support_rated_bad" = "You left negative feedback about the answer.";
"wrong_parameters" = "Invalid request parameters.";
"fast_answers" = "Fast answers";
"fast_answers" = "Quick Answers";
"ignore_report" = "Ignore report";
"report_number" = "Report #";
@ -1306,7 +1315,7 @@
"poll_editor_tips" = "Pressing backspace in empty option will remove it. Use Tab/Enter (in last option) to create new options faster.";
"poll_embed" = "Embed code";
"poll_voter_count_zero" = "Be <b>the first one</b> to vote!";
"poll_voter_count_zero" = "Be <b>the first</b> to vote!";
"poll_voter_count_one" = "<b>Only one</b> user voted.";
"poll_voter_count_few" = "<b>$1</b> users voted.";
"poll_voter_count_many" = "<b>$1</b> users voted.";
@ -1333,7 +1342,7 @@
"messages_other" = "$1 messages";
"topic_messages_count_zero" = "Topic has no messages";
"topic_messages_count_one" = "There are one message in the topic";
"topic_messages_count_one" = "There is one message in the topic";
"topic_messages_count_other" = "There are $1 messages in the topic";
"replied" = "replied";
@ -1399,7 +1408,7 @@
"photo_saved" = "Photo saved";
"photo_saved_comment" = "New profile picture will appear on your page";
"shared_succ" = "The post will appear on your wall. Click on the notification to go to your wall.";
"shared_succ" = "The post will appear on your wall. Click on this notification to go there.";
"invalid_email_address" = "Invalid Email address";
"invalid_email_address_comment" = "The Email you entered is not correct.";
@ -1553,6 +1562,10 @@
"ffmpeg_not_installed" = "Failed to proccess the file. It looks like ffmpeg is not installed on this server.";
"too_many_or_to_lack" = "Too few or too many sources.";
"error_adding_source_regex" = "Error adding source: incorrect link.";
"error_adding_source_long" = "Error adding source: link is too long.";
"error_adding_source_sus" = "Error adding source: suspicious link.";
/* Admin actions */
"login_as" = "Login as $1";
@ -1993,6 +2006,7 @@
/* Search */
"s_params" = "Search params";
"s_people" = "Users";
"s_groups" = "Clubs";
"s_apps" = "Applications";

View file

@ -207,6 +207,12 @@
"attachment" = "Вложение";
"post_as_group" = "От имени сообщества";
"comment_as_group" = "От имени сообщества";
"add_source" = "Добавление источника";
"set_source" = "Указать источник";
"source" = "Источник";
"set_source_tip" = "Если вы используете материалы других авторов, важно указывать ссылку на оригинал.<br>Сделать это вы можете ниже.";
"add_signature" = "Подпись автора";
"contains_nsfw" = "Содержит NSFW-контент";
"nsfw_warning" = "Данный пост может содержать 18+ контент";
@ -791,6 +797,7 @@
/* Audios */
"my" = "Моё";
"audios" = "Аудиозаписи";
"audio" = "Аудиозапись";
"playlist" = "Плейлист";
@ -870,7 +877,7 @@
"remove_from_playlist" = "Удалить из плейлиста";
"delete_playlist" = "Удалить плейлист";
"playlist_cover" = "Обложка плейлиста";
"playlists_user" = "Плейлисты польз.";
"playlists_user" = "Плейлисты пользователя";
"playlists_club" = "Плейлисты группы";
"change_cover" = "Сменить обложку";
"playlist_cover" = "Обложка плейлиста";
@ -1455,6 +1462,10 @@
"ffmpeg_not_installed" = "Не удалось обработать файл. Похоже, на сервере не установлен ffmpeg.";
"too_many_or_to_lack" = "Слишком мало либо слишком много источников.";
"error_adding_source_regex" = "Ошибка добавления источника: некорректная ссылка.";
"error_adding_source_long" = "Ошибка добавления источника: слишком длинная ссылка.";
"error_adding_source_sus" = "Ошибка добавления источника: подозрительная ссылка.";
/* Admin actions */
"login_as" = "Войти как $1";
@ -1883,6 +1894,7 @@
/* Search */
"s_params" = "Параметры поиска";
"s_people" = "Пользователи";
"s_groups" = "Группы";
"s_apps" = "Приложения";