diff --git a/ServiceAPI/Photos.php b/ServiceAPI/Photos.php new file mode 100644 index 00000000..16d602f2 --- /dev/null +++ b/ServiceAPI/Photos.php @@ -0,0 +1,92 @@ +user = $user; + $this->photos = new PhotosRepo; + } + + function getPhotos(int $page = 1, int $album = 0, callable $resolve, callable $reject) + { + if($album == 0) { + $photos = $this->photos->getEveryUserPhoto($this->user, $page, 24); + $count = $this->photos->getUserPhotosCount($this->user); + } else { + $album = (new Albums)->get($album); + + if(!$album || $album->isDeleted()) + $reject(55, "Invalid ."); + + if($album->getOwner() instanceof User) { + if($album->getOwner()->getId() != $this->user->getId()) + $reject(555, "Access to album denied"); + } else { + if(!$album->getOwner()->canBeModifiedBy($this->user)) + $reject(555, "Access to album denied"); + } + + $photos = $album->getPhotos($page, 24); + $count = $album->size(); + } + + $arr = [ + "count" => $count, + "items" => [], + ]; + + foreach($photos as $photo) { + $res = json_decode(json_encode($photo->toVkApiStruct()), true); + + $arr["items"][] = $res; + } + + $resolve($arr); + } + + function getAlbums(int $club, callable $resolve, callable $reject) + { + $albumsRepo = (new Albums); + + $count = $albumsRepo->getUserAlbumsCount($this->user); + $albums = $albumsRepo->getUserAlbums($this->user, 1, $count); + + $arr = [ + "count" => $count, + "items" => [], + ]; + + foreach($albums as $album) { + $res = ["id" => $album->getId(), "name" => $album->getName()]; + + $arr["items"][] = $res; + } + + if($club > 0) { + $cluber = (new Clubs)->get($club); + + if(!$cluber || !$cluber->canBeModifiedBy($this->user)) + $reject(1337, "Invalid (club), or you can't modify him"); + + $clubCount = (new Albums)->getClubAlbumsCount($cluber); + $clubAlbums = (new Albums)->getClubAlbums($cluber, 1, $clubCount); + + foreach($clubAlbums as $albumr) { + $res = ["id" => $albumr->getId(), "name" => $albumr->getName()]; + + $arr["items"][] = $res; + } + + $arr["count"] = $arr["count"] + $clubCount; + } + + $resolve($arr); + } +} diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php index 7f594e1d..467d59fe 100644 --- a/ServiceAPI/Wall.php +++ b/ServiceAPI/Wall.php @@ -4,6 +4,7 @@ use openvk\Web\Models\Entities\Post; use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\Notifications\PostAcceptedNotification; use openvk\Web\Models\Repositories\{Posts, Notes}; +use openvk\Web\Models\Repositories\{Posts, Notes, Videos}; class Wall implements Handler { @@ -16,6 +17,7 @@ class Wall implements Handler $this->user = $user; $this->posts = new Posts; $this->notes = new Notes; + $this->videos = new Videos; } function getPost(int $id, callable $resolve, callable $reject): void @@ -99,7 +101,7 @@ class Wall implements Handler $resolve($arr); } - + function declinePost(int $id, callable $resolve, callable $reject) { $post = $this->posts->get($id); @@ -159,4 +161,45 @@ class Wall implements Handler $resolve(["id" => $post->getPrettyId(), "new_count" => $this->posts->getSuggestedPostsCount($post->getWallOwner()->getId())]); } + + function getVideos(int $page = 1, callable $resolve, callable $reject) + { + $videos = $this->videos->getByUser($this->user, $page, 8); + $count = $this->videos->getUserVideosCount($this->user); + + $arr = [ + "count" => $count, + "items" => [], + ]; + + foreach($videos as $video) { + $res = json_decode(json_encode($video->toVkApiStruct()), true); + $res["video"]["author_name"] = $video->getOwner()->getCanonicalName(); + + $arr["items"][] = $res; + } + + $resolve($arr); + } + + function searchVideos(int $page = 1, string $query, callable $resolve, callable $reject) + { + $dbc = $this->videos->find($query); + $videos = $dbc->page($page, 8); + $count = $dbc->size(); + + $arr = [ + "count" => $count, + "items" => [], + ]; + + foreach($videos as $video) { + $res = json_decode(json_encode($video->toVkApiStruct()), true); + $res["video"]["author_name"] = $video->getOwner()->getCanonicalName(); + + $arr["items"][] = $res; + } + + $resolve($arr); + } } diff --git a/VKAPI/Handlers/Notes.php b/VKAPI/Handlers/Notes.php index ce26baae..7c9c9fec 100644 --- a/VKAPI/Handlers/Notes.php +++ b/VKAPI/Handlers/Notes.php @@ -211,7 +211,7 @@ final class Notes extends VKAPIRequestHandler $items = []; $note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]); - if($note) { + if($note && !$note->isDeleted()) { $nodez->notes[] = $note->toVkApiStruct(); } } diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index f58feebf..85585290 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -578,28 +578,25 @@ final class Wall extends VKAPIRequestHandler if($attachmentType == "photo") { $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); if(!$attacc || $attacc->isDeleted()) - $this->fail(100, "Photo does not exists"); - if($attacc->getOwner()->getId() != $this->getUser()->getId()) - $this->fail(43, "You do not have access to this photo"); + $this->fail(100, "Invalid photo"); + if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) + $this->fail(43, "Access to photo denied"); $post->attach($attacc); } elseif($attachmentType == "video") { $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); if(!$attacc || $attacc->isDeleted()) $this->fail(100, "Video does not exists"); - if($attacc->getOwner()->getId() != $this->getUser()->getId()) - $this->fail(43, "You do not have access to this video"); + if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser())) + $this->fail(43, "Access to video denied"); $post->attach($attacc); } elseif($attachmentType == "note") { $attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId); if(!$attacc || $attacc->isDeleted()) $this->fail(100, "Note does not exist"); - if($attacc->getOwner()->getId() != $this->getUser()->getId()) - $this->fail(43, "You do not have access to this note"); - - if($attacc->getOwner()->getPrivacySetting("notes.read") < 1) - $this->fail(11, "You can't attach note to post, because your notes list is closed. Change it in privacy settings in web-version."); + if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser())) + $this->fail(11, "Access to note denied"); $post->attach($attacc); } @@ -809,7 +806,7 @@ final class Wall extends VKAPIRequestHandler return $response; } - function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0, string $attachments = "") { + function createComment(int $owner_id, int $post_id, string $message = "", int $from_group = 0, string $attachments = "") { $this->requireUser(); $this->willExecuteWriteAction(); @@ -867,16 +864,16 @@ final class Wall extends VKAPIRequestHandler $attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); if(!$attacc || $attacc->isDeleted()) $this->fail(100, "Photo does not exists"); - if($attacc->getOwner()->getId() != $this->getUser()->getId()) - $this->fail(43, "You do not have access to this photo"); + if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) + $this->fail(11, "Access to photo denied"); $comment->attach($attacc); } elseif($attachmentType == "video") { $attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); if(!$attacc || $attacc->isDeleted()) $this->fail(100, "Video does not exists"); - if($attacc->getOwner()->getId() != $this->getUser()->getId()) - $this->fail(43, "You do not have access to this video"); + if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser())) + $this->fail(11, "Access to video denied"); $comment->attach($attacc); } diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index 9feb29b9..217c7dab 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -241,7 +241,7 @@ class Club extends RowModel "shape" => "spline", "color" => "#597da3", ], - "name" => $unique ? "Полный охват" : "Все просмотры", + "name" => $unique ? tr("full_coverage") : tr("all_views"), ], "subs" => [ "x" => array_reverse(range(1, 7)), @@ -252,7 +252,7 @@ class Club extends RowModel "color" => "#b05c91", ], "fill" => "tozeroy", - "name" => $unique ? "Охват подписчиков" : "Просмотры подписчиков", + "name" => $unique ? tr("subs_coverage") : tr("subs_views"), ], "viral" => [ "x" => array_reverse(range(1, 7)), @@ -263,7 +263,7 @@ class Club extends RowModel "color" => "#4d9fab", ], "fill" => "tozeroy", - "name" => $unique ? "Виральный охват" : "Виральные просмотры", + "name" => $unique ? tr("viral_coverage") : tr("viral_views"), ], ]; } diff --git a/Web/Models/Entities/Comment.php b/Web/Models/Entities/Comment.php index d813d6be..37b06dda 100644 --- a/Web/Models/Entities/Comment.php +++ b/Web/Models/Entities/Comment.php @@ -11,7 +11,7 @@ class Comment extends Post function getPrettyId(): string { - return $this->getRecord()->id; + return (string)$this->getRecord()->id; } function getVirtualId(): int @@ -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(); + } } diff --git a/Web/Models/Entities/IP.php b/Web/Models/Entities/IP.php index df2c9787..0d9b8fd0 100644 --- a/Web/Models/Entities/IP.php +++ b/Web/Models/Entities/IP.php @@ -105,7 +105,7 @@ class IP extends RowModel $this->stateChanges("ip", $ip); } - function save($log): void + function save(?bool $log = false): void { if(is_null($this->getRecord())) $this->stateChanges("first_seen", time()); diff --git a/Web/Models/Entities/Media.php b/Web/Models/Entities/Media.php index 9377f3e8..648d3564 100644 --- a/Web/Models/Entities/Media.php +++ b/Web/Models/Entities/Media.php @@ -121,14 +121,14 @@ abstract class Media extends Postable $this->stateChanges("hash", $hash); } - function save(): void + function save(?bool $log = false): void { if(!is_null($this->processingPlaceholder) && is_null($this->getRecord())) { $this->stateChanges("processed", 0); $this->stateChanges("last_checked", time()); } - parent::save(); + parent::save($log); } function delete(bool $softly = true): void diff --git a/Web/Models/Entities/Note.php b/Web/Models/Entities/Note.php index 37d9ac29..83082bf3 100644 --- a/Web/Models/Entities/Note.php +++ b/Web/Models/Entities/Note.php @@ -124,7 +124,7 @@ class Note extends Postable $res = (object) []; $res->type = "note"; - $res->id = $this->getId(); + $res->id = $this->getVirtualId(); $res->owner_id = $this->getOwner()->getId(); $res->title = $this->getName(); $res->text = $this->getText(); diff --git a/Web/Models/Entities/Post.php b/Web/Models/Entities/Post.php index cfb86003..a43a8b47 100644 --- a/Web/Models/Entities/Post.php +++ b/Web/Models/Entities/Post.php @@ -248,11 +248,25 @@ class Post extends Postable $this->unwire(); $this->save(); } - + function getSuggestionType() { return $this->getRecord()->suggested; } + + function canBeEditedBy(?User $user = NULL): bool + { + if(!$user) + return false; + + if($this->isDeactivationMessage() || $this->isUpdateAvatarMessage()) + return false; + + if($this->getTargetWall() > 0) + return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId(); + + return $user->getId() == $this->getOwner(false)->getId(); + } use Traits\TRichText; } diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php index 6ea55400..74ed492a 100644 --- a/Web/Models/Entities/Postable.php +++ b/Web/Models/Entities/Postable.php @@ -152,7 +152,7 @@ abstract class Postable extends Attachable throw new ISE("Setting virtual id manually is forbidden"); } - function save(): void + function save(?bool $log = false): void { $vref = $this->upperNodeReferenceColumnName; @@ -167,11 +167,11 @@ abstract class Postable extends Attachable $this->stateChanges("created", time()); $this->stateChanges("virtual_id", $pCount + 1); - } else { + } /*else { $this->stateChanges("edited", time()); - } + }*/ - parent::save(); + parent::save($log); } use Traits\TAttachmentHost; diff --git a/Web/Models/Entities/Report.php b/Web/Models/Entities/Report.php index 4070952e..d449a2e8 100644 --- a/Web/Models/Entities/Report.php +++ b/Web/Models/Entities/Report.php @@ -3,6 +3,7 @@ namespace openvk\Web\Models\Entities; use openvk\Web\Util\DateTime; use Nette\Database\Table\ActiveRow; use openvk\Web\Models\RowModel; +use openvk\Web\Models\Entities\Club; use Chandler\Database\DatabaseConnection; use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Reports, Users, Posts, Photos, Videos, Clubs}; use Chandler\Database\DatabaseConnection as DB; @@ -96,8 +97,19 @@ class Report extends RowModel { if ($this->getContentType() !== "user") { $pubTime = $this->getContentObject()->getPublicationTime(); - $name = $this->getContentObject()->getName(); - $this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $pubTime ($name) был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать."); + if (method_exists($this->getContentObject(), "getName")) { + $name = $this->getContentObject()->getName(); + $placeholder = "$pubTime ($name)"; + } else { + $placeholder = "$pubTime"; + } + + if ($this->getAuthor() instanceof Club) { + $name = $this->getAuthor()->getName(); + $this->getAuthor()->getOwner()->adminNotify("Ваш контент, который опубликовали $placeholder в созданной вами группе \"$name\" был удалён модераторами инстанса. За повторные или серьёзные нарушения группу могут заблокировать."); + } else { + $this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $placeholder был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать."); + } $this->getContentObject()->delete($this->getContentType() !== "app"); } diff --git a/Web/Models/Entities/Traits/TAttachmentHost.php b/Web/Models/Entities/Traits/TAttachmentHost.php index cbe7cad2..db814cce 100644 --- a/Web/Models/Entities/Traits/TAttachmentHost.php +++ b/Web/Models/Entities/Traits/TAttachmentHost.php @@ -1,6 +1,7 @@ get($rel->attachable_id); } } + + function getChildrenWithLayout(int $w, int $h = -1): object + { + if($h < 0) + $h = $w; + + $children = $this->getChildren(); + $skipped = $photos = $result = []; + foreach($children as $child) { + if($child instanceof Photo) { + $photos[] = $child; + continue; + } + + $skipped[] = $child; + } + + $height = "unset"; + $width = $w; + if(sizeof($photos) < 2) { + if(isset($photos[0])) + $result[] = ["100%", "unset", $photos[0], "unset"]; + } else { + $mak = new Makima($photos); + $layout = $mak->computeMasonryLayout($w, $h); + $height = $layout->height; + $width = $layout->width; + for($i = 0; $i < sizeof($photos); $i++) { + $tile = $layout->tiles[$i]; + $result[] = [$tile->width . "px", $tile->height . "px", $photos[$i], "left"]; + } + } + + return (object) [ + "width" => $width . "px", + "height" => $height . "px", + "tiles" => $result, + "extras" => $skipped, + ]; + } function attach(Attachable $attachment): void { diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index e5b8da06..aaf00ec9 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -462,6 +462,7 @@ class User extends RowModel "news", "links", "poster", + "apps" ], ])->get($id); } @@ -1026,6 +1027,7 @@ class User extends RowModel "news", "links", "poster", + "apps" ], ])->set($id, (int) $status)->toInteger(); diff --git a/Web/Models/Repositories/Gifts.php b/Web/Models/Repositories/Gifts.php index f36b82a5..3baa2397 100644 --- a/Web/Models/Repositories/Gifts.php +++ b/Web/Models/Repositories/Gifts.php @@ -42,4 +42,10 @@ class Gifts foreach($cats as $cat) yield new GiftCategory($cat); } + + function getCategoriesCount(): int + { + $cats = $this->cats->where("deleted", false); + return $cats->count(); + } } diff --git a/Web/Models/Repositories/Photos.php b/Web/Models/Repositories/Photos.php index 88c7e804..0698c914 100644 --- a/Web/Models/Repositories/Photos.php +++ b/Web/Models/Repositories/Photos.php @@ -33,14 +33,26 @@ class Photos return new Photo($photo); } - function getEveryUserPhoto(User $user): \Traversable + function getEveryUserPhoto(User $user, int $page = 1, ?int $perPage = NULL): \Traversable { + $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; $photos = $this->photos->where([ - "owner" => $user->getId() - ]); + "owner" => $user->getId(), + "deleted" => 0 + ])->order("id DESC"); - foreach($photos as $photo) { + foreach($photos->page($page, $perPage) as $photo) { yield new Photo($photo); } } + + function getUserPhotosCount(User $user) + { + $photos = $this->photos->where([ + "owner" => $user->getId(), + "deleted" => 0 + ]); + + return sizeof($photos); + } } diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index f5c40bcc..14fbbc74 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -283,7 +283,7 @@ final class AdminPresenter extends OpenVKPresenter $this->notFound(); $gift->delete(); - $this->flashFail("succ", "Gift moved successfully", "This gift will now be in Recycle Bin."); + $this->flashFail("succ", tr("admin_gift_moved_successfully"), tr("admin_gift_moved_to_recycle")); break; case "copy": case "move": @@ -302,7 +302,7 @@ final class AdminPresenter extends OpenVKPresenter $catTo->addGift($gift); $name = $catTo->getName(); - $this->flash("succ", "Gift moved successfully", "This gift will now be in $name."); + $this->flash("succ", tr("admin_gift_moved_successfully"), "This gift will now be in $name."); $this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/"); break; default: @@ -333,10 +333,10 @@ final class AdminPresenter extends OpenVKPresenter $gift->setUsages((int) $this->postParam("usages")); if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) { if(!$gift->setImage($_FILES["pic"]["tmp_name"])) - $this->flashFail("err", "Не удалось сохранить подарок", "Изображение подарка кривое."); + $this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_bad_image")); } else if($gen) { # If there's no gift pic but it's newly created - $this->flashFail("err", "Не удалось сохранить подарок", "Пожалуйста, загрузите изображение подарка."); + $this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_no_image")); } $gift->save(); @@ -363,7 +363,7 @@ final class AdminPresenter extends OpenVKPresenter if (str_contains($this->queryParam("reason"), "*")) exit(json_encode([ "error" => "Incorrect reason" ])); - $unban_time = strtotime($this->queryParam("date")) ?: NULL; + $unban_time = strtotime($this->queryParam("date")) ?: "permanent"; $user = $this->users->get($id); if(!$user) diff --git a/Web/Presenters/AuthPresenter.php b/Web/Presenters/AuthPresenter.php index 23b55dc9..c6a7f143 100644 --- a/Web/Presenters/AuthPresenter.php +++ b/Web/Presenters/AuthPresenter.php @@ -1,7 +1,7 @@ authenticator->authenticate($chUser->getId()); - (new Logs)->create($user->getId(), "profiles", "openvk\\Web\\Models\\Entities\\User", 0, $user, $user, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]); $this->redirect("/id" . $user->getId()); $user->save(); } diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php index cb0efd0d..e005af86 100644 --- a/Web/Presenters/CommentPresenter.php +++ b/Web/Presenters/CommentPresenter.php @@ -2,7 +2,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post}; use openvk\Web\Models\Entities\Notifications\CommentNotification; -use openvk\Web\Models\Repositories\{Comments, Clubs}; +use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos}; final class CommentPresenter extends OpenVKPresenter { @@ -54,9 +54,6 @@ final class CommentPresenter extends OpenVKPresenter if ($entity instanceof Post && $entity->getWallOwner()->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden")); - if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) - $this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator."); - $flags = 0; if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity)) $flags |= 0b10000000; @@ -66,31 +63,49 @@ final class CommentPresenter extends OpenVKPresenter try { $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]); } catch(ISE $ex) { - $this->flashFail("err", "Не удалось опубликовать пост", "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой."); + $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_when_publishing_comment_description")); } } - # TODO move to trait - try { - $photo = NULL; - $video = NULL; - if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { - $album = NULL; - if($wall > 0 && $wall === $this->user->id) - $album = (new Albums)->getUserWallAlbum($wallOwner); - - $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album); + $photos = []; + if(!empty($this->postParam("photos"))) { + $un = rtrim($this->postParam("photos"), ","); + $arr = explode(",", $un); + + if(sizeof($arr) < 11) { + foreach($arr as $dat) { + $ids = explode("_", $dat); + $photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]); + + if(!$photo || $photo->isDeleted()) + continue; + + $photos[] = $photo; + } } + } + + $videos = []; + + if(!empty($this->postParam("videos"))) { + $un = rtrim($this->postParam("videos"), ","); + $arr = explode(",", $un); - if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) { - $video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]); + if(sizeof($arr) < 11) { + foreach($arr as $dat) { + $ids = explode("_", $dat); + $video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]); + + if(!$video || $video->isDeleted()) + continue; + + $videos[] = $video; + } } - } catch(ISE $ex) { - $this->flashFail("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик."); } - if(empty($this->postParam("text")) && !$photo && !$video) - $this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий пустой или слишком большой."); + if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1) + $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty")); try { $comment = new Comment; @@ -102,14 +117,15 @@ final class CommentPresenter extends OpenVKPresenter $comment->setFlags($flags); $comment->save(); } catch (\LengthException $ex) { - $this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой."); + $this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big")); } - if(!is_null($photo)) - $comment->attach($photo); + foreach($photos as $photo) + $comment->attach($photo); - if(!is_null($video)) - $comment->attach($video); + if(sizeof($videos) > 0) + foreach($videos as $vid) + $comment->attach($vid); if($entity->getOwner()->getId() !== $this->user->identity->getId()) if(($owner = $entity->getOwner()) instanceof User) @@ -124,7 +140,7 @@ final class CommentPresenter extends OpenVKPresenter if($mentionee instanceof User) (new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit(); - $this->flashFail("succ", "Комментарий добавлен", "Ваш комментарий появится на странице."); + $this->flashFail("succ", tr("comment_is_added"), tr("comment_is_added_desc")); } function renderDeleteComment(int $id): void @@ -135,15 +151,15 @@ final class CommentPresenter extends OpenVKPresenter $comment = (new Comments)->get($id); if(!$comment) $this->notFound(); if(!$comment->canBeDeletedBy($this->user->identity)) - $this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс."); + $this->throwError(403, "Forbidden", tr("error_access_denied")); if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden")); $comment->delete(); $this->flashFail( "succ", - "Успешно", - "Этот комментарий больше не будет показыватся.
Отметить как спам?" + tr("success"), + tr("comment_will_not_appear") ); } } diff --git a/Web/Presenters/GiftsPresenter.php b/Web/Presenters/GiftsPresenter.php index 8f59bdcb..39359add 100644 --- a/Web/Presenters/GiftsPresenter.php +++ b/Web/Presenters/GiftsPresenter.php @@ -41,6 +41,7 @@ final class GiftsPresenter extends OpenVKPresenter $this->template->user = $user; $this->template->iterator = $cats; + $this->template->count = $this->gifts->getCategoriesCount(); $this->template->_template = "Gifts/Menu.xml"; } @@ -49,7 +50,7 @@ final class GiftsPresenter extends OpenVKPresenter $user = $this->users->get((int) ($this->queryParam("user") ?? 0)); $cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0)); if(!$user || !$cat) - $this->flashFail("err", "Не удалось подарить", "Пользователь или набор не существуют."); + $this->flashFail("err", tr("error_when_gifting"), tr("error_user_not_exists")); $this->template->page = $page = (int) ($this->queryParam("p") ?? 1); $gifts = $cat->getGifts($page, null, $this->template->count); @@ -66,14 +67,14 @@ final class GiftsPresenter extends OpenVKPresenter $gift = $this->gifts->get((int) ($this->queryParam("elid") ?? 0)); $cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0)); if(!$user || !$cat || !$gift || !$cat->hasGift($gift)) - $this->flashFail("err", "Не удалось подарить", "Не удалось подтвердить права на подарок."); + $this->flashFail("err", tr("error_when_gifting"), tr("error_no_rights_gifts")); if(!$gift->canUse($this->user->identity)) - $this->flashFail("err", "Не удалось подарить", "У вас больше не осталось таких подарков."); + $this->flashFail("err", tr("error_when_gifting"), tr("error_no_more_gifts")); $coinsLeft = $this->user->identity->getCoins() - $gift->getPrice(); if($coinsLeft < 0) - $this->flashFail("err", "Не удалось подарить", "Ору нищ не пук."); + $this->flashFail("err", tr("error_when_gifting"), tr("error_no_money")); $this->template->_template = "Gifts/Confirm.xml"; if($_SERVER["REQUEST_METHOD"] !== "POST") { @@ -91,7 +92,7 @@ final class GiftsPresenter extends OpenVKPresenter $user->gift($this->user->identity, $gift, $comment, !is_null($this->postParam("anonymous"))); $gift->used(); - $this->flash("succ", "Подарок отправлен", "Вы отправили подарок " . $user->getFirstName() . " за " . $gift->getPrice() . " голосов."); + $this->flash("succ", tr("gift_sent"), tr("gift_sent_desc", $user->getFirstName(), $gift->getPrice())); $this->redirect($user->getURL()); } diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 7a196439..36a60296 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -61,7 +61,7 @@ final class GroupPresenter extends OpenVKPresenter $club->save(); } catch(\PDOException $ex) { if($ex->getCode() == 23000) - $this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."); + $this->flashFail("err", tr("error"), tr("error_on_server_side")); else throw $ex; } @@ -69,7 +69,7 @@ final class GroupPresenter extends OpenVKPresenter $club->toggleSubscription($this->user->identity); $this->redirect("/club" . $club->getId()); }else{ - $this->flashFail("err", "Ошибка", "Вы не ввели название группы."); + $this->flashFail("err", tr("error"), tr("error_no_group_name")); } } } @@ -139,7 +139,7 @@ final class GroupPresenter extends OpenVKPresenter $this->notFound(); if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) - $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); if(!is_null($hidden)) { if($club->getOwner()->getId() == $user->getId()) { @@ -157,9 +157,9 @@ final class GroupPresenter extends OpenVKPresenter } if($hidden) { - $this->flashFail("succ", "Операция успешна", "Теперь " . $user->getCanonicalName() . " будет показываться как обычный подписчик всем кроме других администраторов"); + $this->flashFail("succ", tr("success_action"), tr("x_is_now_hidden", $user->getCanonicalName())); } else { - $this->flashFail("succ", "Операция успешна", "Теперь все будут знать про то что " . $user->getCanonicalName() . " - администратор"); + $this->flashFail("succ", tr("success_action"), tr("x_is_now_showed", $user->getCanonicalName())); } } elseif($removeComment) { if($club->getOwner()->getId() == $user->getId()) { @@ -171,11 +171,11 @@ final class GroupPresenter extends OpenVKPresenter $manager->save(); } - $this->flashFail("succ", "Операция успешна", "Комментарий к администратору удален"); + $this->flashFail("succ", tr("success_action"), tr("comment_is_deleted")); } elseif($comment) { if(mb_strlen($comment) > 36) { $commentLength = (string) mb_strlen($comment); - $this->flashFail("err", "Ошибка", "Комментарий слишком длинный ($commentLength символов вместо 36 символов)"); + $this->flashFail("err", tr("error"), tr("comment_is_too_long", $commentLength)); } if($club->getOwner()->getId() == $user->getId()) { @@ -187,16 +187,16 @@ final class GroupPresenter extends OpenVKPresenter $manager->save(); } - $this->flashFail("succ", "Операция успешна", "Комментарий к администратору изменён"); + $this->flashFail("succ", tr("success_action"), tr("comment_is_changed")); }else{ if($club->canBeModifiedBy($user)) { $club->removeManager($user); - $this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " более не администратор."); + $this->flashFail("succ", tr("success_action"), tr("x_no_more_admin", $user->getCanonicalName())); } else { $club->addManager($user); (new ClubModeratorNotification($user, $club, $this->user->identity))->emit(); - $this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " назначен(а) администратором."); + $this->flashFail("succ", tr("success_action"), tr("x_is_admin", $user->getCanonicalName())); } } @@ -257,7 +257,7 @@ final class GroupPresenter extends OpenVKPresenter (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); } catch(ISE $ex) { $name = $album->getName(); - $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию."); + $this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); } } @@ -265,12 +265,12 @@ final class GroupPresenter extends OpenVKPresenter $club->save(); } catch(\PDOException $ex) { if($ex->getCode() == 23000) - $this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."); + $this->flashFail("err", tr("error"), tr("error_on_server_side")); else throw $ex; } - $this->flash("succ", "Изменения сохранены", "Новые данные появятся в вашей группе."); + $this->flash("succ", tr("changes_saved"), tr("new_changes_desc")); } } @@ -310,7 +310,7 @@ final class GroupPresenter extends OpenVKPresenter } catch(ISE $ex) { $name = $album->getName(); - $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию."); + $this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); } } $this->returnJson([ @@ -362,7 +362,7 @@ final class GroupPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); if(!eventdb()) - $this->flashFail("err", "Ошибка подключения", "Не удалось подключится к службе телеметрии."); + $this->flashFail("err", tr("connection_error"), tr("connection_error_desc")); $club = $this->clubs->get($id); if(!$club->canBeModifiedBy($this->user->identity)) diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index 1a107659..e2e6b50e 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -1,5 +1,6 @@ postParam("parentType", false) == "post") { + $post = (new Posts)->getPostById($owner_id, $post_id); + } else { + $post = (new Comments)->get($post_id); + } + + + if(is_null($post)) { + $this->returnJson([ + "success" => 0 + ]); + } else { + $response = []; + $attachments = $post->getChildren(); + foreach($attachments as $attachment) + { + if($attachment instanceof \openvk\Web\Models\Entities\Photo) + { + $response[] = [ + "url" => $attachment->getURLBySizeId('normal'), + "id" => $attachment->getPrettyId() + ]; + } + } + $this->returnJson([ + "success" => 1, + "body" => $response + ]); + } + } } diff --git a/Web/Presenters/NoSpamPresenter.php b/Web/Presenters/NoSpamPresenter.php index 1560ba63..8164d05e 100644 --- a/Web/Presenters/NoSpamPresenter.php +++ b/Web/Presenters/NoSpamPresenter.php @@ -177,26 +177,25 @@ final class NoSpamPresenter extends OpenVKPresenter if ($conditions) { $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`"); - if (!$where) { - foreach ($logs as $log) { - $log = (new Logs)->get($log->id); - $response[] = $log->getObject()->unwrap(); - } - } else { - foreach ($logs as $log) { - $log = (new Logs)->get($log->id); - $object = $log->getObject()->unwrap(); + foreach ($logs as $log) { + $log = (new Logs)->get($log->id); + $object = $log->getObject()->unwrap(); - if (!$object) continue; + if (!$object) continue; + if ($where) { if (str_starts_with($where, " AND")) { $where = substr_replace($where, "", 0, strlen(" AND")); } - foreach ($db->query("SELECT * FROM `$table` WHERE $where")->fetchAll() as $o) { - if ($object->id === $o["id"]) { + $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll(); + foreach ($a as $o) { + if ($object->id == $o["id"]) { $response[] = $object; } } + + } else { + $response[] = $object; } } } @@ -206,70 +205,72 @@ final class NoSpamPresenter extends OpenVKPresenter } try { - $response = []; - $processed = 0; + $response = []; + $processed = 0; - $where = $this->postParam("where"); - $ip = $this->postParam("ip"); - $useragent = $this->postParam("useragent"); - $searchTerm = $this->postParam("q"); - $ts = (int)$this->postParam("ts"); - $te = (int)$this->postParam("te"); - $user = $this->postParam("user"); + $where = $this->postParam("where"); + $ip = addslashes($this->postParam("ip")); + $useragent = addslashes($this->postParam("useragent")); + $searchTerm = addslashes($this->postParam("q")); + $ts = (int)$this->postParam("ts"); + $te = (int)$this->postParam("te"); + $user = addslashes($this->postParam("user")); - if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user) - $this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]); - - $models = explode(",", $this->postParam("models")); - - foreach ($models as $_model) { - $model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model; - if (!class_exists($model_name)) { - continue; + if ($where) { + $where = explode(";", $where)[0]; } - $model = new $model_name; + if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user) + $this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]); - $c = new \ReflectionClass($model_name); - if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") { - continue; - } + $models = explode(",", $this->postParam("models")); - $db = DatabaseConnection::i()->getContext(); - $table = $model->getTableName(); - $columns = $db->getStructure()->getColumns($table); - - if ($searchTerm) { - $conditions = []; - $need_deleted = false; - foreach ($columns as $column) { - if ($column["name"] == "deleted") { - $need_deleted = true; - } else { - $conditions[] = "`$column[name]` REGEXP '$searchTerm'"; - } + foreach ($models as $_model) { + $model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model; + if (!class_exists($model_name)) { + continue; } - $conditions = implode(" OR ", $conditions); - $where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)"); - if ($need_deleted) $where .= " AND (`deleted` = 0)"; - } + $model = new $model_name; - $rows = []; - if ($ip || $useragent || $ts || $te || $user) { - $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); - } + $c = new \ReflectionClass($model_name); + if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") { + continue; + } - if (count($rows) === 0) { - if (!$searchTerm) { - if (str_starts_with($where, " AND")) { - if ($searchTerm && !$this->postParam("where")) { - $where = substr_replace($where, "", 0, strlen(" AND")); + $db = DatabaseConnection::i()->getContext(); + $table = $model->getTableName(); + $columns = $db->getStructure()->getColumns($table); + + if ($searchTerm) { + $conditions = []; + $need_deleted = false; + foreach ($columns as $column) { + if ($column["name"] == "deleted") { + $need_deleted = true; } else { - $where = "(" . $this->postParam("where") . ")" . $where; + $conditions[] = "`$column[name]` REGEXP '$searchTerm'"; } } + $conditions = implode(" OR ", $conditions); + $where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)"); + if ($need_deleted) $where .= " AND (`deleted` = 0)"; + } + + $rows = []; + + if (str_starts_with($where, " AND")) { + if ($searchTerm && !$this->postParam("where")) { + $where = substr_replace($where, "", 0, strlen(" AND")); + } else { + $where = "(" . $this->postParam("where") . ")" . $where; + } + } + + if ($ip || $useragent || $ts || $te || $user) { + $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); + } else { if (!$where) { $rows = []; } else { @@ -277,99 +278,105 @@ final class NoSpamPresenter extends OpenVKPresenter $rows = $result->fetchAll(); } } - } - if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) { - foreach ($rows as $key => $object) { - $object = (array)$object; - $_obj = []; - foreach ($object as $key => $value) { - foreach ($columns as $column) { - if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) { - $value = "[BINARY]"; - break; - } - } - - $_obj[$key] = $value; - $_obj["__model_name"] = $_model; - } - $response[] = $_obj; - } - } else { - $ids = []; - - foreach ($rows as $object) { - $object = new $model_name($db->table($table)->get($object->id)); - if (!$object) continue; - $ids[] = $object->getId(); - } - - $log = new NoSpamLog; - $log->setUser($this->user->id); - $log->setModel($_model); - if ($searchTerm) { - $log->setRegex($searchTerm); - } else { - $log->setRequest($where); - } - $log->setBan_Type((int)$this->postParam("ban")); - $log->setCount(count($rows)); - $log->setTime(time()); - $log->setItems(implode(",", $ids)); - $log->save(); - - $banned_ids = []; - foreach ($rows as $object) { - $object = new $model_name($db->table($table)->get($object->id)); - if (!$object) continue; - - $owner = NULL; - $methods = ["getOwner", "getUser", "getRecipient", "getInitiator"]; - - if (method_exists($object, "ban")) { - $owner = $object; - } else { - foreach ($methods as $method) { - if (method_exists($object, $method)) { - $owner = $object->$method(); - break; - } - } - } - - if ($owner instanceof User && $owner->getId() === $this->user->id) { - if (count($rows) === 1) { - $this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]); - } else { - continue; - } - } - - if (in_array((int)$this->postParam("ban"), [2, 3])) { - if ($owner) { - $_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId()); - if (!in_array($_id, $banned_ids)) { - if ($owner instanceof User) { - $owner->ban("**content-noSpamTemplate-" . $log->getId() . "**", false, time() + $owner->getNewBanTime(), $this->user->id); - } else { - $owner->ban("Подозрительная активность"); + if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) { + foreach ($rows as $key => $object) { + $object = (array)$object; + $_obj = []; + foreach ($object as $key => $value) { + foreach ($columns as $column) { + if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) { + $value = "[BINARY]"; + break; } - - $banned_ids[] = $_id; } + + $_obj[$key] = $value; + $_obj["__model_name"] = $_model; } + $response[] = $_obj; + } + } else { + $ids = []; + + foreach ($rows as $object) { + $object = new $model_name($db->table($table)->get($object->id)); + if (!$object) continue; + $ids[] = $object->getId(); } - if (in_array((int)$this->postParam("ban"), [1, 3])) - $object->delete(); + $log = new NoSpamLog; + $log->setUser($this->user->id); + $log->setModel($_model); + if ($searchTerm) { + $log->setRegex($searchTerm); + } else { + $log->setRequest($where); + } + $log->setBan_Type((int)$this->postParam("ban")); + $log->setCount(count($rows)); + $log->setTime(time()); + $log->setItems(implode(",", $ids)); + $log->save(); + + $banned_ids = []; + foreach ($rows as $object) { + $object = new $model_name($db->table($table)->get($object->id)); + if (!$object) continue; + + $owner = NULL; + $methods = ["getOwner", "getUser", "getRecipient", "getInitiator"]; + + if (method_exists($object, "ban")) { + $owner = $object; + } else { + foreach ($methods as $method) { + if (method_exists($object, $method)) { + $owner = $object->$method(); + break; + } + } + } + + if ($owner instanceof User && $owner->getId() === $this->user->id) { + if (count($rows) === 1) { + $this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]); + } else { + continue; + } + } + + if (in_array((int)$this->postParam("ban"), [2, 3])) { + $reason = mb_strlen(trim($this->postParam("ban_reason"))) > 0 ? addslashes($this->postParam("ban_reason")) : ("**content-noSpamTemplate-" . $log->getId() . "**"); + $is_forever = (string)$this->postParam("is_forever") === "true"; + $unban_time = $is_forever ? 0 : (int)$this->postParam("unban_time") ?? NULL; + + if ($owner) { + $_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId()); + if (!in_array($_id, $banned_ids)) { + if ($owner instanceof User) { + if (!$unban_time && !$is_forever) + $unban_time = time() + $owner->getNewBanTime(); + + $owner->ban($reason, false, $unban_time, $this->user->id); + } else { + $owner->ban("Подозрительная активность"); + } + + $banned_ids[] = $_id; + } + } + } + + if (in_array((int)$this->postParam("ban"), [1, 3])) + $object->delete(); + } + + $processed++; } - - $processed++; } - } - $this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]); + $this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]); } catch (\Throwable $e) { $this->returnJson(["success" => false, "error" => $e->getMessage()]); } diff --git a/Web/Presenters/NotesPresenter.php b/Web/Presenters/NotesPresenter.php index 50437ad7..4b71c8b1 100644 --- a/Web/Presenters/NotesPresenter.php +++ b/Web/Presenters/NotesPresenter.php @@ -107,7 +107,7 @@ final class NotesPresenter extends OpenVKPresenter if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted()) $this->notFound(); if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity)) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->template->note = $note; if($_SERVER["REQUEST_METHOD"] === "POST") { @@ -135,11 +135,11 @@ final class NotesPresenter extends OpenVKPresenter if(!$note) $this->notFound(); if($note->getOwner()->getId() . "_" . $note->getId() !== $owner . "_" . $id || $note->isDeleted()) $this->notFound(); if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity)) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $name = $note->getName(); $note->delete(); - $this->flash("succ", "Заметка удалена", "Заметка \"$name\" была успешно удалена."); + $this->flash("succ", tr("note_is_deleted"), tr("note_x_is_now_deleted", $name)); $this->redirect("/notes" . $this->user->id); } } diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php index 345b2c60..0a8b87e4 100644 --- a/Web/Presenters/PhotosPresenter.php +++ b/Web/Presenters/PhotosPresenter.php @@ -1,6 +1,6 @@ notFound(); if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL)) $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); - $this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1); + $this->template->albums = $this->albums->getUserAlbums($user, (int)($this->queryParam("p") ?? 1)); $this->template->count = $this->albums->getUserAlbumsCount($user); $this->template->owner = $user; $this->template->canEdit = false; @@ -36,7 +36,7 @@ final class PhotosPresenter extends OpenVKPresenter } else { $club = (new Clubs)->get(abs($owner)); if(!$club) $this->notFound(); - $this->template->albums = $this->albums->getClubAlbums($club, $this->queryParam("p") ?? 1); + $this->template->albums = $this->albums->getClubAlbums($club, (int)($this->queryParam("p") ?? 1)); $this->template->count = $this->albums->getClubAlbumsCount($club); $this->template->owner = $club; $this->template->canEdit = false; @@ -46,7 +46,7 @@ final class PhotosPresenter extends OpenVKPresenter $this->template->paginatorConf = (object) [ "count" => $this->template->count, - "page" => $this->queryParam("p") ?? 1, + "page" => (int)($this->queryParam("p") ?? 1), "amount" => NULL, "perPage" => OPENVK_DEFAULT_PER_PAGE, ]; @@ -94,7 +94,7 @@ final class PhotosPresenter extends OpenVKPresenter if(!$album) $this->notFound(); if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound(); if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity) || $album->isDeleted()) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->template->album = $album; if($_SERVER["REQUEST_METHOD"] === "POST") { @@ -106,7 +106,7 @@ final class PhotosPresenter extends OpenVKPresenter $album->setEdited(time()); $album->save(); - $this->flash("succ", "Изменения сохранены", "Новые данные приняты."); + $this->flash("succ", tr("changes_saved"), tr("new_data_accepted")); } } @@ -120,13 +120,13 @@ final class PhotosPresenter extends OpenVKPresenter if(!$album) $this->notFound(); if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound(); if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $name = $album->getName(); $owner = $album->getOwner(); $album->delete(); - $this->flash("succ", "Альбом удалён", "Альбом $name был успешно удалён."); + $this->flash("succ", tr("album_is_deleted"), tr("album_x_is_deleted", $name)); $this->redirect("/albums" . ($owner instanceof Club ? "-" : "") . $owner->getId()); } @@ -147,7 +147,7 @@ final class PhotosPresenter extends OpenVKPresenter $this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1), 20) ); $this->template->paginatorConf = (object) [ "count" => $album->getPhotosCount(), - "page" => $this->queryParam("p") ?? 1, + "page" => (int)($this->queryParam("p") ?? 1), "amount" => sizeof($this->template->photos), "perPage" => 20, "atBottom" => true @@ -205,13 +205,13 @@ final class PhotosPresenter extends OpenVKPresenter $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); if(!$photo) $this->notFound(); if(is_null($this->user) || $this->user->id != $ownerId) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); if($_SERVER["REQUEST_METHOD"] === "POST") { $photo->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); $photo->save(); - $this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с фоткой."); + $this->flash("succ", tr("changes_saved"), tr("new_description_will_appear")); $this->redirect("/photo" . $photo->getPrettyId()); } @@ -221,39 +221,82 @@ final class PhotosPresenter extends OpenVKPresenter function renderUploadPhoto(): void { $this->assertUserLoggedIn(); - $this->willExecuteWriteAction(); - - if(is_null($this->queryParam("album"))) - $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED."); - - [$owner, $id] = explode("_", $this->queryParam("album")); - $album = $this->albums->get((int) $id); + $this->willExecuteWriteAction(true); + + if(is_null($this->queryParam("album"))) { + $album = $this->albums->getUserWallAlbum($this->user->identity); + } else { + [$owner, $id] = explode("_", $this->queryParam("album")); + $album = $this->albums->get((int) $id); + } + if(!$album) - $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в DELETED."); - if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true); + + # Для быстрой загрузки фоток из пикера фотографий нужен альбом, но юзер не может загружать фото + # в системные альбомы, так что так. + if(is_null($this->user) || !is_null($this->queryParam("album")) && !$album->canBeModifiedBy($this->user->identity)) + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), 500, true); if($_SERVER["REQUEST_METHOD"] === "POST") { - if(!isset($_FILES["blob"])) - $this->flashFail("err", "Нету фотографии", "Выберите файл."); - - try { - $photo = new Photo; - $photo->setOwner($this->user->id); - $photo->setDescription($this->postParam("desc")); - $photo->setFile($_FILES["blob"]); - $photo->setCreated(time()); - $photo->save(); - } catch(ISE $ex) { - $name = $album->getName(); - $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name."); - } - - $album->addPhoto($photo); - $album->setEdited(time()); - $album->save(); + if($this->queryParam("act") == "finish") { + $result = json_decode($this->postParam("photos"), true); + + foreach($result as $photoId => $description) { + $phot = $this->photos->get($photoId); - $this->redirect("/photo" . $photo->getPrettyId() . "?from=album" . $album->getId()); + if(!$phot || $phot->isDeleted() || $phot->getOwner()->getId() != $this->user->id) + continue; + + if(iconv_strlen($description) > 255) + $this->flashFail("err", tr("error"), tr("description_too_long"), 500, true); + + $phot->setDescription($description); + $phot->save(); + + $album = $phot->getAlbum(); + } + + $this->returnJson(["success" => true, + "album" => $album->getId(), + "owner" => $album->getOwner() instanceof User ? $album->getOwner()->getId() : $album->getOwner()->getId() * -1]); + } + + if(!isset($_FILES)) + $this->flashFail("err", tr("no_photo"), tr("select_file"), 500, true); + + $photos = []; + if((int)$this->postParam("count") > 10) + $this->flashFail("err", tr("no_photo"), "ты еблан", 500, true); + + for($i = 0; $i < $this->postParam("count"); $i++) { + try { + $photo = new Photo; + $photo->setOwner($this->user->id); + $photo->setDescription(""); + $photo->setFile($_FILES["photo_".$i]); + $photo->setCreated(time()); + $photo->save(); + + $photos[] = [ + "url" => $photo->getURLBySizeId("tiny"), + "id" => $photo->getId(), + "vid" => $photo->getVirtualId(), + "owner" => $photo->getOwner()->getId(), + "link" => $photo->getURL() + ]; + } catch(ISE $ex) { + $name = $album->getName(); + $this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name.", 500, true); + } + + $album->addPhoto($photo); + $album->setEdited(time()); + $album->save(); + } + + $this->returnJson(["success" => true, + "photos" => $photos]); } else { $this->template->album = $album; } @@ -269,7 +312,7 @@ final class PhotosPresenter extends OpenVKPresenter if(!$album || !$photo) $this->notFound(); if(!$album->hasPhoto($photo)) $this->notFound(); if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity)) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); if($_SERVER["REQUEST_METHOD"] === "POST") { $this->assertNoCSRF(); @@ -277,7 +320,7 @@ final class PhotosPresenter extends OpenVKPresenter $album->setEdited(time()); $album->save(); - $this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена."); + $this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc")); $this->redirect("/album" . $album->getPrettyId()); } } @@ -285,18 +328,23 @@ final class PhotosPresenter extends OpenVKPresenter function renderDeletePhoto(int $ownerId, int $photoId): void { $this->assertUserLoggedIn(); - $this->willExecuteWriteAction(); + $this->willExecuteWriteAction($_SERVER["REQUEST_METHOD"] === "POST"); $this->assertNoCSRF(); $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); if(!$photo) $this->notFound(); if(is_null($this->user) || $this->user->id != $ownerId) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); - + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); + + $redirect = $photo->getAlbum()->getOwner() instanceof User ? "/id0" : "/club" . $ownerId; + $photo->isolate(); $photo->delete(); - $this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена."); - $this->redirect("/id0"); + if($_SERVER["REQUEST_METHOD"] === "POST") + $this->returnJson(["success" => true]); + + $this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc")); + $this->redirect($redirect); } } diff --git a/Web/Presenters/ReportPresenter.php b/Web/Presenters/ReportPresenter.php index 68d27861..a87154c8 100644 --- a/Web/Presenters/ReportPresenter.php +++ b/Web/Presenters/ReportPresenter.php @@ -118,22 +118,22 @@ final class ReportPresenter extends OpenVKPresenter $report->deleteContent(); $report->banUser($this->user->identity->getId()); - $this->flash("suc", "Смэрть...", "Пользователь успешно забанен."); + $this->flash("suc", tr("death"), tr("user_successfully_banned")); } else if ($this->postParam("delete")) { $report->deleteContent(); - $this->flash("suc", "Нехай живе!", "Контент удалён, а пользователю прилетело предупреждение."); + $this->flash("suc", tr("nehay"), tr("content_is_deleted")); } else if ($this->postParam("ignore")) { $report->delete(); - $this->flash("suc", "Нехай живе!", "Жалоба проигнорирована."); + $this->flash("suc", tr("nehay"), tr("report_is_ignored")); } else if ($this->postParam("banClubOwner") || $this->postParam("banClub")) { if ($report->getContentType() !== "group") - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $club = $report->getContentObject(); if (!$club || $club->isBanned()) - $this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); if ($this->postParam("banClubOwner")) { $club->getOwner()->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**", false, $club->getOwner()->getNewBanTime(), $this->user->identity->getId()); @@ -143,7 +143,7 @@ final class ReportPresenter extends OpenVKPresenter $report->delete(); - $this->flash("suc", "Смэрть...", ($this->postParam("banClubOwner") ? "Создатель сообщества успешно забанен." : "Сообщество успешно забанено")); + $this->flash("suc", tr("death"), ($this->postParam("banClubOwner") ? tr("group_owner_is_banned") : tr("group_is_banned"))); } $this->redirect("/scumfeed"); diff --git a/Web/Presenters/TopicsPresenter.php b/Web/Presenters/TopicsPresenter.php index e7b08ac3..92d67e84 100644 --- a/Web/Presenters/TopicsPresenter.php +++ b/Web/Presenters/TopicsPresenter.php @@ -111,7 +111,7 @@ final class TopicsPresenter extends OpenVKPresenter $video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]); } } catch(ISE $ex) { - $this->flash("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик."); + $this->flash("err", tr("error_when_publishing_comment"), tr("error_comment_file_too_big")); $this->redirect("/topic" . $topic->getPrettyId()); } @@ -126,7 +126,7 @@ final class TopicsPresenter extends OpenVKPresenter $comment->setFlags($flags); $comment->save(); } catch (\LengthException $ex) { - $this->flash("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой."); + $this->flash("err", tr("error_when_publishing_comment"), tr("error_comment_too_big")); $this->redirect("/topic" . $topic->getPrettyId()); } diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 9cfa3654..51ddc6aa 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -72,7 +72,7 @@ final class UserPresenter extends OpenVKPresenter if(!is_null($this->user)) { if($this->template->mode !== "friends" && $this->user->id !== $id) { $name = $user->getFullName(); - $this->flash("err", "Ошибка доступа", "Вы не можете просматривать полный список подписок $name."); + $this->flash("err", tr("error_access_denied_short"), tr("error_viewing_subs", $name)); $this->redirect($user->getURL()); } @@ -107,11 +107,11 @@ final class UserPresenter extends OpenVKPresenter $this->notFound(); if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) - $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.", NULL, true); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), NULL, true); $isClubPinned = $this->user->identity->isClubPinned($club); if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10) - $this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп", NULL, true); + $this->flashFail("err", tr("error"), tr("error_max_pinned_clubs"), NULL, true); if($club->getOwner()->getId() === $this->user->identity->getId()) { $club->setOwner_Club_Pinned(!$isClubPinned); @@ -237,7 +237,7 @@ final class UserPresenter extends OpenVKPresenter } elseif($_GET['act'] === "status") { if(mb_strlen($this->postParam("status")) > 255) { $statusLength = (string) mb_strlen($this->postParam("status")); - $this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)", NULL, true); + $this->flashFail("err", tr("error"), tr("error_status_too_long", $statusLength), NULL, true); } $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); @@ -281,7 +281,7 @@ final class UserPresenter extends OpenVKPresenter if($_SERVER["REQUEST_METHOD"] === "POST") { if(!$user->verifyNumber($this->postParam("code") ?? 0)) - $this->flashFail("err", "Ошибка", "Не удалось подтвердить номер телефона: неверный код."); + $this->flashFail("err", tr("error"), tr("invalid_code")); $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); } @@ -481,6 +481,7 @@ final class UserPresenter extends OpenVKPresenter "menu_novajoj" => "news", "menu_ligiloj" => "links", "menu_standardo" => "poster", + "menu_aplikoj" => "apps" ]; foreach($settings as $checkbox => $setting) $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); diff --git a/Web/Presenters/VideosPresenter.php b/Web/Presenters/VideosPresenter.php index 4e4d484a..9d2fddc6 100644 --- a/Web/Presenters/VideosPresenter.php +++ b/Web/Presenters/VideosPresenter.php @@ -58,7 +58,7 @@ final class VideosPresenter extends OpenVKPresenter $this->willExecuteWriteAction(); if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) - $this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator."); + $this->flashFail("err", tr("error"), tr("video_uploads_disabled")); if($_SERVER["REQUEST_METHOD"] === "POST") { if(!empty($this->postParam("name"))) { @@ -74,18 +74,18 @@ final class VideosPresenter extends OpenVKPresenter else if(!empty($this->postParam("link"))) $video->setLink($this->postParam("link")); else - $this->flashFail("err", "Нету видеозаписи", "Выберите файл или укажите ссылку."); + $this->flashFail("err", tr("no_video"), tr("no_video_desc")); } catch(\DomainException $ex) { - $this->flashFail("err", "Произошла ошибка", "Файл повреждён или не содержит видео." ); + $this->flashFail("err", tr("error_occured"), tr("error_video_damaged_file")); } catch(ISE $ex) { - $this->flashFail("err", "Произошла ошибка", "Возможно, ссылка некорректна."); + $this->flashFail("err", tr("error_occured"), tr("error_video_incorrect_link")); } $video->save(); $this->redirect("/video" . $video->getPrettyId()); } else { - $this->flashFail("err", "Произошла ошибка", "Видео не может быть опубликовано без названия."); + $this->flashFail("err", tr("error_occured"), tr("error_video_no_title")); } } } @@ -99,14 +99,14 @@ final class VideosPresenter extends OpenVKPresenter if(!$video) $this->notFound(); if(is_null($this->user) || $this->user->id !== $owner) - $this->flashFail("err", "Ошибка доступа", "Вы не имеете права редактировать этот ресурс."); + $this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); if($_SERVER["REQUEST_METHOD"] === "POST") { $video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name")); $video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); $video->save(); - $this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с видосиком."); + $this->flash("succ", tr("changes_saved"), tr("new_data_video")); $this->redirect("/video" . $video->getPrettyId()); } @@ -128,7 +128,7 @@ final class VideosPresenter extends OpenVKPresenter $video->deleteVideo($owner, $vid); } } else { - $this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт."); + $this->flashFail("err", tr("error_deleting_video"), tr("login_please")); } $this->redirect("/videos" . $owner); diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index 9ea18453..39bc86a3 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -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, NewSuggestedPostsNotification, RepostNotification, WallPostNotification}; -use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes}; +use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos}; use Chandler\Database\DatabaseConnection; use Nette\InvalidStateException as ISE; use Bhaktaraz\RSSGenerator\Item; @@ -253,10 +253,7 @@ final class WallPresenter extends OpenVKPresenter if(!$canPost) $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); - - if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) - $this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator."); - + $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) { $manager = $wallOwner->getManager($this->user->identity); @@ -274,23 +271,23 @@ final class WallPresenter extends OpenVKPresenter if($this->postParam("force_sign") === "on") $flags |= 0b01000000; - try { - $photo = NULL; - $video = NULL; - if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { - $album = NULL; - if(!$anon && $wall > 0 && $wall === $this->user->id) - $album = (new Albums)->getUserWallAlbum($wallOwner); - - $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album, $anon); + $photos = []; + + if(!empty($this->postParam("photos"))) { + $un = rtrim($this->postParam("photos"), ","); + $arr = explode(",", $un); + + if(sizeof($arr) < 11) { + foreach($arr as $dat) { + $ids = explode("_", $dat); + $photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]); + + if(!$photo || $photo->isDeleted()) + continue; + + $photos[] = $photo; + } } - - if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) - $video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"], $anon); - } catch(\DomainException $ex) { - $this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted")); - } catch(ISE $ex) { - $this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large")); } try { @@ -317,8 +314,27 @@ final class WallPresenter extends OpenVKPresenter $this->flashFail("err", " "); } } + + $videos = []; + + if(!empty($this->postParam("videos"))) { + $un = rtrim($this->postParam("videos"), ","); + $arr = explode(",", $un); + + if(sizeof($arr) < 11) { + foreach($arr as $dat) { + $ids = explode("_", $dat); + $video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]); + + if(!$video || $video->isDeleted()) + continue; + + $videos[] = $video; + } + } + } - if(empty($this->postParam("text")) && !$photo && !$video && !$poll && !$note) + if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && !$poll && !$note) $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big")); try { @@ -339,11 +355,12 @@ final class WallPresenter extends OpenVKPresenter $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big")); } - if(!is_null($photo)) - $post->attach($photo); + foreach($photos as $photo) + $post->attach($photo); - if(!is_null($video)) - $post->attach($video); + if(sizeof($videos) > 0) + foreach($videos as $vid) + $post->attach($vid); if(!is_null($poll)) $post->attach($poll); @@ -547,4 +564,64 @@ 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", + "new_text" => $post->getText(false), + "author" => [ + "name" => $post->getOwner()->getCanonicalName(), + "avatar" => $post->getOwner()->getAvatarUrl() + ]]); + } } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 577ecdda..510fb7a7 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -38,9 +38,8 @@

- Вы вошли как {$thisUser->getCanonicalName()}. Пожалуйста, уважайте - право на тайну переписки других людей и не злоупотребляйте подменой пользователя. - Нажмите здесь, чтобы выйти. + {_you_entered_as} {$thisUser->getCanonicalName()}. {_please_rights} + {_click_on} {_there}, {_to_leave}.

@@ -176,7 +175,7 @@ {_my_page} {_my_friends} - + ({$thisUser->getFollowersCount()}) @@ -196,7 +195,7 @@ ({$thisUser->getNotificationsCount()}) {/if} - {_my_apps} + {_my_apps} {_my_settings} {var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)} @@ -310,7 +309,7 @@ {ifset $thisUser} - {if !$thisUser->isBanned()} + {if !$thisUser->isBanned() && !$thisUser->isDeleted()} {/if} {/ifset} diff --git a/Web/Presenters/templates/About/BB.xml b/Web/Presenters/templates/About/BB.xml index 17ab3b0f..9aa745b6 100644 --- a/Web/Presenters/templates/About/BB.xml +++ b/Web/Presenters/templates/About/BB.xml @@ -1,12 +1,10 @@ {extends "../@layout.xml"} -{block title}Ваш браузер устарел{/block} +{block title}{_deprecated_browser}{/block} {block header} - Устаревший браузер + {_deprecated_browser} {/block} {block content} - Для просмотра этого контента вам понадобится Firefox ESR 52+ или - эквивалентный по функционалу навигатор по всемирной сети интернет.
- Сожалеем об этом. + {_deprecated_browser_description} {/block} diff --git a/Web/Presenters/templates/About/Help.xml b/Web/Presenters/templates/About/Help.xml index 060f1381..64be74a0 100644 --- a/Web/Presenters/templates/About/Help.xml +++ b/Web/Presenters/templates/About/Help.xml @@ -9,5 +9,5 @@
Для кого этот сайт?
Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.
Я попозже допишу ок ~~ veselcraft - 12.01.2020 - 22:05 GMT+3 - + Давай {/block} diff --git a/Web/Presenters/templates/About/Sandbox.xml b/Web/Presenters/templates/About/Sandbox.xml index b84b515a..1b548a48 100644 --- a/Web/Presenters/templates/About/Sandbox.xml +++ b/Web/Presenters/templates/About/Sandbox.xml @@ -2,7 +2,7 @@ {block title}Sandbox{/block} {block header} - Sandbox для разработчиков + {_sandbox_for_developers} {/block} {block content} diff --git a/Web/Presenters/templates/Admin/BansHistory.xml b/Web/Presenters/templates/Admin/BansHistory.xml index c0dc1b64..2144c949 100644 --- a/Web/Presenters/templates/Admin/BansHistory.xml +++ b/Web/Presenters/templates/Admin/BansHistory.xml @@ -1,7 +1,7 @@ {extends "./@layout.xml"} {block title} - История блокировок + {_bans_history} {/block} {block heading} @@ -13,13 +13,13 @@ ID - Забаненный - Инициатор - Начало - Конец - Время - Причина - Снята + {_bans_history_blocked} + {_bans_history_initiator} + {_bans_history_start} + {_bans_history_end} + {_bans_history_time} + {_bans_history_reason} + {_bans_history_removed} @@ -77,7 +77,7 @@ {_admin_banned} {else} - Активная блокировка + {_bans_history_active} {/if} diff --git a/Web/Presenters/templates/Admin/Logs.xml b/Web/Presenters/templates/Admin/Logs.xml index d953a378..ab5e62f5 100644 --- a/Web/Presenters/templates/Admin/Logs.xml +++ b/Web/Presenters/templates/Admin/Logs.xml @@ -1,11 +1,11 @@ {extends "@layout.xml"} {block title} - Логи + {_logs} {/block} {block heading} - Логи + {_logs} {/block} {block content} @@ -18,23 +18,23 @@
- + + + + + - - + +
- + - +
@@ -42,11 +42,11 @@ ID - Пользователь - Объект - Тип - Изменения - Время + {_logs_user} + {_logs_object} + {_logs_type} + {_logs_changes} + {_logs_time} diff --git a/Web/Presenters/templates/Apps/Play.xml b/Web/Presenters/templates/Apps/Play.xml index facaa273..93637a2d 100644 --- a/Web/Presenters/templates/Apps/Play.xml +++ b/Web/Presenters/templates/Apps/Play.xml @@ -7,7 +7,7 @@ {block header} {$name} - Пожаловаться + {_report} {/block} {block content} @@ -37,20 +37,20 @@ - {_open_original}
{/block} diff --git a/Web/Presenters/templates/Photos/UnlinkPhoto.xml b/Web/Presenters/templates/Photos/UnlinkPhoto.xml index 54498e06..80a4ad2e 100644 --- a/Web/Presenters/templates/Photos/UnlinkPhoto.xml +++ b/Web/Presenters/templates/Photos/UnlinkPhoto.xml @@ -1,20 +1,20 @@ {extends "../@layout.xml"} -{block title}Удалить фотографию?{/block} +{block title}{_delete_photo}{/block} {block header} - Удаление фотографии + {_delete_photo} {/block} {block content} - Вы уверены что хотите удалить эту фотографию? + {_sure_deleting_photo}

- Нет + {_no}   - +
{/block} diff --git a/Web/Presenters/templates/Photos/UploadPhoto.xml b/Web/Presenters/templates/Photos/UploadPhoto.xml index 9876e5b9..ab81d3aa 100644 --- a/Web/Presenters/templates/Photos/UploadPhoto.xml +++ b/Web/Presenters/templates/Photos/UploadPhoto.xml @@ -2,9 +2,13 @@ {block title}{_upload_photo}{/block} {block header} - {$thisUser->getCanonicalName()} + {$album->getOwner()->getCanonicalName()} » - {_albums} + {if $album->getOwner() instanceof openvk\Web\Models\Entities\Club} + {_albums} + {else} + {_albums} + {/if} » {$album->getName()} » @@ -12,32 +16,53 @@ {/block} {block content} -
- - - - - - - - - - - - - - - -
{_description}:
{_photo}: - -
-
- - -
- - -
+
+
+ {_edit_album} +
+
+ {_add_photos} +
+
+ + + +
+
+
+
+

{_uploading_photos_from_computer}

+ +
+ {_admin_limits} +
    +
  • {_supported_formats}
  • +
  • {_max_load_photos}
  • +
+ +
+ +
+ +
+ {_tip}: {_tip_ctrl} +
+
+
+
+ +
+ + +
+ + + + +{/block} + +{block bodyScripts} + {script "js/al_photos.js"} {/block} diff --git a/Web/Presenters/templates/Report/ViewContent.xml b/Web/Presenters/templates/Report/ViewContent.xml index 3928495f..249e6b38 100644 --- a/Web/Presenters/templates/Report/ViewContent.xml +++ b/Web/Presenters/templates/Report/ViewContent.xml @@ -16,7 +16,7 @@ {elseif $type == "group" || $type == "user"} {include "../components/group.xml", group => $object, isUser => $type == "user"} {elseif $type == "comment"} - {include "../components/comment.xml", comment => $object, timeOnly => true} + {include "../components/comment.xml", comment => $object, timeOnly => true, linkWithPost => true} {elseif $type == "note"} {include "./content/note.xml", note => $object} {elseif $type == "app"} diff --git a/Web/Presenters/templates/User/Edit.xml b/Web/Presenters/templates/User/Edit.xml index 6df070ea..b8e8398c 100644 --- a/Web/Presenters/templates/User/Edit.xml +++ b/Web/Presenters/templates/User/Edit.xml @@ -344,9 +344,9 @@
- +
- +
diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index e61f900d..c8f0f61b 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -190,13 +190,13 @@ {if $graffiti} diff --git a/Web/Presenters/templates/components/video.xml b/Web/Presenters/templates/components/video.xml index 33961c4f..f568e942 100644 --- a/Web/Presenters/templates/components/video.xml +++ b/Web/Presenters/templates/components/video.xml @@ -5,8 +5,10 @@
- +
+ +
@@ -33,5 +35,5 @@ - {/block} diff --git a/Web/Util/Makima/Makima.php b/Web/Util/Makima/Makima.php new file mode 100644 index 00000000..e31bfa42 --- /dev/null +++ b/Web/Util/Makima/Makima.php @@ -0,0 +1,305 @@ +photos = $photos; + } + + private function getOrientation(Photo $photo, &$ratio): int + { + [$width, $height] = $photo->getDimensions(); + $ratio = $width / $height; + if($ratio >= 1.2) + return Makima::ORIENT_WIDE; + else if($ratio >= 0.8) + return Makima::ORIENT_REGULAR; + else + return Makima::ORIENT_SLIM; + } + + private function calculateMultiThumbsHeight(array $ratios, float $w, float $m): float + { + return ($w - (sizeof($ratios) - 1) * $m) / array_sum($ratios); + } + + private function extractSubArr(array $arr, int $from, int $to): array + { + return array_slice($arr, $from, sizeof($arr) - $from - (sizeof($arr) - $to)); + } + + function computeMasonryLayout(float $maxWidth, float $maxHeight): MasonryLayout + { + $orients = []; + $ratios = []; + $count = sizeof($this->photos); + $result = new MasonryLayout; + + foreach($this->photos as $photo) { + $orients[] = $this->getOrientation($photo, $ratio); + $ratios[] = $ratio; + } + + $avgRatio = array_sum($ratios) / sizeof($ratios); + if($maxWidth < 0) + $maxWidth = $maxHeight = 510; + + $maxRatio = $maxWidth / $maxHeight; + $marginWidth = $marginHeight = 2; + + switch($count) { + case 2: + if( + $orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE] # two wide pics + && $avgRatio > (1.4 * $maxRatio) && abs($ratios[0] - $ratios[1]) < 0.2 # that can be positioned on top of each other + ) { + $computedHeight = ceil( min( $maxWidth / $ratios[0], min( $maxWidth / $ratios[1], ($maxHeight - $marginHeight) / 2 ) ) ); + + $result->colSizes = [1]; + $result->rowSizes = [1, 1]; + $result->width = ceil($maxWidth); + $result->height = $computedHeight; + $result->tiles = [new ThumbTile(1, 1, $maxWidth, $computedHeight), new ThumbTile(1, 1, $maxWidth, $computedHeight)]; + } else if( + $orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE] + || $orients == [Makima::ORIENT_REGULAR, Makima::ORIENT_REGULAR] # two normal pics of same ratio + ) { + $computedWidth = ($maxWidth - $marginWidth) / 2; + $height = min( $computedWidth / $ratios[0], min( $computedWidth / $ratios[1], $maxHeight ) ); + + $result->colSizes = [1, 1]; + $result->rowSizes = [1]; + $result->width = ceil($maxWidth); + $result->height = ceil($height); + $result->tiles = [new ThumbTile(1, 1, $computedWidth, $height), new ThumbTile(1, 1, $computedWidth, $height)]; + } else /* next to each other, different ratios */ { + $w0 = ( + ($maxWidth - $marginWidth) / $ratios[1] / ( (1 / $ratios[0]) + (1 / $ratios[1]) ) + ); + $w1 = $maxWidth - $w0 - $marginWidth; + $h = min($maxHeight, min($w0 / $ratios[0], $w1 / $ratios[1])); + + $result->colSizes = [ceil($w0), ceil($w1)]; + $result->rowSizes = [1]; + $result->width = ceil($w0 + $w1 + $marginWidth); + $result->height = ceil($h); + $result->tiles = [new ThumbTile(1, 1, $w0, $h), new ThumbTile(1, 1, $w1, $h)]; + } + break; + case 3: + # Three wide photos, we will put two of them below and one on top + if($orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]) { + $hCover = min($maxWidth / $ratios[0], ($maxHeight - $marginHeight) * (2 / 3)); + $w2 = ($maxWidth - $marginWidth) / 2; + $h = min($maxHeight - $hCover - $marginHeight, min($w2 / $ratios[1], $w2 / $ratios[2])); + + $result->colSizes = [1, 1]; + $result->rowSizes = [ceil($hCover), ceil($h)]; + $result->width = ceil($maxWidth); + $result->height = ceil($marginHeight + $hCover + $h); + $result->tiles = [ + new ThumbTile(2, 1, $maxWidth, $hCover), + new ThumbTile(1, 1, $w2, $h), new ThumbTile(1, 1, $w2, $h), + ]; + } else /* Photos have different sizes or are not wide, so we will put one to left and two to the right */ { + $wCover = min($maxHeight * $ratios[0], ($maxWidth - $marginWidth) * (3 / 4)); + $h1 = ($ratios[1] * ($maxHeight - $marginHeight) / ($ratios[2] + $ratios[1])); + $h0 = $maxHeight - $marginHeight - $h1; + $w = min($maxWidth - $marginWidth - $wCover, min($h1 * $ratios[2], $h0 * $ratios[1])); + + $result->colSizes = [ceil($wCover), ceil($w)]; + $result->rowSizes = [ceil($h0), ceil($h1)]; + $result->width = ceil($w + $wCover + $marginWidth); + $result->height = ceil($maxHeight); + $result->tiles = [ + new ThumbTile(1, 2, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0), + new ThumbTile(1, 1, $w, $h1), + ]; + } + break; + case 4: + # Four wide photos, we will put one to the top and rest below + if($orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]) { + $hCover = min($maxWidth / $ratios[0], ($maxHeight - $marginHeight) / (2 / 3)); + $h = ($maxWidth - 2 * $marginWidth) / (array_sum($ratios) - $ratios[0]); + $w0 = $h * $ratios[1]; + $w1 = $h * $ratios[2]; + $w2 = $h * $ratios[3]; + $h = min($maxHeight - $marginHeight - $hCover, $h); + + $result->colSizes = [ceil($w0), ceil($w1), ceil($w2)]; + $result->rowSizes = [ceil($hCover), ceil($h)]; + $result->width = ceil($maxWidth); + $result->height = ceil($hCover + $marginHeight + $h); + $result->tiles = [ + new ThumbTile(3, 1, $maxWidth, $hCover), + new ThumbTile(1, 1, $w0, $h), new ThumbTile(1, 1, $w1, $h), new ThumbTile(1, 1, $w2, $h), + ]; + } else /* Four photos, we will put one to the left and rest to the right */ { + $wCover = min($maxHeight * $ratios[0], ($maxWidth - $marginWidth) * (2 / 3)); + $w = ($maxHeight - 2 * $marginHeight) / (1 / $ratios[1] + 1 / $ratios[2] + 1 / $ratios[3]); + $h0 = $w / $ratios[1]; + $h1 = $w / $ratios[2]; + $h2 = $w / $ratios[3] + $marginHeight; + $w = min($w, $maxWidth - $marginWidth - $wCover); + + $result->colSizes = [ceil($wCover), ceil($w)]; + $result->rowSizes = [ceil($h0), ceil($h1), ceil($h2)]; + $result->width = ceil($wCover + $marginWidth + $w); + $result->height = ceil($maxHeight); + $result->tiles = [ + new ThumbTile(1, 3, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0), + new ThumbTile(1, 1, $w, $h1), + new ThumbTile(1, 1, $w, $h1), + ]; + } + break; + default: + // как лопать пузырики + $ratiosCropped = []; + if($avgRatio > 1.1) { + foreach($ratios as $ratio) + $ratiosCropped[] = max($ratio, 1.0); + } else { + foreach($ratios as $ratio) + $ratiosCropped[] = min($ratio, 1.0); + } + + $tries = []; + + $firstLine; + $secondLine; + $thirdLine; + + # Try one line: + $tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)]; + + # Try two lines: + for($firstLine = 1; $firstLine < ($count - 1); $firstLine++) { + $secondLine = $count - $firstLine; + $key = "$firstLine&$secondLine"; + $tries[$key] = [ + $this->calculateMultiThumbsHeight(array_slice($ratiosCropped, 0, $firstLine), $maxWidth, $marginWidth), + $this->calculateMultiThumbsHeight(array_slice($ratiosCropped, $firstLine), $maxWidth, $marginWidth), + ]; + } + + # Try three lines: + for($firstLine = 1; $firstLine < ($count - 2); $firstLine++) { + for($secondLine = 1; $secondLine < ($count - $firstLine - 1); $secondLine++) { + $thirdLine = $count - $firstLine - $secondLine; + $key = "$firstLine&$secondLine&$thirdLine"; + $tries[$key] = [ + $this->calculateMultiThumbsHeight(array_slice($ratiosCropped, 0, $firstLine), $maxWidth, $marginWidth), + $this->calculateMultiThumbsHeight($this->extractSubArr($ratiosCropped, $firstLine, $firstLine + $secondLine), $maxWidth, $marginWidth), + $this->calculateMultiThumbsHeight($this->extractSubArr($ratiosCropped, $firstLine + $secondLine, sizeof($ratiosCropped)), $maxWidth, $marginWidth), + ]; + } + } + + # Now let's find the most optimal configuration: + $optimalConfiguration = $optimalDifference = NULL; + foreach($tries as $config => $heights) { + $config = explode('&', (string) $config); # да да стринговые ключи пхп даже со стриктайпами автокастует к инту (см. 187) + $confH = $marginHeight * (sizeof($heights) - 1); + foreach($heights as $h) + $confH += $h; + + $confDiff = abs($confH - $maxHeight); + if(sizeof($config) > 1) + if($config[0] > $config[1] || sizeof($config) >= 2 && $config[1] > $config[2]) + $confDiff *= 1.1; + + if(!$optimalConfiguration || $confDigff < $optimalDifference) { + $optimalConfiguration = $config; + $optimalDifference = $confDiff; + } + } + + $thumbsRemain = $this->photos; + $ratiosRemain = $ratiosCropped; + $optHeights = $tries[implode('&', $optimalConfiguration)]; + $k = 0; + + $result->width = ceil($maxWidth); + $result->rowSizes = [sizeof($optHeights)]; + $result->tiles = []; + + $totalHeight = 0.0; + $gridLineOffsets = []; + $rowTiles = []; // vector> + + for($i = 0; $i < sizeof($optimalConfiguration); $i++) { + $lineChunksNum = $optimalConfiguration[$i]; + $lineThumbs = []; + for($j = 0; $j < $lineChunksNum; $j++) + $lineThumbs[] = array_shift($thumbsRemain); + + $lineHeight = $optHeights[$i]; + $totalHeight += $lineHeight; + + $result->rowSizes[$i] = ceil($lineHeight); + + $totalWidth = 0; + $row = []; + for($j = 0; $j < sizeof($lineThumbs); $j++) { + $thumbRatio = array_shift($ratiosRemain); + if($j == sizeof($lineThumbs) - 1) + $w = $maxWidth - $totalWidth; + else + $w = $thumbRatio * $lineHeight; + + $totalWidth += ceil($w); + if($j < (sizeof($lineThumbs) - 1) && !in_array($totalWidth, $gridLineOffsets)) + $gridLineOffsets[] = $totalWidth; + + $tile = new ThumbTile(1, 1, $w, $lineHeight); + $result->tiles[$k++] = $row[] = $tile; + } + + $rowTiles[] = $row; + } + + sort($gridLineOffsets, SORT_NUMERIC); + $gridLineOffsets[] = $maxWidth; + + $result->colSizes = [$gridLineOffsets[0]]; + for($i = sizeof($gridLineOffsets) - 1; $i > 0; $i--) + $result->colSizes[$i] = $gridLineOffsets[$i] - $gridLineOffsets[$i - 1]; + + foreach($rowTiles as $row) { + $columnOffset = 0; + foreach($row as $tile) { + $startColumn = $columnOffset; + $width = 0; + $tile->colSpan = 0; + for($i = $startColumn; $i < sizeof($result->colSizes); $i++) { + $width += $result->colSizes[$i]; + $tile->colSpan++; + if($width == $tile->width) + break; + } + + $columnOffset += $tile->colSpan; + } + } + + $result->height = ceil($totalHeight + $marginHeight * (sizeof($optHeights) - 1)); + break; + } + + return $result; + } +} diff --git a/Web/Util/Makima/MasonryLayout.php b/Web/Util/Makima/MasonryLayout.php new file mode 100644 index 00000000..b23aa483 --- /dev/null +++ b/Web/Util/Makima/MasonryLayout.php @@ -0,0 +1,10 @@ +width, $this->height, $this->rowSpan, $this->colSpan] = [ceil($w), ceil($h), $rs, $cs]; + } +} diff --git a/Web/routes.yml b/Web/routes.yml index 92098790..5703fd28 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -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" @@ -371,6 +373,8 @@ routes: handler: "About->humansTxt" - url: "/dev" handler: "About->dev" + - url: "/iapi/getPhotosFromPost/{num}_{num}" + handler: "InternalAPI->getPhotosFromPost" - url: "/tour" handler: "About->tour" - url: "/{?shortCode}" diff --git a/Web/static/css/main.css b/Web/static/css/main.css index 540a14e9..30a4ccb8 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -29,6 +29,10 @@ a { cursor: pointer; } +.linkunderline:hover { + text-decoration: underline; +} + p { margin: 5px 0; } @@ -740,10 +744,14 @@ h4 { line-height: 130%; } -.post-content .attachments_b { +.post-content .attachments:first-of-type { margin-top: 8px; } +.post-content .attachments_m .attachment { + width: 98%; +} + .attachment .post { width: 102%; } @@ -753,6 +761,12 @@ h4 { image-rendering: -webkit-optimize-contrast; } +.post-content .media_makima { + width: calc(100% - 4px); + height: calc(100% - 4px); + object-fit: cover; +} + .post-signature { margin: 4px; margin-bottom: 2px; @@ -1462,6 +1476,12 @@ body.scrolled .toTop:hover { display: none; } +.post-has-videos { + margin-top: 11px; + margin-left: 3px; + color: #3c3c3c; +} + .post-upload::before, .post-has-poll::before, .post-has-note::before { content: " "; width: 8px; @@ -1473,6 +1493,28 @@ body.scrolled .toTop:hover { margin-left: 2px; } +.post-has-video { + padding-bottom: 4px; + cursor: pointer; +} + +.post-has-video:hover span { + text-decoration: underline; +} + +.post-has-video::before { + content: " "; + width: 14px; + height: 15px; + display: inline-block; + vertical-align: bottom; + background-image: url("/assets/packages/static/openvk/img/video.png"); + background-repeat: no-repeat; + margin: 3px; + margin-left: 2px; + margin-bottom: -1px; +} + .post-opts { margin-top: 10px; } @@ -2247,6 +2289,124 @@ a.poll-retract-vote { border-radius: 1px; } +.progress { + border: 1px solid #eee; + height: 15px; + background: linear-gradient(to bottom, #fefefe, #fafafa); +} + +.progress .progress-bar { + background: url('progress.png'); + background-repeat: repeat-x; + height: 15px; + animation-name: progress; + animation-duration: 1s; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +@keyframes progress { + from { + background-position: 0; + } + + to { + background-position: 20px; + } +} + +.upload { + margin-top: 8px; +} + +.upload .upload-item { + width: 75px; + height: 60px; + overflow: hidden; + display: inline-block; + margin-right: 3px; +} + +.upload-item .upload-delete { + position: absolute; + background: rgba(0,0,0,0.5); + padding: 2px 5px; + text-decoration: none; + color: #fff; + font-size: 11px; + margin-left: 57px; /* мне лень переделывать :DDDD */ + opacity: 0; + transition: 0.25s; +} + +.upload-item:hover > .upload-delete { + opacity: 1; +} + +.upload-item img { + width: 100%; + max-height: 60px; + object-fit: cover; + border-radius: 3px; +} + +/* https://imgur.com/a/ihB3JZ4 */ + +.ovk-photo-view-dimmer { + position: fixed; + left: 0px; + top: 0px; + right: 0px; + bottom: 0px; + overflow: auto; + padding-bottom: 20px; + z-index: 300; +} + +.ovk-photo-view { + position: relative; + z-index: 999; + background: #fff; + width: 610px; + padding: 20px; + padding-top: 15px; + padding-bottom: 10px; + box-shadow: 0px 0px 3px 1px #222; + margin: 15px auto 0 auto; +} + +.ovk-photo-details { + overflow: auto; +} + +.photo_com_title { + font-weight: bold; + padding-bottom: 20px; +} + +.photo_com_title div { + float: right; + font-weight: normal; +} + +.ovk-photo-slide-left { + left: 0; + width: 35%; + height: 100%; + max-height: 60vh; + position: absolute; + cursor: pointer; +} + +.ovk-photo-slide-right { + right: 0; + width: 35%; + height: 100%; + max-height: 60vh; + position: absolute; + cursor: pointer; +} + .client_app > img { top: 3px; position: relative; @@ -2750,3 +2910,119 @@ body.article .floating_sidebar, body.article .page_content { font-weight: normal !important; font-size: 11px; } + +.topGrayBlock { + background: #F0F0F0; + height: 37px; + border-bottom: 1px solid #C7C7C7; +} + +.edited { + color: #9b9b9b; +} + +.uploadedImage img { + max-height: 76px; + object-fit: cover; +} + +.lagged { + filter: opacity(0.5); + cursor: progress; + user-select: none; +} + +.editMenu.loading { + filter: opacity(0.5); + cursor: progress; + user-select: none; +} + +.editMenu.loading * { + pointer-events: none; +} + +.lagged * { + pointer-events: none; +} + +.button.dragged { + background: #c4c4c4 !important; + border-color: #c4c4c4 !important; + color: black !important; +} + +.whiteBox { + background: white; + width: 421px; + margin-left: auto; + margin-right: auto; + border: 1px solid #E8E8E8; + margin-top: 7%; + height: 231px; +} + +.boxContent { + padding: 24px 38px; +} + +.blueList { + list-style-type: none; +} + +.blueList li { + color: black; + font-size: 11px; + padding-top: 7px; +} + +.blueList li::before { + content: " "; + width: 5px; + height: 5px; + display: inline-block; + vertical-align: bottom; + background-color: #73889C; + margin: 3px; + margin-left: 2px; + margin-right: 7px; +} + +.insertedPhoto { + background: white; + border: 1px solid #E8E7EA; + padding: 10px; + height: 100px; + margin-top: 6px; +} + +.uploadedImage { + float: right; + display: flex; + flex-direction: column; +} + +.uploadedImageDescription { + width: 449px; +} + +.uploadedImageDescription textarea { + width: 84%; + height: 86px; +} + +.smallFrame { + border: 1px solid #E1E3E5; + background: #F0F0F0; + height: 33px; + text-align: center; + cursor: pointer; +} + +.smallFrame .smallBtn { + margin-top: 10px; +} + +.smallFrame:hover { + background: #E9F0F1 !important; +} diff --git a/Web/static/css/microblog.css b/Web/static/css/microblog.css index bf5d0d53..503af42a 100644 --- a/Web/static/css/microblog.css +++ b/Web/static/css/microblog.css @@ -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%; diff --git a/Web/static/img/edit.png b/Web/static/img/edit.png new file mode 100644 index 00000000..b3474d0f Binary files /dev/null and b/Web/static/img/edit.png differ diff --git a/Web/static/img/video.png b/Web/static/img/video.png new file mode 100644 index 00000000..5c115f1c Binary files /dev/null and b/Web/static/img/video.png differ diff --git a/Web/static/js/al_photos.js b/Web/static/js/al_photos.js new file mode 100644 index 00000000..b4632d60 --- /dev/null +++ b/Web/static/js/al_photos.js @@ -0,0 +1,198 @@ +$(document).on("change", "#uploadButton", (e) => { + let iterator = 0 + + if(e.currentTarget.files.length > 10) { + MessageBox(tr("error"), tr("too_many_pictures"), [tr("ok")], [() => {Function.noop}]) + return; + } + + for(const file of e.currentTarget.files) { + if(!file.type.startsWith('image/')) { + MessageBox(tr("error"), tr("only_images_accepted", escapeHtml(file.name)), [tr("ok")], [() => {Function.noop}]) + return; + } + + if(file.size > 5 * 1024 * 1024) { + MessageBox(tr("error"), tr("max_filesize", 5), [tr("ok")], [() => {Function.noop}]) + return; + } + } + + if(document.querySelector(".whiteBox").style.display == "block") { + document.querySelector(".whiteBox").style.display = "none" + document.querySelector(".insertThere").append(document.getElementById("fakeButton")); + } + + let photos = new FormData() + for(file of e.currentTarget.files) { + photos.append("photo_"+iterator, file) + iterator += 1 + } + + photos.append("count", e.currentTarget.files.length) + photos.append("hash", u("meta[name=csrf]").attr("value")) + + let xhr = new XMLHttpRequest() + xhr.open("POST", "/photos/upload?album="+document.getElementById("album").value) + + xhr.onloadstart = () => { + document.querySelector(".insertPhotos").insertAdjacentHTML("beforeend", ``) + } + + xhr.onload = () => { + let result = JSON.parse(xhr.responseText) + + if(result.success) { + u("#loader").remove() + let photosArr = result.photos + + for(photo of photosArr) { + let table = document.querySelector(".insertPhotos") + + table.insertAdjacentHTML("beforeend", ` +
+
+ ${tr("description")}: + +
+
+ + ${tr("delete")} + +
+
+ `) + } + + document.getElementById("endUploading").style.display = "block" + } else { + u("#loader").remove() + MessageBox(tr("error"), escapeHtml(result.flash.message) ?? tr("error_uploading_photo"), [tr("ok")], [() => {Function.noop}]) + } + } + + xhr.send(photos) +}) + +$(document).on("click", "#endUploading", (e) => { + let table = document.querySelector("#photos") + let data = new FormData() + let arr = new Map(); + for(el of table.querySelectorAll("div#photo")) { + arr.set(el.dataset.id, el.querySelector("textarea").value) + } + + data.append("photos", JSON.stringify(Object.fromEntries(arr))) + data.append("hash", u("meta[name=csrf]").attr("value")) + + let xhr = new XMLHttpRequest() + // в самом вк на каждое изменение описания отправляется свой запрос, но тут мы экономим запросы + xhr.open("POST", "/photos/upload?act=finish&album="+document.getElementById("album").value) + + xhr.onloadstart = () => { + e.currentTarget.setAttribute("disabled", "disabled") + } + + xhr.onerror = () => { + MessageBox(tr("error"), tr("error_uploading_photo"), [tr("ok")], [() => {Function.noop}]) + } + + xhr.onload = () => { + let result = JSON.parse(xhr.responseText) + + if(!result.success) { + MessageBox(tr("error"), escapeHtml(result.flash.message), [tr("ok")], [() => {Function.noop}]) + } else { + document.querySelector(".page_content .insertPhotos").innerHTML = "" + document.getElementById("endUploading").style.display = "none" + + NewNotification(tr("photos_successfully_uploaded"), tr("click_to_go_to_album"), null, () => {window.location.assign(`/album${result.owner}_${result.album}`)}) + + document.querySelector(".whiteBox").style.display = "block" + document.querySelector(".insertAgain").append(document.getElementById("fakeButton")) + } + + e.currentTarget.removeAttribute("disabled") + } + + xhr.send(data) +}) + +$(document).on("click", "#deletePhoto", (e) => { + let data = new FormData() + data.append("hash", u("meta[name=csrf]").attr("value")) + + let xhr = new XMLHttpRequest() + xhr.open("POST", `/photo${e.currentTarget.dataset.owner}_${e.currentTarget.dataset.id}/delete`) + + xhr.onloadstart = () => { + e.currentTarget.closest("div#photo").classList.add("lagged") + } + + xhr.onerror = () => { + MessageBox(tr("error"), tr("unknown_error"), [tr("ok")], [() => {Function.noop}]) + } + + xhr.onload = () => { + u(e.currentTarget.closest("div#photo")).remove() + + if(document.querySelectorAll("div#photo").length < 1) { + document.getElementById("endUploading").style.display = "none" + document.querySelector(".whiteBox").style.display = "block" + document.querySelector(".insertAgain").append(document.getElementById("fakeButton")) + } + } + + xhr.send(data) +}) + +$(document).on("dragover drop", (e) => { + e.preventDefault() + + return false; +}) + +$(".container_gray").on("dragover", (e) => { + e.preventDefault() + document.querySelector("#fakeButton").classList.add("dragged") + document.querySelector("#fakeButton").value = tr("drag_files_here") +}) + +$(".container_gray").on("dragleave", (e) => { + e.preventDefault() + document.querySelector("#fakeButton").classList.remove("dragged") + document.querySelector("#fakeButton").value = tr("upload_picts") +}) + +$(".container_gray").on("drop", (e) => { + e.originalEvent.dataTransfer.dropEffect = 'move'; + e.preventDefault() + + $(".container_gray").trigger("dragleave") + + let files = e.originalEvent.dataTransfer.files + + for(const file of files) { + if(!file.type.startsWith('image/')) { + MessageBox(tr("error"), tr("only_images_accepted", escapeHtml(file.name)), [tr("ok")], [() => {Function.noop}]) + return; + } + + if(file.size > 5 * 1024 * 1024) { + MessageBox(tr("error"), tr("max_filesize", 5), [tr("ok")], [() => {Function.noop}]) + return; + } + } + + document.getElementById("uploadButton").files = files + u("#uploadButton").trigger("change") +}) + +u(".container_gray").on("paste", (e) => { + if(e.clipboardData.files.length > 0 && e.clipboardData.files.length < 10) { + document.getElementById("uploadButton").files = e.clipboardData.files; + u("#uploadButton").trigger("change") + } +}) diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index 50e9f4b4..9d8f0357 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -22,37 +22,14 @@ function trim(string) { return newStr; } -function handleUpload(id) { - console.warn("блять..."); - - u("#post-buttons" + id + " .postFileSel").not("#" + this.id).each(input => input.value = null); - - var indicator = u("#post-buttons" + id + " .post-upload"); - var file = this.files[0]; - if(typeof file === "undefined") { - indicator.attr("style", "display: none;"); - } else { - u("span", indicator.nodes[0]).text(trim(file.name) + " (" + humanFileSize(file.size, false) + ")"); - indicator.attr("style", "display: block;"); - } - - document.querySelector("#post-buttons" + id + " #wallAttachmentMenu").classList.add("hidden"); -} - function initGraffiti(id) { let canvas = null; let msgbox = MessageBox(tr("draw_graffiti"), "
", [tr("save"), tr("cancel")], [function() { canvas.getImage({includeWatermark: false}).toBlob(blob => { let fName = "Graffiti-" + Math.ceil(performance.now()).toString() + ".jpeg"; let image = new File([blob], fName, {type: "image/jpeg", lastModified: new Date().getTime()}); - let trans = new DataTransfer(); - trans.items.add(image); - let fileSelect = document.querySelector("#post-buttons" + id + " input[name='_pic_attachment']"); - fileSelect.files = trans.files; - - u(fileSelect).trigger("change"); - u("#post-buttons" + id + " #write textarea").trigger("focusin"); + fastUploadImage(id, image) }, "image/jpeg", 0.92); canvas.teardown(); @@ -75,6 +52,79 @@ function initGraffiti(id) { }); } +function fastUploadImage(textareaId, file) { + // uploading images + + if(!file.type.startsWith('image/')) { + MessageBox(tr("error"), tr("only_images_accepted", escapeHtml(file.name)), [tr("ok")], [() => {Function.noop}]) + return; + } + + // 🤓🤓🤓 + if(file.size > 5 * 1024 * 1024) { + MessageBox(tr("error"), tr("max_filesize", 5), [tr("ok")], [() => {Function.noop}]) + return; + } + + let imagesCount = document.querySelector("#post-buttons" + textareaId + " input[name='photos']").value.split(",").length + + if(imagesCount > 10) { + MessageBox(tr("error"), tr("too_many_photos"), [tr("ok")], [() => {Function.noop}]) + return + } + + let xhr = new XMLHttpRequest + let data = new FormData + + data.append("photo_0", file) + data.append("count", 1) + data.append("hash", u("meta[name=csrf]").attr("value")) + + xhr.open("POST", "/photos/upload") + + xhr.onloadstart = () => { + document.querySelector("#post-buttons"+textareaId+" .upload").insertAdjacentHTML("beforeend", ``) + } + + xhr.onload = () => { + let response = JSON.parse(xhr.responseText) + + appendImage(response, textareaId) + } + + xhr.send(data) +} + +// append image after uploading via /photos/upload +function appendImage(response, textareaId) { + if(!response.success) { + MessageBox(tr("error"), (tr("error_uploading_photo") + response.flash.message), [tr("ok")], [() => {Function.noop}]) + } else { + let form = document.querySelector("#post-buttons"+textareaId) + let photosInput = form.querySelector("input[name='photos']") + let photosIndicator = form.querySelector(".upload") + + for(const phot of response.photos) { + let id = phot.owner + "_" + phot.vid + + photosInput.value += (id + ",") + + u(photosIndicator).append(u(` +
+ × + +
+ `)) + + u(photosIndicator.querySelector(`.upload #aP[data-id='${id}'] .upload-delete`)).on("click", () => { + photosInput.value = photosInput.value.replace(id + ",", "") + u(form.querySelector(`.upload #aP[data-id='${id}']`)).remove() + }) + } + } + u(`#post-buttons${textareaId} .upload #loader`).remove() +} + u(".post-like-button").on("click", function(e) { e.preventDefault(); @@ -97,11 +147,12 @@ u(".post-like-button").on("click", function(e) { function setupWallPostInputHandlers(id) { u("#wall-post-input" + id).on("paste", function(e) { + // Если вы находитесь на странице с постом с id 11, то копирование произойдёт джва раза. + // Оч ржачный баг, но вот как его исправить, я, если честно, не знаю. + if(e.clipboardData.files.length === 1) { - var input = u("#post-buttons" + id + " input[name=_pic_attachment]").nodes[0]; - input.files = e.clipboardData.files; - - u(input).trigger("change"); + fastUploadImage(id, e.clipboardData.files[0]) + return; } }); @@ -116,6 +167,183 @@ function setupWallPostInputHandlers(id) { // revert to original size if it is larger (possibly changed by user) // textArea.style.height = (newHeight > originalHeight ? (newHeight + boost) : originalHeight) + "px"; }); + + u("#wall-post-input" + id).on("dragover", function(e) { + e.preventDefault() + + // todo add animation + return; + }); + + $("#wall-post-input" + id).on("drop", function(e) { + e.originalEvent.dataTransfer.dropEffect = 'move'; + fastUploadImage(id, e.originalEvent.dataTransfer.files[0]) + return; + }); +} + +function OpenMiniature(e, photo, post, photo_id, type = "post") { + /* + костыли но смешные однако + */ + e.preventDefault(); + + if(u(".ovk-photo-view").length > 0) u(".ovk-photo-view-dimmer").remove(); + + // Значения для переключения фоток + + let json; + + let imagesCount = 0; + let imagesIndex = 0; + + let tempDetailsSection = []; + + let dialog = u( + `
+
+
+ + + + +
+
+
+
+ +
+
+ +
+
+
`); + u("body").addClass("dimmed").append(dialog); + document.querySelector("html").style.overflowY = "hidden" + + let button = u("#ovk-photo-close"); + + button.on("click", function(e) { + let __closeDialog = () => { + u("body").removeClass("dimmed"); + u(".ovk-photo-view-dimmer").remove(); + document.querySelector("html").style.overflowY = "scroll" + }; + + __closeDialog(); + }); + + function __reloadTitleBar() { + u("#photo_com_title_photos").last().innerHTML = imagesCount > 1 ? tr("photo_x_from_y", imagesIndex, imagesCount) : tr("photo"); + } + + function __loadDetails(photo_id, index) { + if(tempDetailsSection[index] == null) { + u(".ovk-photo-details").last().innerHTML = ''; + ky("/photo" + photo_id, { + hooks: { + afterResponse: [ + async (_request, _options, response) => { + let parser = new DOMParser(); + let body = parser.parseFromString(await response.text(), "text/html"); + + let element = u(body.getElementsByClassName("ovk-photo-details")).last(); + + tempDetailsSection[index] = element.innerHTML; + + if(index == imagesIndex) { + u(".ovk-photo-details").last().innerHTML = element.innerHTML; + } + + document.querySelectorAll(".ovk-photo-details .bsdn").forEach(bsdnInitElement) + document.querySelectorAll(".ovk-photo-details script").forEach(scr => { + // stolen from #953 + let newScr = document.createElement('script') + + if(scr.src) { + newScr.src = scr.src + } else { + newScr.textContent = scr.textContent + } + + document.querySelector(".ovk-photo-details").appendChild(newScr); + }) + } + ] + } + }); + } else { + u(".ovk-photo-details").last().innerHTML = tempDetailsSection[index]; + } + } + + function __slidePhoto(direction) { + /* direction = 1 - right + direction = 0 - left */ + if(json == undefined) { + console.log("Да подожди ты. Куда торопишься?"); + } else { + if(imagesIndex >= imagesCount && direction == 1) { + imagesIndex = 1; + } else if(imagesIndex <= 1 && direction == 0) { + imagesIndex = imagesCount; + } else if(direction == 1) { + imagesIndex++; + } else if(direction == 0) { + imagesIndex--; + } + + let photoURL = json.body[imagesIndex - 1].url; + + u("#ovk-photo-img").last().src = photoURL; + __reloadTitleBar(); + __loadDetails(json.body[imagesIndex - 1].id, imagesIndex); + } + } + + let slideLeft = u(".ovk-photo-slide-left"); + + slideLeft.on("click", (e) => { + __slidePhoto(0); + }); + + let slideRight = u(".ovk-photo-slide-right"); + + slideRight.on("click", (e) => { + __slidePhoto(1); + }); + + let data = new FormData() + data.append('parentType', type); + ky.post("/iapi/getPhotosFromPost/" + (type == "post" ? post : "1_"+post), { + hooks: { + afterResponse: [ + async (_request, _options, response) => { + json = await response.json(); + + imagesCount = json.body.length; + imagesIndex = 0; + // Это всё придётся правда на 1 прибавлять + + json.body.every(element => { + imagesIndex++; + if(element.id == photo_id) { + return false; + } else { + return true; + } + }); + + __reloadTitleBar(); + __loadDetails(json.body[imagesIndex - 1].id, imagesIndex); } + ] + }, + body: data + }); + + return u(".ovk-photo-view-dimmer"); } u("#write > form").on("keydown", function(event) { @@ -210,6 +438,7 @@ function addNote(textareaId, nid) u("body").removeClass("dimmed"); u(".ovk-diag-cont").remove(); + document.querySelector("html").style.overflowY = "scroll" } async function attachNote(id) @@ -263,3 +492,472 @@ async function showArticle(note_id) { u("body").removeClass("dimmed"); u("body").addClass("article"); } + +$(document).on("click", "#videoAttachment", async (e) => { + e.preventDefault() + + let body = ` + + +
+ ` + + let form = e.currentTarget.closest("form") + + MessageBox(tr("selecting_video"), body, [tr("close")], [Function.noop]); + + // styles for messageboxx + document.querySelector(".ovk-diag-body").style.padding = "0" + document.querySelector(".ovk-diag-cont").style.width = "580px" + document.querySelector(".ovk-diag-body").style.height = "335px" + + async function insertVideos(page, query = "") { + document.querySelector(".videosInsert").insertAdjacentHTML("beforeend", ``) + + let vidoses + let noVideosText = tr("no_videos") + if(query == "") { + vidoses = await API.Wall.getVideos(page) + } else { + vidoses = await API.Wall.searchVideos(page, query) + noVideosText = tr("no_videos_results") + } + + if(vidoses.count < 1) { + document.querySelector(".videosInsert").innerHTML = `${noVideosText}` + } + + let pagesCount = Math.ceil(Number(vidoses.count) / 8) + u("#loader").remove() + let insert = document.querySelector(".videosInsert") + + for(const vid of vidoses.items) { + let isAttached = (form.querySelector("input[name='videos']").value.includes(`${vid.video.owner_id}_${vid.video.id},`)) + + insert.insertAdjacentHTML("beforeend", ` +
+ + + + + + + + +
+ +
+ ${escapeHtml(vid.video.title)} +
+
+
+ + + ${ovk_proc_strtr(escapeHtml(vid.video.title), 30)} + + +
+

+ ${ovk_proc_strtr(escapeHtml(vid.video.description ?? ""), 140)} +

+ ${escapeHtml(vid.video.author_name ?? "")} +
+
+ `) + } + + if(page < pagesCount) { + document.querySelector(".videosInsert").insertAdjacentHTML("beforeend", ` +
+ more... +
`) + } + } + + $(".videosInsert").on("click", "#showMoreVideos", (e) => { + u(e.currentTarget).remove() + insertVideos(Number(e.currentTarget.dataset.page), document.querySelector(".topGrayBlock #vquery").value) + }) + + $(".topGrayBlock #vquery").on("change", async (e) => { + await new Promise(r => setTimeout(r, 1000)); + + if(e.currentTarget.value === document.querySelector(".topGrayBlock #vquery").value) { + document.querySelector(".videosInsert").innerHTML = "" + insertVideos(1, e.currentTarget.value) + return; + } else { + console.info("skipping") + } + }) + + insertVideos(1) + + function insertAttachment(id) { + let videos = form.querySelector("input[name='videos']") + + if(!videos.value.includes(id + ",")) { + if(videos.value.split(",").length > 10) { + NewNotification(tr("error"), tr("max_attached_videos")) + return false + } + + form.querySelector("input[name='videos']").value += (id + ",") + + console.info(id + " attached") + return true + } else { + form.querySelector("input[name='videos']").value = form.querySelector("input[name='videos']").value.replace(id + ",", "") + + console.info(id + " detached") + return false + } + } + + $(".videosInsert").on("click", "#attachvid", (ev) => { + // откреплено от псто + if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) { + u(`.post-has-videos .post-has-video[data-id='${ev.currentTarget.dataset.attachmentdata}']`).remove() + ev.currentTarget.innerHTML = tr("attach") + } else { + ev.currentTarget.innerHTML = tr("detach") + + form.querySelector(".post-has-videos").insertAdjacentHTML("beforeend", ` +
+ ${tr("video")} "${ovk_proc_strtr(escapeHtml(ev.currentTarget.dataset.name), 20)}" +
+ `) + + u(`#unattachVideo[data-id='${ev.currentTarget.dataset.attachmentdata}']`).on("click", (e) => { + let id = ev.currentTarget.dataset.attachmentdata + form.querySelector("input[name='videos']").value = form.querySelector("input[name='videos']").value.replace(id + ",", "") + + console.info(id + " detached") + + u(e.currentTarget).remove() + }) + } + }) +}) + +$(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", ` +
+
+ + + +
+ ${e.currentTarget.dataset.nsfw != null ? ` +
+ +
+ ` : ``} + ${e.currentTarget.dataset.fromgroup != null ? ` +
+ +
+ ` : ``} +
+ `) + + 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", ` + (${tr("edited_short")}) + `) + } + + 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 + post.querySelector(".really_text").setAttribute("data-text", result.new_text) + } 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" + } +}) + +// copypaste from videos picker +$(document).on("click", "#photosAttachments", async (e) => { + let body = ` +
+
+ ${tr("upload_new_photo")}: + + + +
+
+ +
+

${tr("is_x_photos", 0)}

+
+
+ ` + + let form = e.currentTarget.closest("form") + + MessageBox(tr("select_photo"), body, [tr("close")], [Function.noop]); + + document.querySelector(".ovk-diag-body").style.padding = "0" + document.querySelector(".ovk-diag-cont").style.width = "630px" + document.querySelector(".ovk-diag-body").style.height = "335px" + + async function insertPhotos(page, album = 0) { + u("#loader").remove() + + let insertPlace = document.querySelector(".photosInsert .photosList") + document.querySelector(".photosInsert").insertAdjacentHTML("beforeend", ``) + + let photos; + + try { + photos = await API.Photos.getPhotos(page, Number(album)) + } catch(e) { + document.querySelector(".photosInsert h4").innerHTML = tr("is_x_photos", -1) + insertPlace.innerHTML = "Invalid album" + console.error(e) + u("#loader").remove() + return; + } + + document.querySelector(".photosInsert h4").innerHTML = tr("is_x_photos", photos.count) + + let pagesCount = Math.ceil(Number(photos.count) / 24) + u("#loader").remove() + + for(const photo of photos.items) { + let isAttached = (form.querySelector("input[name='photos']").value.includes(`${photo.owner_id}_${photo.id},`)) + + insertPlace.insertAdjacentHTML("beforeend", ` +
+ + ... + +
+ `) + } + + if(page < pagesCount) { + insertPlace.insertAdjacentHTML("beforeend", ` +
+ more... +
`) + } + } + + insertPhotos(1) + + let albums = await API.Photos.getAlbums(Number(e.currentTarget.dataset.club ?? 0)) + + for(const alb of albums.items) { + let sel = document.querySelector(".ovk-diag-body #albumSelect") + + sel.insertAdjacentHTML("beforeend", ``) + } + + $(".photosInsert").on("click", "#showMorePhotos", (e) => { + u(e.currentTarget).remove() + insertPhotos(Number(e.currentTarget.dataset.page), document.querySelector(".topGrayBlock #albumSelect").value) + }) + + $(".topGrayBlock #albumSelect").on("change", (evv) => { + document.querySelector(".photosInsert .photosList").innerHTML = "" + + insertPhotos(1, evv.currentTarget.value) + }) + + function insertAttachment(id) { + let photos = form.querySelector("input[name='photos']") + + if(!photos.value.includes(id + ",")) { + if(photos.value.split(",").length > 10) { + NewNotification(tr("error"), tr("max_attached_photos")) + return false + } + + form.querySelector("input[name='photos']").value += (id + ",") + + console.info(id + " attached") + return true + } else { + form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "") + + console.info(id + " detached") + return false + } + } + + $(".photosList").on("click", ".album-photo", (ev) => { + ev.preventDefault() + + if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) { + u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove() + ev.currentTarget.querySelector("img").style.backgroundColor = "white" + } else { + ev.currentTarget.querySelector("img").style.backgroundColor = "#646464" + let id = ev.currentTarget.dataset.attachmentdata + + u(form.querySelector(`.upload`)).append(u(` +
+ × + +
+ `)); + + u(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}'] .upload-delete`).on("click", () => { + form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "") + u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove() + }) + } + }) + + u("#fastFotosUplod").on("change", (evn) => { + let xhr = new XMLHttpRequest() + xhr.open("POST", "/photos/upload") + + let formdata = new FormData() + let iterator = 0 + + for(const fille of evn.currentTarget.files) { + if(!fille.type.startsWith('image/')) { + continue; + } + + if(fille.size > 5 * 1024 * 1024) { + continue; + } + + if(evn.currentTarget.files.length >= 10) { + NewNotification(tr("error"), tr("max_attached_photos")) + return; + } + + formdata.append("photo_"+iterator, fille) + iterator += 1 + } + + xhr.onloadstart = () => { + evn.currentTarget.parentNode.insertAdjacentHTML("beforeend", ``) + } + + xhr.onload = () => { + let result = JSON.parse(xhr.responseText) + + u("#loader").remove() + if(result.success) { + for(const pht of result.photos) { + let id = pht.owner + "_" + pht.vid + + if(!insertAttachment(id)) { + return + } + + u(form.querySelector(`.upload`)).append(u(` +
+ × + +
+ `)); + + u(`.upload #aP[data-id='${pht.owner + "_" + pht.vid}'] .upload-delete`).on("click", () => { + form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "") + u(form.querySelector(`.upload #aP[data-id='${id}']`)).remove() + }) + } + + u("body").removeClass("dimmed"); + u(".ovk-diag-cont").remove(); + document.querySelector("html").style.overflowY = "scroll" + } else { + // todo: https://vk.com/wall-32295218_78593 + alert(result.flash.message) + } + } + + formdata.append("hash", u("meta[name=csrf]").attr("value")) + formdata.append("count", iterator) + + xhr.send(formdata) + }) +}) diff --git a/Web/static/js/messagebox.js b/Web/static/js/messagebox.js index 45791fd3..e56c720f 100644 --- a/Web/static/js/messagebox.js +++ b/Web/static/js/messagebox.js @@ -3,6 +3,7 @@ Function.noop = () => {}; function MessageBox(title, body, buttons, callbacks) { if(u(".ovk-diag-cont").length > 0) return false; + document.querySelector("html").style.overflowY = "hidden" let dialog = u( `
@@ -19,7 +20,11 @@ function MessageBox(title, body, buttons, callbacks) { button.on("click", function(e) { let __closeDialog = () => { - u("body").removeClass("dimmed"); + if(document.querySelector(".ovk-photo-view-dimmer") == null) { + u("body").removeClass("dimmed"); + document.querySelector("html").style.overflowY = "scroll" + } + u(".ovk-diag-cont").remove(); }; diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js index 22144cfc..b131bfa0 100644 --- a/Web/static/js/openvk.cls.js +++ b/Web/static/js/openvk.cls.js @@ -68,7 +68,7 @@ function toggleMenu(id) { } document.addEventListener("DOMContentLoaded", function() { //BEGIN - u("#_photoDelete").on("click", function(e) { + $(document).on("click", "#_photoDelete", function(e) { var formHtml = ""; formHtml += ""; formHtml += ""; diff --git a/install/automated/common/10-openvk.conf b/install/automated/common/10-openvk.conf index d842eefd..b272f5ac 100644 --- a/install/automated/common/10-openvk.conf +++ b/install/automated/common/10-openvk.conf @@ -8,6 +8,5 @@ Require all granted - ErrorLog /var/log/openvk/error.log - CustomLog /var/log/openvk/access.log combinedio + LogFormat combinedio diff --git a/install/automated/docker/acl_handler.sh b/install/automated/docker/acl_handler.sh index 8ef23239..e23176a3 100755 --- a/install/automated/docker/acl_handler.sh +++ b/install/automated/docker/acl_handler.sh @@ -8,7 +8,6 @@ do chown -R 33:33 /opt/chandler/extensions/available/openvk/tmp/api-storage/audios chown -R 33:33 /opt/chandler/extensions/available/openvk/tmp/api-storage/photos chown -R 33:33 /opt/chandler/extensions/available/openvk/tmp/api-storage/videos - chown -R 33:33 /var/log/openvk sleep 600 done \ No newline at end of file diff --git a/install/automated/docker/base-php-apache.Dockerfile b/install/automated/docker/base-php-apache.Dockerfile index de24d34f..3ead71f0 100644 --- a/install/automated/docker/base-php-apache.Dockerfile +++ b/install/automated/docker/base-php-apache.Dockerfile @@ -18,5 +18,6 @@ RUN apt update; \ yaml \ pdo_mysql \ rdkafka \ + imagick \ && \ rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/install/automated/docker/docker-compose.yml b/install/automated/docker/docker-compose.yml index cbd697a4..685ab713 100644 --- a/install/automated/docker/docker-compose.yml +++ b/install/automated/docker/docker-compose.yml @@ -12,7 +12,6 @@ services: - openvk-audios:/opt/chandler/extensions/available/openvk/tmp/api-storage/audios - openvk-photos:/opt/chandler/extensions/available/openvk/tmp/api-storage/photos - openvk-videos:/opt/chandler/extensions/available/openvk/tmp/api-storage/videos - - openvk-logs:/var/log/openvk - ./openvk.yml:/opt/chandler/extensions/available/openvk/openvk.yml:ro - ./chandler.yml:/opt/chandler/chandler.yml:ro depends_on: @@ -32,7 +31,6 @@ services: - openvk-audios:/opt/chandler/extensions/available/openvk/tmp/api-storage/audios - openvk-photos:/opt/chandler/extensions/available/openvk/tmp/api-storage/photos - openvk-videos:/opt/chandler/extensions/available/openvk/tmp/api-storage/videos - - openvk-logs:/var/log/openvk - ./acl_handler.sh:/bin/acl_handler.sh:ro mariadb-primary: @@ -76,6 +74,7 @@ services: - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 - KAFKA_BROKER_ID=1 - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@127.0.0.1:9093 + - KAFKA_CFG_NODE_ID=1 phpmyadmin: image: docker.io/phpmyadmin:5 diff --git a/install/automated/docker/openvk.Dockerfile b/install/automated/docker/openvk.Dockerfile index 47f63b77..389f7443 100644 --- a/install/automated/docker/openvk.Dockerfile +++ b/install/automated/docker/openvk.Dockerfile @@ -48,7 +48,6 @@ RUN ln -s /opt/chandler/extensions/available/commitcaptcha/ /opt/chandler/extens ln -s /opt/chandler/extensions/available/openvk/install/automated/common/10-openvk.conf /etc/apache2/sites-enabled/10-openvk.conf && \ a2enmod rewrite -VOLUME [ "/var/log/openvk" ] VOLUME [ "/opt/chandler/extensions/available/openvk/storage" ] VOLUME [ "/opt/chandler/extensions/available/openvk/tmp/api-storage/audios" ] VOLUME [ "/opt/chandler/extensions/available/openvk/tmp/api-storage/photos" ] diff --git a/install/automated/kubernetes/manifests/003-deployment.yaml b/install/automated/kubernetes/manifests/003-deployment.yaml index e15d3ccd..edbc40f9 100644 --- a/install/automated/kubernetes/manifests/003-deployment.yaml +++ b/install/automated/kubernetes/manifests/003-deployment.yaml @@ -38,13 +38,10 @@ items: chown -R 33:33 /opt/chandler/extensions/available/openvk/tmp/api-storage/audios chown -R 33:33 /opt/chandler/extensions/available/openvk/tmp/api-storage/photos chown -R 33:33 /opt/chandler/extensions/available/openvk/tmp/api-storage/videos - chown -R 33:33 /var/log/openvk sleep 600 done volumeMounts: - - mountPath: /var/log/openvk - name: openvk-logs - mountPath: /opt/chandler/extensions/available/openvk/storage name: openvk-storage - mountPath: /opt/chandler/extensions/available/openvk/tmp/api-storage/audios @@ -66,8 +63,6 @@ items: cpu: 100m memory: 512Mi volumeMounts: - - mountPath: /var/log/openvk - name: openvk-logs - mountPath: /opt/chandler/extensions/available/openvk/openvk.yml name: openvk-config subPath: openvk.yml diff --git a/install/sqls/00002-support-aliases.sql b/install/sqls/00002-support-aliases.sql index b586a1dd..e3d17ca0 100644 --- a/install/sqls/00002-support-aliases.sql +++ b/install/sqls/00002-support-aliases.sql @@ -1 +1,7 @@ -CREATE TABLE `support_names` ( `agent` BIGINT UNSIGNED NOT NULL , `name` VARCHAR(512) NOT NULL , `icon` VARCHAR(1024) NULL DEFAULT NULL , `numerate` BOOLEAN NOT NULL DEFAULT FALSE , PRIMARY KEY (`agent`)) ENGINE = InnoDB; \ No newline at end of file +CREATE TABLE `support_names` ( + `agent` BIGINT UNSIGNED NOT NULL, + `name` VARCHAR(512) NOT NULL, + `icon` VARCHAR(1024) NULL DEFAULT NULL, + `numerate` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`agent`) +) ENGINE = InnoDB; \ No newline at end of file diff --git a/install/sqls/00018-reports.sql b/install/sqls/00019-reports.sql similarity index 100% rename from install/sqls/00018-reports.sql rename to install/sqls/00019-reports.sql diff --git a/install/sqls/00019-block-in-support.sql b/install/sqls/00020-block-in-support.sql similarity index 100% rename from install/sqls/00019-block-in-support.sql rename to install/sqls/00020-block-in-support.sql diff --git a/install/sqls/00020-image-sizes.sql b/install/sqls/00021-image-sizes.sql similarity index 100% rename from install/sqls/00020-image-sizes.sql rename to install/sqls/00021-image-sizes.sql diff --git a/install/sqls/00021-video-processing.sql b/install/sqls/00022-video-processing.sql similarity index 100% rename from install/sqls/00021-video-processing.sql rename to install/sqls/00022-video-processing.sql diff --git a/install/sqls/00022-group-alerts.sql b/install/sqls/00023-group-alerts.sql similarity index 100% rename from install/sqls/00022-group-alerts.sql rename to install/sqls/00023-group-alerts.sql diff --git a/install/sqls/00023-email-change.sql b/install/sqls/00024-email-change.sql similarity index 100% rename from install/sqls/00023-email-change.sql rename to install/sqls/00024-email-change.sql diff --git a/install/sqls/00024-main-page-setting.sql b/install/sqls/00025-main-page-setting.sql similarity index 100% rename from install/sqls/00024-main-page-setting.sql rename to install/sqls/00025-main-page-setting.sql diff --git a/install/sqls/00025-toncoin-fetching.sql b/install/sqls/00026-toncoin-fetching.sql similarity index 100% rename from install/sqls/00025-toncoin-fetching.sql rename to install/sqls/00026-toncoin-fetching.sql diff --git a/install/sqls/00026-better-birthdays.sql b/install/sqls/00027-better-birthdays.sql similarity index 100% rename from install/sqls/00026-better-birthdays.sql rename to install/sqls/00027-better-birthdays.sql diff --git a/install/sqls/00027-rating.sql b/install/sqls/00028-rating.sql similarity index 100% rename from install/sqls/00027-rating.sql rename to install/sqls/00028-rating.sql diff --git a/install/sqls/00028-deactivation.sql b/install/sqls/00029-deactivation.sql similarity index 100% rename from install/sqls/00028-deactivation.sql rename to install/sqls/00029-deactivation.sql diff --git a/install/sqls/00029-hashtag-search.sql b/install/sqls/00030-hashtag-search.sql similarity index 100% rename from install/sqls/00029-hashtag-search.sql rename to install/sqls/00030-hashtag-search.sql diff --git a/install/sqls/00030-apps.sql b/install/sqls/00031-apps.sql similarity index 100% rename from install/sqls/00030-apps.sql rename to install/sqls/00031-apps.sql diff --git a/install/sqls/00032-agent-card.sql b/install/sqls/00032-agent-card.sql deleted file mode 100644 index a8354c80..00000000 --- a/install/sqls/00032-agent-card.sql +++ /dev/null @@ -1,15 +0,0 @@ - -CREATE TABLE `support_names` ( - `id` bigint(20) UNSIGNED NOT NULL, - `agent` bigint(20) UNSIGNED NOT NULL, - `name` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL, - `icon` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `numerate` tinyint(1) NOT NULL DEFAULT 0 -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -ALTER TABLE `support_names` - ADD PRIMARY KEY (`id`); - -ALTER TABLE `support_names` - MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; -COMMIT; \ No newline at end of file diff --git a/install/sqls/00031-ban-page-until.sql b/install/sqls/00032-ban-page-until.sql similarity index 100% rename from install/sqls/00031-ban-page-until.sql rename to install/sqls/00032-ban-page-until.sql diff --git a/install/sqls/00033-agent-card.sql b/install/sqls/00033-agent-card.sql new file mode 100644 index 00000000..0f82460e --- /dev/null +++ b/install/sqls/00033-agent-card.sql @@ -0,0 +1,9 @@ + +ALTER TABLE `support_names` ADD `id` bigint(20) UNSIGNED NOT NULL FIRST; + +ALTER TABLE `support_names` + ADD UNIQUE KEY `id` (`id`); + +ALTER TABLE `support_names` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; +COMMIT; \ No newline at end of file diff --git a/install/sqls/00032-banned-urls.sql b/install/sqls/00034-banned-urls.sql similarity index 100% rename from install/sqls/00032-banned-urls.sql rename to install/sqls/00034-banned-urls.sql diff --git a/install/sqls/00032-better-reports.sql b/install/sqls/00035-better-reports.sql similarity index 100% rename from install/sqls/00032-better-reports.sql rename to install/sqls/00035-better-reports.sql diff --git a/install/sqls/00033-shortcode-aliases.sql b/install/sqls/00036-shortcode-aliases.sql similarity index 100% rename from install/sqls/00033-shortcode-aliases.sql rename to install/sqls/00036-shortcode-aliases.sql diff --git a/install/sqls/00037-agent-card-profilefix.sql b/install/sqls/00037-agent-card-profilefix.sql deleted file mode 100644 index e7ebc230..00000000 --- a/install/sqls/00037-agent-card-profilefix.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE `support_names` - ADD COLUMN `id` bigint(20) NOT NULL AUTO_INCREMENT UNIQUE FIRST; diff --git a/install/sqls/00034-polls.sql b/install/sqls/00037-polls.sql similarity index 100% rename from install/sqls/00034-polls.sql rename to install/sqls/00037-polls.sql diff --git a/install/sqls/00035-backdrops.sql b/install/sqls/00038-backdrops.sql similarity index 100% rename from install/sqls/00035-backdrops.sql rename to install/sqls/00038-backdrops.sql diff --git a/install/sqls/00036-platforms.sql b/install/sqls/00039-platforms.sql similarity index 100% rename from install/sqls/00036-platforms.sql rename to install/sqls/00039-platforms.sql diff --git a/install/sqls/00038-noSpam-templates.sql b/install/sqls/00040-noSpam-templates.sql similarity index 100% rename from install/sqls/00038-noSpam-templates.sql rename to install/sqls/00040-noSpam-templates.sql diff --git a/locales/en.strings b/locales/en.strings index f4640133..5988626a 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -149,6 +149,10 @@ "user_banned" = "Unfortunately, we had to block the $1 user page."; "user_banned_comment" = "Moderator's comment:"; +"verified_page" = "Verified page"; +"user_is_blocked" = "User is blocked"; +"before" = "before"; +"forever" = "forever"; /* Wall */ @@ -202,6 +206,7 @@ "nsfw_warning" = "This post may have NSFW-content"; "report" = "Report"; "attach" = "Attach"; +"detach" = "Detach"; "attach_photo" = "Attach photo"; "attach_video" = "Attach video"; "draw_graffiti" = "Draw graffiti"; @@ -213,6 +218,9 @@ "graffiti" = "Graffiti"; "reply" = "Reply"; +"post_is_ad" = "This post is sponsored."; + +"edited_short" = "edited"; "all_posts" = "All posts"; "users_posts" = "Posts by $1"; @@ -400,9 +408,15 @@ "create" = "Create"; "albums" = "Albums"; +"album" = "Album"; +"photos" = "photos"; +"photo" = "Photo"; "create_album" = "Create album"; "edit_album" = "Edit album"; +"edit_photo" = "Edit photo"; "creating_album" = "Creating album"; +"delete_photo" = "Delete photo"; +"sure_deleting_photo" = "Do you really want to delete this picture?"; "upload_photo" = "Upload photo"; "photo" = "Photo"; "upload_button" = "Upload"; @@ -444,6 +458,39 @@ "upd_f" = "updated her profile picture"; "upd_g" = "updated group's picture"; +"add_photos" = "Add photos"; +"upload_picts" = "Upload photos"; +"end_uploading" = "Finish uploading"; +"photos_successfully_uploaded" = "Photos successfully uploaded"; +"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"; + +"drag_files_here" = "Drag files here"; +"only_images_accepted" = "File \"$1\" is not an image"; +"max_filesize" = "Max filesize is $1 MB"; + +"uploading_photos_from_computer" = "Uploading photos from Your computer"; +"supported_formats" = "Supported file formats: JPG, PNG and GIF."; +"max_load_photos" = "You can upload up to 10 photos at a time."; +"tip" = "Tip"; +"tip_ctrl" = "to select multiple photos at once, hold down the Ctrl key when selecting files in Windows or the CMD key in Mac OS."; +"album_poster" = "Album poster"; +"select_photo" = "Select photos"; +"upload_new_photo" = "Upload new photo"; + +"is_x_photos_zero" = "Just zero photos."; +"is_x_photos_one" = "Just one photo."; +"is_x_photos_few" = "Just $1 photos."; +"is_x_photos_many" = "Just $1 photos."; +"is_x_photos_other" = "Just $1 photos."; + +"all_photos" = "All photos"; +"error_uploading_photo" = "Error when uploading photo. Error text: "; +"too_many_photos" = "Too many photos."; + +"photo_x_from_y" = "Photo $1 from $2"; + /* Notes */ "notes" = "Notes"; @@ -478,6 +525,8 @@ "notes_closed" = "You can't attach note to post, because only you can see them.
You can change it in settings."; "do_not_attach_note" = "Do not attach note"; +"something" = "Something"; +"supports_xhtml" = "from (X)HTML supported."; /* Notes: Article Viewer */ "aw_legacy_ui" = "Legacy interface"; @@ -693,6 +742,9 @@ "two_factor_authentication_backup_codes_1" = "Backup codes allow you to validate your login when you don't have access to your phone, for example, while traveling."; "two_factor_authentication_backup_codes_2" = "You have 10 more codes, each code can only be used once. Print them out, put them away in a safe place and use them when you need codes to validate your login."; "two_factor_authentication_backup_codes_3" = "You can get new codes if they run out. Only the last created backup codes are valid."; +"viewing_backup_codes" = "View backup codes"; +"disable_2fa" = "Disable 2FA"; +"viewing" = "View"; /* Sorting */ @@ -720,6 +772,16 @@ "view_video" = "View"; +"selecting_video" = "Selecting videos"; +"upload_new_video" = "Upload new video"; +"max_attached_videos" = "Max is 10 videos"; +"max_attached_photos" = "Max is 10 photos"; +"no_videos" = "You don't have uploaded videos."; +"no_videos_results" = "No results."; + +"change_video" = "Change video"; +"unknown_video" = "This video is not supported in your version of OpenVK."; + /* Notifications */ "feedback" = "Feedback"; @@ -761,6 +823,7 @@ "nt_mention_in_video" = "in discussion of this video"; "nt_mention_in_note" = "in discussion of this note"; "nt_mention_in_topic" = "in the discussion"; +"nt_sent_gift" = "sent you a gift"; "nt_post_small" = "post"; @@ -977,6 +1040,20 @@ "text_of_the_post" = "Text of the post"; "today" = "today"; +"will_be_watched" = "It will be reviewed by the moderators soon"; + +"report_question" = "Report?"; +"report_question_text" = "What exactly do you find unacceptable about this material?"; +"report_reason" = "Report reason"; +"reason" = "Reason"; +"going_to_report_app" = "You are about to report this application."; +"going_to_report_club" = "You are about to report this club."; +"going_to_report_photo" = "You are about to report this photo."; +"going_to_report_user" = "You are about to report this user."; +"going_to_report_video" = "You are about to report this video."; +"going_to_report_post" = "You are about to report this post."; +"going_to_report_comment" = "You are about to report this comment."; + "comment" = "Comment"; "sender" = "Sender"; @@ -1134,6 +1211,7 @@ "error_data_too_big" = "Attribute '$1' must be at most $2 $3 long"; "forbidden" = "Access error"; +"unknown_error" = "Unknown error"; "forbidden_comment" = "This user's privacy settings do not allow you to look at his page."; "changes_saved" = "Changes saved"; @@ -1188,21 +1266,116 @@ "error_deleting_suggested" = "You can't delete your accepted post"; "error_invalid_wall_value" = "Invalid wall value"; +"error_sending_report" = "Failed to make a report..."; + +"error_when_saving_gift" = "Error when saving gift"; +"error_when_saving_gift_bad_image" = "Gift image is crooked."; +"error_when_saving_gift_no_image" = "Please, upload gift's image."; +"video_uploads_disabled" = "Video uploads are disabled by the system administrator."; + +"error_when_publishing_comment" = "Error when publishing comment"; +"error_when_publishing_comment_description" = "Image is corrupted, too big or one side is many times larger than the other."; +"error_comment_empty" = "Comment is empty or too big."; +"error_comment_too_big" = "Comment is too big."; +"error_comment_file_too_big" = "Media file is corrupted or too big."; + +"comment_is_added" = "Comment has been added"; +"comment_is_added_desc" = "Your comment will appear on page."; + +"error_access_denied_short" = "Access denied"; +"error_access_denied" = "You don't have rights to edit this resource"; +"success" = "Success"; +"comment_will_not_appear" = "This comment will no longer appear."; + +"error_when_gifting" = "Failed to gift"; +"error_user_not_exists" = "User or pack does not exist."; +"error_no_rights_gifts" = "Failed to check rights on gift."; +"error_no_more_gifts" = "You no longer have such gifts."; +"error_no_money" = "Shout out to a beggar."; + +"gift_sent" = "Gift sent"; +"gift_sent_desc" = "You sent a gift to $1 for $2 votes"; + +"error_on_server_side" = "An error occurred on the server side. Contact your system administrator."; +"error_no_group_name" = "You did not enter a group name."; + +"success_action" = "Action successful"; +"connection_error" = "Connection error"; +"connection_error_desc" = "Failed to connect to telemetry service."; + +"error_when_uploading_photo" = "Failed to save photo"; + +"new_changes_desc" = "New data will appear in your group."; +"comment_is_changed" = "Admin comment changed"; +"comment_is_deleted" = "Admin comment deleted"; +"comment_is_too_long" = "Comment is too long ($1 symbols instead 36)"; +"x_no_more_admin" = "$1 no longer an administrator."; +"x_is_admin" = "$1 appointed as an administrator."; + +"x_is_now_hidden" = "Now $1 will be shown as a normal subscriber to everyone except other admins"; +"x_is_now_showed" = "Now everyone will know that $1 is an administrator."; + +"note_is_deleted" = "Note was deleted"; +"note_x_is_now_deleted" = "Note \"$1\" was successfully deleted."; +"new_data_accepted" = "New data accepted."; + +"album_is_deleted" = "Album was deleted"; +"album_x_is_deleted" = "Album $1 was successfully deleted."; + +"error_adding_to_deleted" = "Failed to save photo to DELETED."; +"error_adding_to_x" = "Failed to save photo to $1."; +"no_photo" = "No photo"; + +"select_file" = "Select file"; +"new_description_will_appear" = "The updated description will appear on the photo page.."; +"photo_is_deleted" = "Photo was deleted"; +"photo_is_deleted_desc" = "This photo has been successfully deleted."; + +"no_video" = "No video"; +"no_video_desc" = "Select a file or provide a link."; +"error_occured" = "Error occured"; +"error_video_damaged_file" = "The file is corrupted or does not contain video."; +"error_video_incorrect_link" = "Perhaps the link is incorrect."; +"error_video_no_title" = "Video can't be published without title."; + +"new_data_video" = "The updated description will appear on the video page."; +"error_deleting_video" = "Failed to delete video"; +"login_please" = "You are not signed in."; +"invalid_code" = "Failed to verify phone number: Invalid code."; + +"error_max_pinned_clubs" = "Maximum count of the pinned groups is 10."; +"error_viewing_subs" = "You cannot view the full list of subscriptions $1."; +"error_status_too_long" = "Status is too long ($1 instead 255)"; +"death" = "Death..."; +"nehay" = "Live long!"; +"user_successfully_banned" = "User was successfully banned."; + +"content_is_deleted" = "The content has been removed and the user has received a warning."; +"report_is_ignored" = "Report was ignored."; +"group_owner_is_banned" = "Group's owner was successfully banned"; +"group_is_banned" = "Group was successfully banned"; + +"description_too_long" = "Description is too long."; + /* Admin actions */ "login_as" = "Login as $1"; "manage_user_action" = "Manage user"; "manage_group_action" = "Manage group"; "ban_user_action" = "Ban user"; +"blocks" = "Blocks"; +"last_actions" = "Last actions"; "unban_user_action" = "Unban user"; "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 */ "admin" = "Admin panel"; +"sandbox_for_developers" = "Sandbox for developers"; "admin_ownerid" = "Owner ID"; "admin_author" = "Author"; "admin_name" = "Name"; @@ -1288,6 +1461,11 @@ "admin_banned_link_not_specified" = "The link is not specified"; "admin_banned_link_not_found" = "Link not found"; +"admin_gift_moved_successfully" = "Gift moved successfully"; +"admin_gift_moved_to_recycle" = "This gift will now be in Recycle Bin."; + +"logs" = "Logs"; +"logs_anything" = "Anything"; "logs_adding" = "Creation"; "logs_editing" = "Editing"; "logs_removing" = "Deletion"; @@ -1296,6 +1474,28 @@ "logs_edited" = "edited"; "logs_removed" = "removed"; "logs_restored" = "restored"; +"logs_id_post" = "ID записи"; +"logs_id_object" = "ID объекта"; +"logs_uuid_user" = "UUID пользователя"; +"logs_change_type" = "Тип изменения"; +"logs_change_object" = "Тип объекта"; + +"logs_user" = "User"; +"logs_object" = "Object"; +"logs_type" = "Type"; +"logs_changes" = "Changes"; +"logs_time" = "Time"; + +"bans_history" = "Blocks history"; +"bans_history_blocked" = "Blocked"; +"bans_history_initiator" = "Initiator"; +"bans_history_start" = "Start"; +"bans_history_end" = "End"; +"bans_history_time" = "Time"; +"bans_history_reason" = "Reason"; +"bans_history_start" = "Start"; +"bans_history_removed" = "Removed"; +"bans_history_active" = "Active block"; /* Paginator (deprecated) */ @@ -1343,6 +1543,8 @@ "warning" = "Warning"; "question_confirm" = "This action can't be undone. Do you really wanna do it?"; +"confirm_m" = "Confirm"; +"action_successfully" = "Success"; /* User alerts */ @@ -1356,6 +1558,8 @@ /* Away */ +"transition_is_blocked" = "Transition is blocked"; +"caution" = "Caution"; "url_is_banned" = "Link is not allowed"; "url_is_banned_comment" = "The $1 administration recommends not to follow this link."; "url_is_banned_comment_r" = "The $1 administration recommends not to follow this link.

The reason is: $2"; @@ -1620,6 +1824,41 @@ "no_results" = "No results"; +/* BadBrowser */ + +"deprecated_browser" = "Deprecated browser"; +"deprecated_browser_description" = "To view this content, you will need Firefox ESR 52+ or an equivalent World Wide Web navigator. Sorry about that."; + +/* Statistics */ + +"coverage" = "Coverage"; +"coverage_this_week" = "This graph shows the coverage over the last 7 days."; +"views" = "Views"; +"views_this_week" = "This graph shows the views of community posts over the last 7 days."; + +"full_coverage" = "Full coverage"; +"all_views" = "All views"; + +"subs_coverage" = "Subscribers coverage"; +"subs_views" = "Subscribers views"; + +"viral_coverage" = "Viral coverage"; +"viral_views" = "Viral views"; + +/* Sudo */ + +"you_entered_as" = "You logged as"; +"please_rights" = "please respect the right to privacy of other people's correspondence and do not abuse user swapping."; +"click_on" = "Click"; +"there" = "there"; +"to_leave" = "to logout"; + +/* Phone number */ + +"verify_phone_number" = "Confirm phone number"; +"we_sended_first" = "We sended SMS with code on number"; +"we_sended_end" = "enter it here"; + /* Mobile */ "mobile_friends" = "Friends"; "mobile_photos" = "Photos"; @@ -1635,3 +1874,40 @@ "mobile_like" = "Like"; "mobile_user_info_hide" = "Hide"; "mobile_user_info_show_details" = "Show details"; + +/* Moderation */ + +"section" = "Section"; +"template_ban" = "Ban by template"; +"active_templates" = "Active templates"; +"users_reports" = "Users reports"; +"substring" = "Substring"; +"n_user" = "User"; +"time_before" = "Time earlier than"; +"time_after" = "Time later than"; +"where_for_search" = "WHERE for search by section"; +"block_params" = "Block params"; +"only_rollback" = "Only rollback"; +"only_block" = "Only blocking"; +"rollback_and_block" = "Rollback and blocking"; +"subm" = "Apply"; + +"select_section_for_start" = "Choose a section to get started"; +"results_will_be_there" = "Search results will be displayed here"; +"search_results" = "Search results"; +"cnt" = "pcs"; + +"link_to_page" = "Link on page"; +"or_subnet" = "or subnet"; +"error_when_searching" = "Error while executing request"; +"no_found" = "No found"; +"operation_successfully" = "Operation completed successfully"; + +"unknown_error" = "Unknown error"; +"templates" = "Template"; +"type" = "Type"; +"count" = "Count"; +"time" = "Time"; + +"roll_back" = "rollback"; +"roll_backed" = "rollbacked"; diff --git a/locales/hy.strings b/locales/hy.strings index de11783f..09a8e344 100644 --- a/locales/hy.strings +++ b/locales/hy.strings @@ -17,6 +17,7 @@ "password" = "Գաղտնաբառ"; "registration" = "Գրանցում"; "forgot_password" = "Մոռացե՞լ եք գաղտնաբառը"; + "checkbox_in_registration" = "Ես համաձայն եմ կոնֆիդենցիալության քաղաքականությանն ու կայքի կանոնադրությանը։"; "checkbox_in_registration_unchecked" = "Դուք պետք է համաձայնվեք պայմանների հետ նախքան գրանցվելը։"; @@ -54,8 +55,11 @@ "register_meta_desc" = "Գրանցվեք $1 -ում հենց հիմա՛"; "register_referer_meta_title" = "$1 -ն հրավիրում է ձեզ դեպի $2"; "register_referer_meta_desc" = "Միացե՛ք $1 -ին և բազմաթիվ օգտատերերին $2 -ու՛մ"; +"registration_welcome_1" = "յուրահատուկ գործընկերների որոնման գործիք է, հիմնված ՎԿոնտակտե–ի կառուցվաշքի վրա։"; +"registration_welcome_2" = "Մենք ցանկանում ենք, որպեսզի Ձեր ընկերները, դասարանցիները, հարևանները և գործընկերները միշտ մնան կապի մեջ։"; "users" = "Օգտատերեր"; +"other_fields" = "Այլ դաշտեր"; /* Profile information */ @@ -75,18 +79,20 @@ "female" = "իգական"; "description" = "Նկարագրություն"; "save" = "Պահպանել"; -"main_information" = "Հիմնական ինֆորմացիա"; +"main_information" = "Հիմնական տեղեկություն"; +"additional_information" = "Հավելյալ տեղեկություն"; "nickname" = "Մականուն"; "online" = "Օնլայն"; "was_online" = "եղել է ցանցում"; "was_online_m" = "եղել է ցանցում"; "was_online_f" = "եղել է ցանցում"; "all_title" = "Բոլորը"; -"information" = "Ինֆորմացիա"; +"information" = "Տեղեկություն"; "status" = "Կարգավիճակ"; -"no_information_provided" = "Ինֆորմացիան բացակայում է"; +"no_information_provided" = "Տեղեկությունը բացակայում է"; "deceased_person" = "Վախճանված"; "none" = "բացակայում է"; +"desc_none" = "առանց նկարագրության"; "send" = "ուղարկել"; "years_zero" = "Զրո տարեկան"; @@ -98,7 +104,7 @@ "show_my_birthday" = "Ցույց տալ ծննդյան օրը"; "show_only_month_and_day" = "Ցուցադրել միայն ամիսն ու օրը"; -"relationship" = "Ընտանեկան դրություն"; +"relationship" = "Կարգավիճակ"; "relationship_0" = "Ընտրված չէ"; "relationship_1" = "Չամուսնացած եմ"; @@ -128,7 +134,7 @@ "politViews_8" = "Ուլտրակոնսերվատիվ"; "politViews_9" = "Լիբերտարիանական"; -"contact_information" = "Կոնտակտային ինֆորմացիա"; +"contact_information" = "Կոնտակտային տեղեկատվություն"; "email" = "Էլեկտրոնային հասցե"; "phone" = "Հեռախոս"; @@ -137,7 +143,7 @@ "city" = "Քաղաք"; "address" = "Հասցե"; -"personal_information" = "Անձնական ինֆորմացիա"; +"personal_information" = "Անձնական տեղեկատվություն"; "interests" = "Հետաքրքրություններ"; "favorite_music" = "Սիրված երգ"; @@ -150,7 +156,7 @@ "updated_at" = "Թարմացված է $1"; "user_banned" = "Ցավո՛ք, մենք ստիպված կասեցրել ենք $1-ի էջը։"; -"user_banned_comment" = "Մոդերատորի մեկնաբանությունը․"; +"user_banned_comment" = "Մոդերատորի մեկնաբանությունը."; /* Wall */ @@ -163,6 +169,9 @@ "post_deact_f" = "ջնջել է էջը հետևյալ բառերով."; "post_deact_silent_m" = "սուս ու փուս ջնջել է էջը։"; "post_deact_silent_f" = "սուս ու փուս ջնջել է էջը։"; +"post_on_your_wall" = "Ձեր պատին"; +"post_on_group_wall" = "$1 –ին"; +"post_on_user_wall" = "$1 –ի պատին"; "wall" = "Պատ"; "post" = "Գրություն"; "write" = "Գրել"; @@ -194,9 +203,9 @@ "attachment" = "Հավելում"; "post_as_group" = "Խմբի անունից"; "comment_as_group" = "Մեկնաբանել խմբի անունից"; -"add_signature" = "Հեղինակի ստորագրություն"; +"add_signature" = "Ավելացնել ստորագրություն"; "contains_nsfw" = "Պարունակում է NSFW մատերիալ"; -"nsfw_warning" = "Այս պոստը կարող է պարունակել 18+ մատերիալ"; +"nsfw_warning" = "Այս գրությունը կարող է պարունակել 18+ մատերիալ"; "report" = "Բողոքարկել"; "attach" = "Ամրացնել"; "attach_photo" = "Ամրացնել նկար"; @@ -214,47 +223,37 @@ /* Friends */ "friends" = "Ընկերներ"; -"followers" = "Բաժանորդներ"; -"follower" = "Բաժանորդ"; +"followers" = "Հետևորդներ"; +"follower" = "Հետևորդ"; "friends_add" = "Ավելացնել դեպի ընկերներ"; "friends_delete" = "Հեռացնել ընկերներից"; "friends_reject" = "Չեղարկել հայտը"; "friends_accept" = "Ընդունել հայտը"; "send_message" = "Ուղարկել նամակ"; -"incoming_req" = "Բաժանորդներ"; -"outcoming_req" = "Հայցեր"; +"incoming_req" = "Սպասվող"; +"outcoming_req" = "Արտագնա"; "req" = "Հայցեր"; "friends_online" = "Ընկերները ցանցում"; "all_friends" = "Բոլոր ընկերները"; "req_zero" = "Ոչ մի հայտ չի գտնվել..."; "req_one" = "Գտնվեց մեկ հայտ"; -"req_few" = "Գտնվեց $1 հայտ"; -"req_many" = "Գտնվեց $1 հայտ"; "req_other" = "Գտնվեց $1 հայտ"; "friends_zero" = "Ոչ մի ընկեր չկա"; "friends_one" = "$1 ընկեր"; -"friends_few" = "$1 ընկեր"; -"friends_many" = "$1 հատ ընկեր"; "friends_other" = "$1 հատ ընկեր"; "friends_list_zero" = "Դուք դեռ չունեք ընկերներ"; "friends_list_one" = "Դուք ունեք մեկ ընկեր"; -"friends_list_few" = "Դուք ունեք $1 ընկեր"; -"friends_list_many" = "Դուք ունեք $1 ընկեր"; "friends_list_other" = "Դուք ունեք $1 ընկեր"; -"followers_zero" = "Ոչ մի բաժանորդ չունեք"; -"followers_one" = "$1 բաժանորդ"; -"followers_few" = "$1 բաժանորդ"; -"followers_many" = "$1 բաժանորդ"; -"followers_other" = "$1 բաժանորդ"; +"followers_zero" = "Ոչ մի հետևորդ չունեք"; +"followers_one" = "$1 հետևորդ"; +"followers_other" = "$1 հետևորդ"; "subscriptions_zero" = "Ոչ մեկի վրա չեք բաժանորդագրվել"; "subscriptions_one" = "$1 բաժանորդագրություն"; -"subscriptions_few" = "$1 բաժանորդագրություն"; -"subscriptions_many" = "$1 բաժանորդագրություն"; "subscriptions_other" = "$1 բաժանորդագրություն"; "friends_list_online_zero" = "Դուք դեռ չունեք ցանցի մեջ գտնվող ընկերներ"; @@ -269,7 +268,7 @@ "subscribe" = "Բաժանորդագրվել"; "unsubscribe" = "Հետ բաժանորդագրվել"; "subscriptions" = "Բաժանորդագրություններ"; -"join_community" = "Մտնել խումբ"; +"join_community" = "Միանալ խմբին"; "leave_community" = "Լքել խումբը"; "check_community" = "Դիտել խումբը"; "min_6_community" = "Անվանումը չպետք է լինի 6 նշից պակաս"; @@ -279,20 +278,19 @@ "create_group" = "Ստեղծել խումբ"; "group_managers" = "Ղեկավարություն"; "group_type" = "Խմբի տեսակ"; -"group_type_open" = "Սա բաց խումբ է․ այստեղ ամեն ոք կարող է մտնել։"; +"group_type_open" = "Սա բաց խումբ է․ ամեն ոք կարող է միանալ իրեն։"; "group_type_closed" = "Սա փակ խումբ է․ այստեղ միանալու համար անհրաժեշտ է հայտ թողնել։"; -"creator" = "Ստեղծող"; +"creator" = "Հեղինակ"; "administrators" = "Ադմինիստրատորներ"; -"add_to_left_menu" = "Ավելացնել դեպի ձախ մենյու"; -"remove_from_left_menu" = "Ջնջել ձախ մենյուից"; -"all_followers" = "Բոլոր բաժանորդները"; +"add_to_left_menu" = "Ավելացնել ձախ մենյույում"; +"remove_from_left_menu" = "Հեռացնել ձախ մենյուից"; +"all_followers" = "Բոլոր հետևորդները"; "only_administrators" = "Միայն ադմինիստրատորները"; -"website" = "Վեբկայք"; +"website" = "Կայք"; "managed" = "Կառավարվում է"; "size" = "Չափ"; "administrators_one" = "$1 ադմինիստրատոր"; -"administrators_few" = "$1 ադմինիստրատոր"; "administrators_other" = "$1 ադմինիստրատոր"; "role" = "Դեր"; @@ -301,30 +299,26 @@ "promote_to_owner" = "Դարձնել տեր"; "devote" = "Հետ բողոքարկել"; "set_comment" = "Փոփոխել մեկնաբանությունը"; -"hidden_yes" = "Թաքցված է"; -"hidden_no" = "Թաքցված չէ"; +"hidden_yes" = "Թաքնված է"; +"hidden_no" = "Թաքնված չէ"; "group_allow_post_for_everyone" = "Թույլատրել հրապարակել բոլորին"; "group_hide_from_global_feed" = "Չցույց տալ հրապարակությունները ընդհանուր լրահոսում։"; -"statistics" = "Ստատիստիկա"; +"statistics" = "Վիճակագրություն"; "group_administrators_list" = "Ադմինների ցուցակ"; -"group_display_only_creator" = "Ցույց տալ միայն խմբի ստեղծողին"; +"group_display_only_creator" = "Ցուցադրել միայն խմբի ստեղծողին"; "group_display_all_administrators" = "Ցուցադրել բոլոր ադմինիստրատորներին"; "group_dont_display_administrators_list" = "Ոչ մեկին ցույց չտալ"; -"group_changeowner_modal_title" = "Օգտատերի իրավասությունների փոխանցում"; -"group_changeowner_modal_text" = "Ուշադրությու՛ն։ Դուք փոխանցում եք խմբի բոլոր իրավունքները $1-ին։ Այս գործողությունը անդառնալի է։ Դուք էլի կմնաք ադմինիստրատոր, բայց հեշտությամբ դա ձեզնից կարող են խլել։"; -"group_owner_setted" = "Նոր տերը ($1) նշանակված է $2 միությունում։ Ձեզ տրված են ադմինիստրատորի իրավասություններ։ Եթե ուզում եք հետ բերել իրավասությունները, գրե՛ք կայքի տեխնիկական աջակցությանը։"; +"group_changeowner_modal_title" = "Սեփականատիրոջ իրավասությունների փոխանցում"; +"group_changeowner_modal_text" = "Ուշադրությու՛ն։ Դուք փոխանցում եք խմբի բոլոր իրավունքները $1-ին։ Այս գործողությունը անդառնալի է։ Դուք էլի կմնաք ադմինիստրատոր, բայց հեշտությամբ այդ դերը ձեզնից կարող են խլել։"; +"group_owner_setted" = "Նոր տերը ($1) նշանակված է $2 հանրությունում։ Ձեզ տրված են ադմինիստրատորի իրավասություններ։ Եթե ուզում եք հետ բերել իրավասությունները, գրե՛ք կայքի տեխնիկական աջակցությանը։"; "participants_zero" = "Ոչ մի մասնակից"; "participants_one" = "Միայն մեկ մասնակից"; -"participants_few" = "$1 մասնակից"; -"participants_many" = "$1 հատ մասնակից"; "participants_other" = "$1 հատ մասնակից"; "groups_zero" = "Ոչ մի խումբ"; "groups_one" = "Մեկ խումբ"; -"groups_few" = "$1 խումբ"; -"groups_many" = "$1 խումբ"; "groups_other" = "$1 խումբ"; "groups_list_zero" = "Դուք չկաք գեթ ոչ մի խմբում"; @@ -333,12 +327,10 @@ "meetings_zero" = "Ոչ մի հանդիպում"; "meetings_one" = "Մեկ հանդիպում"; -"meetings_few" = "$1 հանդիպում"; -"meetings_many" = "$1 հանդիպում"; "meetings_other" = "$1 հանդիպում"; "open_new_group" = "Նոր խումբ բացել"; -"open_group_desc" = "Չե՞ք կարող խումբ գտնել, բացեք ձերը․․․"; +"open_group_desc" = "Չե՞ք կարողանում գտնել ճիշտ խումբը, բացե՛ք ձերը․․․"; "search_group" = "Խմբի որոնում"; "search_by_groups" = "Որոնում ըստ խմբերի"; "search_group_desc" = "Այստեղ դուք կարող եք փնտրել խմբեր և ընտրել ձեզ ամենահարմարը․․․"; @@ -361,16 +353,36 @@ "albums_zero" = "Ոչ մի ալբոմ չկա"; "albums_one" = "Մեկ ալբոմ"; -"albums_few" = "$1 ալբոմ"; -"albums_many" = "$1 ալբոմ"; "albums_other" = "$1 ալբոմ"; "albums_list_zero" = "Դուք ոչ մի ալբոմ չունեք"; "albums_list_one" = "Դուք ունեք մեկ ալբոմ"; -"albums_list_few" = "Դուք ունեք $1 ալբոմ"; -"albums_list_many" = "Դուք ունեք $1 ալբոմ"; "albums_list_other" = "Դուք ունեք $1 ալբոմ"; +"add_image" = "Ավելացնել պատկեր"; +"add_image_group" = "Վերբեռնել պատկեր"; +"upload_new_picture" = "Ավելացնել նոր պատկեր"; +"uploading_new_image" = "Վերբեռնվում է նոր պատկերը․․․"; +"friends_avatar" = "Այն ավելի կհեշտացնի ձեր ընկերներին ճանաչել Ձեզ, եթե տեղադրեք ձեր իրական լուսանկարը։"; +"groups_avatar" = "Լավ պատկերը կարող է Ձեր խումբը ավելի ճանաչելի դարձնել։"; +"formats_avatar" = "Դուք կարող եք վերբեռնել պատկեր միայն JPG, GIF կամ PNG ֆորմատով։"; +"troubles_avatar" = "Եթե դժվարանում եք տեղադրելուց, փորձե՛ք ընտրել ավելի փոքր նկար։"; +"webcam_avatar" = "Եթե Ձեր համակարգիչը ունի վեբ–տեսախցիկ, Դուք կարող եք վերցնել նկար։"; + +"update_avatar_notification" = "Պրոֆիլի պատկերը փոփոխված է"; +"update_avatar_description" = "Սեղմեք դիտելու համար"; + +"deleting_avatar" = "Պատկերը ջնջվում է"; +"deleting_avatar_sure" = "Դուք վստա՞հ եք որ ցանկանում եք ջնջել պատկերը։"; + +"deleted_avatar_notification" = "Պատկերը հաջողությամբ ջնջվել է"; + +"save_changes" = "Պահպանել փոփոխությունները"; + +"upd_m" = "թարմացրել է իր պրոֆիլի պատկերը"; +"upd_f" = "թարմացրել է իր պրոֆիլի պատկերը"; +"upd_g" = "թարմացրել է իր խմբի պատկերը"; + /* Notes */ "notes" = "Նշումներ"; @@ -380,26 +392,35 @@ "create_note" = "Ստեղծել նշում"; "edit_note" = "Խմբագրել նշումը"; "actions" = "Գործողություններ"; + +"edited" = "Խմբագրված է"; + +"notes_zero" = "Ոչ մի նշում"; +"notes_one" = "Մեկ նշում"; +"notes_other" = "$1 նշում"; "notes_start_screen" = "Նշումների շնորհիվ Դուք կարող եք կիսվել ընկերների հետ տարբեր իրադարձություններով, և իմանալ թե ինչ է կատարվում իրենց մոտ։"; "note_preview" = "Նախադիտում"; "note_preview_warn" = "Սա ընդամենը նախադիտում է"; "note_preview_warn_details" = "Պահպանելուց հետո նշումները կարող են այլ տեսք ունենալ։ Ու մեկ էլ, այդքան հաճախ նախադիտում մի արեք։"; "note_preview_empty_err" = "Ինչու՞ նախադիտել նշումը առանց վերնագրի կամ բովանդակության։"; -"edited" = "Խմբագրված է"; - -"notes_zero" = "Ոչ մի նշում"; -"notes_one" = "Մեկ նշում"; -"notes_few" = "$1 նշում"; -"notes_many" = "$1 նշում"; -"notes_other" = "$1 նշում"; - "notes_list_zero" = "Ոչ մի նշում չի գտնվել"; "notes_list_one" = "Գտնվեց մեկ նշում"; -"notes_list_few" = "Գտնվեց $1 նշում"; -"notes_list_many" = "Գտնվեց $1 նշում"; "notes_list_other" = "Գտնվեց $1 նշում"; +"select_note" = "Ընտրվում է նշումը"; +"no_notes" = "Դուք չունե՛ք ոչ մի նշում"; + +"error_attaching_note" = "Խնդիր առաջացավ նշումը ընտրելիս"; + +"select_or_create_new" = "Ընտրե՛ք առկա նշումներից, կամ ստեղծե՛ք նորը"; + +"notes_closed" = "Դուք չե՛ք կարող ամրացնել նշումը հրապարակությանը, քանզի միայն Դուք եք տեսնում նրանք։
Դուք կարող եք փոխել դա կարգավորումներում։"; +"do_not_attach_note" = "Չամրացնել նշում"; + +/* Notes: Article Viewer */ +"aw_legacy_ui" = "Հին ինտերֆեյս"; + /* Menus */ "edit_button" = "խմբ."; @@ -415,8 +436,10 @@ "my_settings" = "Իմ կարգավորումները"; "bug_tracker" = "Բագ–թրեքեր"; +"menu_settings" = "Կարգավորումներ"; "menu_login" = "Մուտք"; "menu_registration" = "Գրանցում"; + "menu_help" = "Օգնություն"; "menu_logout" = "Դուրս գալ"; @@ -436,7 +459,7 @@ "left_menu_donate" = "Աջակցել"; "footer_about_instance" = "հոսքի մասին"; -"footer_rules" = "կանոնները"; +"footer_rules" = "կանոններ"; "footer_blog" = "բլոգ"; "footer_help" = "օգնություն"; "footer_developers" = "մշակողներին"; @@ -452,9 +475,9 @@ "interface" = "Արտաքին տեսք"; "security" = "Անվտանգություն"; -"profile_picture" = "Էջի նկար"; +"profile_picture" = "Էջի պատկեր"; -"picture" = "Նկար"; +"picture" = "Պատկեր"; "change_password" = "Փոխել գաղտնաբառը"; "old_password" = "Հին գաղտնաբառը"; @@ -473,13 +496,17 @@ "apply_style_for_this_device" = "Հաստատել տեսքը միայն այս սարքի համար"; "search_for_groups" = "Խմբերի որոնում"; -"search_for_people" = "Մարդկանց որոնում"; +"search_for_users" = "Մարդկանց որոնում"; +"search_for_posts" = "Հրապարակումների որոնում"; +"search_for_comments" = "Մեկնաբանությունների որոնում"; +"search_for_videos" = "Վիդեոների որոնում"; +"search_for_apps" = "Հավելվածների որոնում"; +"search_for_notes" = "Նշումների որոնում"; +"search_for_audios" = "Երաժշտության որոնում"; "search_button" = "Որոնել"; "search_placeholder" = "Գրեք ցանկացած անուն, անվանում կամ բառ"; "results_zero" = "Ոչ մի արդյունք"; "results_one" = "Մեկ արդյունք"; -"results_few" = "$1 արդյունք"; -"results_many" = "$1 արդյունք"; "results_other" = "$1 արդյունք"; "privacy_setting_access_page" = "Ով կարող է տեսնել ինտերնետում իմ էջը"; @@ -504,9 +531,9 @@ "your_email_address" = "Ձեր էլեկտրոնային հասցեն"; "your_page_address" = "Ձեր էջի հասցեն"; "page_address" = "Էջի հասցեն"; -"current_email_address" = "Ներկայիս հասցեն"; -"new_email_address" = "Նոր հասցեն"; -"save_email_address" = "Պահպանել հասցեն"; +"current_email_address" = "Ներկայիս էլեկտրոնային հասցեն"; +"new_email_address" = "Նոր էլեկտրոնային հասցեն"; +"save_email_address" = "Պահպանել էլեկտրոնային հասցեն"; "page_id" = "Էջի ID–ն"; "you_can_also" = "Դուք նաև կարող եք"; "delete_your_page" = "ջնջել ձեր էջը"; @@ -518,13 +545,14 @@ "ui_settings_rating_show" = "Ցուցադրել"; "ui_settings_rating_hide" = "Թաքցնել"; "ui_settings_nsfw_content" = "NSFW-կոնտենտ"; -"ui_settings_nsfw_content_dont_show" = "Ցույց չտա՛լ գլոբալ ժապավենում"; +"ui_settings_nsfw_content_dont_show" = "Ցույց չտա՛լ ընդհանուր ժապավենում"; "ui_settings_nsfw_content_blur" = "Միայն ներծծել"; "ui_settings_nsfw_content_show" = "Ցույց տալ"; -"ui_settings_view_of_posts" = "Փոսթերի տեսակ"; +"ui_settings_view_of_posts" = "Հրապարակումների տեսք"; "ui_settings_view_of_posts_old" = "Հին"; "ui_settings_view_of_posts_microblog" = "Միկրոբլոգ"; "ui_settings_main_page" = "Գլխավոր էջ"; +"ui_settings_sessions" = "Այցելություններ"; "additional_links" = "Հավելյալ հղումներ"; "ad_poster" = "Գովազդային վահանակ"; @@ -547,7 +575,7 @@ "profile_deactivate_reason_5_text" = "Ինձ այստեղ շան տեղ դնող չկա ու ես տխրում եմ։ Դուք կզղջաք որ ես հեռացա..."; "profile_deactivate_reason_6" = "Այլ պատճառ"; -"profile_deactivated_msg" = "Ձեր էջը ջնջված է։

Եթե Դուք ուզենաք նորից օգտվել Ձեր էջով, կարող եք ապաակտիվացնել այն մինչև $1:"; +"profile_deactivated_msg" = "Ձեր էջը ջնջված է։

Եթե Դուք ուզենաք նորից օգտվել կայքով, ապա կարող եք ապաակտիվացնել այն մինչև $1:"; "profile_deactivated_status" = "Էջը ջնջված է"; "profile_deactivated_info" = "Օգտատիրոջ էջը հեռացվել է։
Մանրամասն տեղեկատվությունը բացակայում է։"; @@ -557,6 +585,18 @@ "end_all_sessions_description" = "Եթե ցանկանում եք դուրս գալ $1–ից ամեն դեվայսից, սեղմե՛ք ներքևի կոճակը"; "end_all_sessions_done" = "Բոլոր սեսսիաները նետված են, ներառյալ բջջային հավելվածները"; +"backdrop_short" = "Ետնապատկեր"; +"backdrop" = "Էջի ետնապատկեր"; +"backdrop_desc" = "Դուք կարող եք տեղադրել երկու նկար, որպես Ձեր պրոֆիլի կամ խմբի նախանկար։ Նրանք կցուցադրվեն էջի ձախ և աջ ծայրերում։ Այս հարմարանքի շնորհիվ Դուք կարող եք ավելացնել հավելյալ անհատականություն Ձեր պրոֆիլին։"; +"backdrop_warn" = "Նկարները կկազմակերպվեն ըստ վերևի դասակարգման։ Իրենց բարձրությունը ավտոմատ կընդլայնվի, ու նրանք կզբաղեցնեն էկրանի բարձրության 100%-ը, նաև կավելացվի մեջտեղում լղոզում։ Այն անհնար կլինի փոխարինել փոխարինել ետնապատկերը OpenVK-ի հիմնական ինտերֆեյսով կամ ավելացնել աուդիո։"; +"backdrop_about_adding" = "Դուք կարող եք ավելացնել միայն մեկ նկար, կախված դիզայնից, վերջնական արդյունքը կարող է տգեղ տեսք ունենալ։ Դուք նաև կարող եք փոխել միայն մեկ նկար. եթե արդեն ունեք երկու տեղադրված նկար և ուզում եք փոխել մեկը – վերբեռնեք միայն մեկ անգամ, ուսի մյուսները չեն ջնջվի։ Որպեսզի ջնջեք երկու նկարները, սեղմե՛ք ներքևի կոճակը, դուք չե՛ք կարող ջնջել նկարները առանձին։"; +"backdrop_save" = "Պահպանել ետնապակներները"; +"backdrop_remove" = "Ջնջել բոլոր ետնապակներները"; +"backdrop_error_title" = "Խնդիր առաջացավ ՝ ետնապակներները պահպանելիս"; +"backdrop_error_no_media" = "Նկարները վնասված են կամ լիարժեք չեն տեղադրվել"; +"backdrop_succ" = "Ետնապատկերի կարգավորումները պահպանված են"; +"backdrop_succ_rem" = "Ետնապատկերները ջնջվեցին"; +"backdrop_succ_desc" = "Օգտատերերը կտեսնեն փոփոխությունները 5 րոպեյվա ընթացքում"; "browse" = "Վերանայում"; /* Two-factor authentication */ @@ -612,11 +652,9 @@ "videos_zero" = "Ոչ մի տեսանյութ չկա"; "videos_one" = "Մեկ տեսանյութ"; -"videos_few" = "$1 տեսանյութ"; -"videos_many" = "$1 տեսանյութ"; "videos_other" = "$1 տեսանյութ"; -"view_video" = "Դիտում"; +"view_video" = "Դիտել"; /* Notifications */ @@ -649,26 +687,34 @@ "nt_photo_instrumental" = "նկարով"; "nt_topic_instrumental" = "թեմայով"; +"nt_you_were_mentioned_u" = "Ձեզ նշել է օգտատերը"; +"nt_you_were_mentioned_g" = "Ձեզ նշել է խումբը"; +"nt_mention_in_post_or_comms" = "իր քննարկման թեմաներից մեկում"; +"nt_mention_in_photo" = "այս նկարի քննարկմանը"; +"nt_mention_in_video" = "այս վիդեոյի քննարկմանը"; +"nt_mention_in_note" = "այս նշման քննարկմանը"; +"nt_mention_in_topic" = "այս քննարկմանը"; + /* Time */ -"time_at_sp" = " -ում "; +"time_at_sp" = " ՝ "; "time_just_now" = "հենց նոր"; "time_exactly_five_minutes_ago" = "ուղիղ հինգ րոպե առաջ"; "time_minutes_ago" = "$1 րոպե առաջ"; "time_today" = "այսօր"; "time_yesterday" = "երեկ"; -"points" = "Ձայն"; +"points" = "Ձայներ"; "points_count" = "ձայն"; "on_your_account" = "ձեր հաշվում"; -"top_up_your_account" = "Լիցքավորել բալանսը"; +"top_up_your_account" = "Ստանալ ավելին"; "you_still_have_x_points" = "Դուք ունեք $1 չօգտագործված ձայն։"; "vouchers" = "Վաուչերներ"; "have_voucher" = "Կա վաուչեր"; "voucher_token" = "Վաուչերի կոդ"; "voucher_activators" = "Օգտագործվածները"; -"voucher_explanation" = "Գրե՛ք վաուչերի սերիական համարը։ Սովորաբար այն նշված է լինում կտրոնի կամ նամակի մեջ։"; +"voucher_explanation" = "Ներմուծե՛ք վաուչերի սերիական համարը։ Սովորաբար այն նշված է լինում կտրոնի կամ նամակի մեջ։"; "voucher_explanation_ex" = "Ուշադրություն դարձրե՛ք, որ վաուչերները կարող են սպառվել և օգտագործվել միայն մեկ անգամ։"; "invalid_voucher" = "Անվավեր վաուչեր"; "voucher_bad" = "Հնարավոր է, դուք ներմուծել եք սխալ վաուչեր, արդեն օգտագործել եք այն կամ էլ այն սպառվել է։"; @@ -677,14 +723,12 @@ "redeem" = "Ակտիվացնել վաուչերը"; "deactivate" = "Դեակտիվացնել"; "usages_total" = "Օգտագործումների քանակ"; -"usages_left" = "Մնացին օգտագործումներ"; +"usages_left" = "Մնաց օգտագործելու"; "points_transfer_dialog_header_1" = "Դուք կարող եք ուղարկել ձայները և նվերների մի մասը այլ մարդուն։"; -"points_transfer_dialog_header_2" = "Ձեր ներկայիս բալանսը․ "; +"points_transfer_dialog_header_2" = "Ձեր ներկայիս բալանսը."; "points_amount_one" = "Մեկ ձայն"; -"points_amount_few" = "$1 ձայն"; -"points_amount_many" = "$1 ձայն"; "points_amount_other" = "$1 ձայն"; "transfer_poins" = "Ձայների փոխանցում"; @@ -732,13 +776,9 @@ "gifts" = "Նվերներ"; "gifts_zero" = "Նվերներ չկան"; "gifts_one" = "Մեկ նվեր"; -"gifts_few" = "$1 նվեր"; -"gifts_many" = "$1 նվեր"; "gifts_other" = "$1 նվեր"; "gifts_left" = "Մնաց $1 նվեր"; "gifts_left_one" = "Մնաց մեկ նվեր"; -"gifts_left_few" = "$1 նվեր մնաց"; -"gifts_left_many" = "$1 նվեր մնաց"; "gifts_left_other" = "$1 նվեր մնաց"; "send_gift" = "Ուղարկել նվեր"; @@ -753,8 +793,6 @@ "coins" = "Ձայներ"; "coins_zero" = "0 ձայն"; "coins_one" = "Մեկ ձայն"; -"coins_few" = "$1 ձայն"; -"coins_many" = "$1 ձայն"; "coins_other" = "$1 ձայն"; "users_gifts" = "Նվերներ"; @@ -838,7 +876,7 @@ "support_status_0" = "Հարցը դիտարկման տակ է"; "support_status_1" = "Կա պատասխան"; "support_status_2" = "Փակ է"; -"support_greeting_hi" = "Բարև ձեզ, $1!"; +"support_greeting_hi" = "Բարև՜ Ձեզ, $1"; "support_greeting_regards" = "Հարգանքով,
$1 -ի աջակցման թիմ։"; "support_faq" = "Հաճախ տրվող հարցեր"; @@ -860,6 +898,12 @@ "fast_answers" = "Արագ պատասխաններ"; +"ignore_report" = "Արհամարել զեկույցը"; +"report_number" = "Զեկույց #"; +"list_of_reports" = "Զեկույցների ցանկ"; +"text_of_the_post" = "Հրապարակման տեքստ"; +"today" = "այսօր"; + "comment" = "Մեկնաբանություն"; "sender" = "Ուղարկող"; @@ -874,6 +918,13 @@ "banned_in_support_1" = "Կներե՛ք, $1, բայց հիմա Ձեզ թույլատրված չէ դիմումներ ստեղծել։"; "banned_in_support_2" = "Դրա պատճառաբանությունը սա է․ $1։ Ցավո՛ք, այդ հնարավորությունը մենք Ձեզնից վերցրել ենք առհավետ։"; +"you_can_close_this_ticket_1" = "Եթե չունեք այլատիպ հարցեր, ապա կարող եք "; +"you_can_close_this_ticket_2" = "փակել այս դիմումը"; +"agent_profile_created_1" = "Պրոֆիլը ստեղծված է"; +"agent_profile_created_2" = "Հիմա օգտատերերը կարող են տեսնել Ձեր անհատականեցված անունը և ավատարը ՝ սովորականների փոխարեն։"; +"agent_profile_edited" = "Պրոֆիլը ստեղծված է"; +"agent_profile" = "Իմ Գործակալի քարտը"; + /* Invite */ "invite" = "Հրավիրել"; @@ -914,19 +965,47 @@ "messages_error_1" = "Նամակը չի ուղարկվել"; "messages_error_1_description" = "Այս նամակը ուղարկելու ժամանակ տեղի է ունեցել ընդհանրացված սխալ..."; +/* Polls */ +"poll" = "Քվեարկություն"; +"create_poll" = "Ստեղծել քվեարկություն"; +"poll_title" = "Հարց տալ"; +"poll_add_option" = "Ավելացնել ընտրություն..."; +"poll_anonymous" = "Գաղտնի քվեարկումներ"; +"poll_multiple" = "Տարբեր պատասխաններ"; +"poll_locked" = "Վիկտորինայի ռեժիմ (առանց վերանայման)"; +"poll_edit_expires" = "Սպառվում է. "; +"poll_edit_expires_days" = "օր"; +"poll_editor_tips" = "Backspace խփելը դատարկ ընտրությունը ջնջելու է։ Օգտագործե՛ք Tab/Enter դատարկ ընտրությանը, որպեսզի այն ավելի արագ ստեղծեք։"; +"poll_embed" = "Ներկառուցված կոդ"; + +"poll_voter_count_zero" = "Եղեք առաջի՛ն քվեարկողը"; +"poll_voter_count_one" = "Միայն մեկ օգտատեր է քվեարկել"; +"poll_voter_count_few" = "Քվեարկեց $1 հոգի"; +"poll_voter_count_many" = "Քվեարկեց $1 հոգի"; +"poll_voter_count_other" = "Քվեարկեց $1 հոգի"; + +"poll_voters_list" = "Քվեարկողներ"; + +"poll_anon" = "Գաղտնի"; +"poll_public" = "Հանրային"; +"poll_multi" = "տարբեր ընտրություններ"; +"poll_lock" = "առանց հետ կանչելու"; +"poll_until" = "մինչև $1"; + +"poll_err_to_much_options" = "Չափից շատ տարբերակ է տրված։"; +"poll_err_anonymous" = "Չի լինում տեսնել քվեարկողների ցանկը. քվեարկությունը գաղտնի է"; +"cast_vote" = "Քվեարկե՛լ"; +"retract_vote" = "Չեղարկել իմ քվեարկումը"; + /* Discussions */ "discussions" = "Քննարկումներ"; "messages_one" = "Մեկ նամակ"; -"messages_few" = "$1 նամակ"; -"messages_many" = "$1 նամակ"; "messages_other" = "$1 նամակ"; "topic_messages_count_zero" = "Թեմայում նամակ չկա"; "topic_messages_count_one" = "Թեմայում մեկ նամակ է"; -"topic_messages_count_few" = "Թեմայում $1 նամակ կա"; -"topic_messages_count_many" = "Թեմայում $1 նամակ կա"; "topic_messages_count_other" = "Թեմայում $1 նամակ կա"; "replied" = "պատասխանել է"; @@ -945,11 +1024,9 @@ "delete_topic" = "Ջնջել թեման"; "topics_one" = "Մեկ թեմա"; -"topics_few" = "$1 թեմա"; -"topics_many" = "$1 թեմա"; "topics_other" = "$1 թեմա"; -"created" = "Ստեղծված է"; +"created" = "Ստեղծվել է"; "everyone_can_create_topics" = "Բոլորը կարող են թեմաներ սարքել"; "display_list_of_topics_above_wall" = "Ցուցադրել պատի տակ թեմաների ցուցակը"; @@ -978,8 +1055,10 @@ "error_upload_failed" = "Չհաջողվեց վերբեռնել նկարը"; "error_old_password" = "Հին գաղտնաբառը չի համընկնում"; "error_new_password" = "Նոր գաղտնաբառերը չեն համընկնում"; +"error_weak_password" = "Գաղտնաբառը այդքան էլ խիստ չէ։ Այն առնվազն պետք է պարունակի 8 նիշ, մեկ մեծատառ տառ և մեկ թիվ։"; "error_shorturl_incorrect" = "Կարճ հասցեն ունի սխալ ֆորմատ"; -"error_repost_fail" = "Չհաջողվեց կիսվել գրության հետ"; +"error_repost_fail" = "Չհաջողվեց կիսվել գրությունով"; +"error_data_too_big" = "'$1' ատտրիբուտը պետք է առնվազն լինի $2 –ը $3 –ի չափ երկար"; "forbidden" = "Հասանելիության սխալ"; "forbidden_comment" = "Այս օգտատիրոջ գաղտնիության կարգավորումները ձեզ թույլ չեն տալիս դիտել օգտատերի էջը։"; @@ -1015,7 +1094,7 @@ "suspicious_registration_attempt_comment" = "Դուք մի տեսակ փորձել եք սխալ տեղից գրանցվել։"; "rate_limit_error" = "Հե՛յ, կարող ա՞ խառնել ես։"; -"rate_limit_error_comment" = "Ա՛յ $1, չի՛ կարելի այսքան հաճախ սպամ հրապարակել։ Հո դու Կառլենը չե՞ս։ Բացառության կոդ․ $2։"; +"rate_limit_error_comment" = "Ա՛յ $1 ջան, չի՛ կարելի այսքան հաճախ սպամել։ Հո դու Գրիգորիսը չե՞ս։ Բացառության կոդ․ $2։"; "not_enough_permissions" = "Այդքան իրավասություն չկա"; "not_enough_permissions_comment" = "Դուք բավական իրավասություն չունեք այս գործողությունը կատարելու համար։"; @@ -1047,7 +1126,7 @@ /* Admin panel */ -"admin" = "Ադմին-վահանակ"; +"admin" = "Ադմինի վահանակ"; "admin_ownerid" = "Օգտատիրոջ ID"; "admin_author" = "Հեղինակ"; @@ -1124,7 +1203,7 @@ "admin_banned_links" = "Արգելափակված հղումներ"; "admin_banned_link" = "Հղում"; "admin_banned_domain" = "Դոմեն"; -"admin_banned_link_description" = "Պրոտոկոլով (https://example.com/)"; +"admin_banned_link_description" = "Պրոտոկոլով (https://example.am/)"; "admin_banned_link_regexp" = "Ռեգուլյար արտահայտություն"; "admin_banned_link_regexp_description" = "Տեղադրվում է վերոնշյալ դոմենից հետո։ Մի լրացրե՛ք, եթե ցանկանում եք արգելափակել ամբողջ դոմենը"; "admin_banned_link_reason" = "Պատճառ"; @@ -1132,6 +1211,15 @@ "admin_banned_link_not_specified" = "Հղումը նշված չէ"; "admin_banned_link_not_found" = "Հղումը չի գտնվել"; +"logs_adding" = "Ստեղծում"; +"logs_editing" = "Խմբագրում"; +"logs_removing" = "Ջնջում"; +"logs_restoring" = "Վերականգնում"; +"logs_added" = "ստեղծվել է"; +"logs_edited" = "խմբագրվել է"; +"logs_removed" = "ջնջվել է"; +"logs_restored" = "վերականգնվել է"; + /* Paginator (deprecated) */ "paginator_back" = "Հետ"; @@ -1150,29 +1238,20 @@ "instance_links" = "Հոսքերի հղումներ․"; "about_users_one" = "Մեկ օգտատեր"; -"about_users_few" = "$1 օգտատեր"; -"about_users_many" = "$1 օգտատեր"; "about_users_other" = "$1 օգտատեր"; "about_online_users_one" = "Մեկ օգտատեր է ցանցի մեջ"; -"about_online_users_few" = "$1 օգտատեր է ցանցի մեջ"; -"about_online_users_many" = "$1 օգտատեր է ցանցի մեջ"; "about_online_users_other" = "$1 օգտատեր է ցանցի մեջ"; "about_active_users_one" = "Մեկ ակտիվ օգտատեր"; -"about_active_users_few" = "$1 ակտիվ օգտատեր"; -"about_active_users_many" = "$1 ակտիվ օգտատեր"; "about_active_users_other" = "$1 ակտիվ օգտատեր"; "about_groups_one" = "Մեկ խումբ"; -"about_groups_few" = "$1 խումբ"; -"about_groups_many" = "$1 խումբ"; "about_groups_other" = "$1 խումբ"; "about_wall_posts_one" = "Մեկ գրություն պատերի վրա"; -"about_wall_posts_few" = "$1 գրություն պատերի վրա"; -"about_wall_posts_many" = "$1 գրություն պատերի վրա"; "about_wall_posts_other" = "$1 գրություն պատերի վրա"; + "about_watch_rules" = "տես այստեղ․"; /* Dialogs */ @@ -1191,10 +1270,11 @@ /* User alerts */ "user_alert_scam" = "Այս հաշվի վրա բազմաթիվ բողոքներ են եկել խարդախության հետ կապված։ Խնդրվում է զգույշ լինել, հատկապես եթե Ձեզնից փորձեն գումար խնդրել և շորթել։"; +"user_may_not_reply" = "Այս օգտատերը կարող է Ձեզ չպատասխանել, ձեր անվտանգության կարգավորումների պատճառով։ Բացել անվտանգության կարգավորումները"; /* Cookies pop-up */ -"cookies_popup_content" = "Cookie բառը անգլերենից նշանակում է թխվածքաբլիթ, իսկ թխվածքաբլիթը համեղ է։ Մեր կայքը չի ուտում թխվածք, բայց օգտագործում է այն ուղղակի սեսսիան կողմնորոշելու համար։ Ավելի մանրամասն կարող եք ծանոթանալ մեր գաղտնիության քաղաքականությանը հավելյալ ինֆորմացիայի համար։"; +"cookies_popup_content" = "Cookie բառը թարգմանաբար նշանակում է թխվածքաբլիթ, իսկ թխվածքաբլիթը լավ բան է։ Մեր կայքը չի ուտում թխվածք, բայց օգտագործում է այն ՝ այցելությունը կողմնորոշելու համար։ Ավելի մանրամասն կարող եք ծանոթանալ մեր գաղտնիության քաղաքականությանը հավելյալ ինֆորմացիայի համար։"; "cookies_popup_agree" = "Համաձայն եմ"; /* Away */ @@ -1206,6 +1286,42 @@ "url_is_banned_title" = "Հղում դեպի կասկածելի կայք"; "url_is_banned_proceed" = "Անցնել հղումով"; +"recently" = "Վերջերս"; + +/* Helpdesk */ + +"helpdesk" = "Աջակցում"; +"helpdesk_agent" = "Աջակցման գործակալ"; +"helpdesk_agent_card" = "Գործակալի քարտ"; +"helpdesk_positive_answers" = "դրական պատասխաններ"; +"helpdesk_negative_answers" = "բացասական պատասխաններ"; +"helpdesk_all_answers" = "բոլոր պատասխանները"; +"helpdesk_showing_name" = "Ցուցադրվող անունը"; +"helpdesk_show_number" = "Ցույց տալ թիվը"; +"helpdesk_avatar_url" = "Ավատարի հղումը"; + +/* Chandler */ + +"c_user_removed_from_group" = "Այս օգտատերը հեռացվել է խմբից"; +"c_permission_removed_from_group" = "Թույլտվությունը հեռացվել է խմբից"; +"c_group_removed" = "Խումբը ջնջվել է"; +"c_groups" = "Chandler–ի Խմբեր"; +"c_users" = "Chandler–ի Օգտատերեր"; +"c_group_permissions" = "Իրավասություններ"; +"c_group_members" = "Մասնակիցներ"; +"c_model" = "Մոդել"; +"c_permission" = "Իրավասություն"; +"c_permissions" = "Իրավասություններ"; +"c_color" = "Գույն"; +"add" = "Ավելացնել"; +"c_edit_groups" = "Խմբագրել Խմբերը"; +"c_user_is_not_in_group" = "Օգտատիրոջ և խմբի հանդեպ հարաբերությունները չգտնվեցին։"; +"c_permission_not_found" = "Իրավասության և խմբի հանդեպ հարաբերությունները չգտնվեցին։"; +"c_group_not_found" = "Խումբը չգտնվե՛ց։"; +"c_user_is_already_in_group" = "Այս օգտատերը արդեն խմբի անդամ է։"; +"c_add_to_group" = "Ավելացնել խմբին"; +"c_remove_from_group" = "Հեռացնել խմբից"; + /* Maintenance */ "global_maintenance" = "Տեխնիկական աշխատանքներ"; @@ -1214,3 +1330,232 @@ "undergoing_section_maintenance" = "Ցավոք սրտի, $1 բաժինը ժամանակավորապես անհասանելի է։ Մենք արդեն աշխատում ենք խնդիրները շտկելու ուղղությամբ։ Խնդրում ենք այցելել մի քիչ ուշ։"; "topics" = "Թեմաներ"; + + +/* Tutorial */ + +"tour_title" = "Կայքի ճամփորդություն"; +"reg_title" = "Գրանցում"; +"ifnotlike_title" = " "Ի՞նչ եթե ինձ այս կայքը դուր չի գալիս։" "; +"tour_promo" = "Ինչ է Ձեզ սպասվում գրանցումից հետո"; + +"reg_text" = "Օգտատիրոջ գրանցումը լրիվ անվճար է և տևում է երկու րոպեյից ոչ ավել։"; +"ifnotlike_text" = "Դուք միշտ կարող եք ջնջել Ձեր հաշիվը"; + + +"tour_next" = "Հաջորդը →"; +"tour_reg" = "Գրանցում →"; + + +"tour_section_1" = "Սկիզբ"; +"tour_section_2" = "Պրոֆիլ"; +"tour_section_3" = "Նկարներ"; +"tour_section_4" = "Որոնում"; +"tour_section_5" = "Վիդեոներ"; +"tour_section_6" = "Աուդիոներ"; +"tour_section_7" = "Հիմնական լուրերի ժապավեն"; +"tour_section_8" = "Ընդհանուր լուրերի ժապավեն"; +"tour_section_9" = "Խմբեր"; +"tour_section_10" = "Իրադարձություններ"; +"tour_section_11" = "Թեմաներ"; +"tour_section_12" = "Անհատականացում"; +"tour_section_13" = "Պրոմոկոդեր"; +"tour_section_14" = "Հեռախոսի տարբերակ"; + + +"tour_section_1_title_1" = "Որտեղի՞ց սկսել"; +"tour_section_1_text_1" = "Օգտատիրոջ գրանցումը ամենաառաջին քայլն է այստեղ Ձեր ճանապարհը սկսելու համար։"; +"tour_section_1_text_2" = "Գրանցվելու համար պետք է ունենալ էլ. հասցե և գաղտնաբառ։"; +"tour_section_1_text_3" = "Հիշե՛ք. Ձեր էլ. հասցեն կօգտագործվի կայք մուտք գործելու համար։ Դուք նաև կունենաք լիիրավ իրավունք չնշել Ձեր ազգանունը գրանցվելիս։ Եթե հանկարծ կորցնեք մուտք գործելու գաղտնաբառը, միշտ կարող եք օգտվել վերականգնման էջից։"; +"tour_section_1_bottom_text_1" = "Գրանցվելով այս կայքում, Դուք համաձայնվում եք կայքի կանոններին և գաղտնիության քաղաքականությանը։"; + +"tour_section_2_title_1" = "Ձեր պրոֆիլը"; +"tour_section_2_text_1_1" = "Գրանցվելուց հետո Դուք ավտոմատ կերպով կվերահղվեք դեպի ձեր էջը:"; +"tour_section_2_text_1_2" = "Դուք կարող եք խմբագրել այն որտեղ և երբ ցանկանաք։"; +"tour_section_2_text_1_3" = "Ակնարկ. Որպեսզի Ձեր պրոֆիլը թույն ու ներկայանալի տեսք ունենա, կարող եք այն լրացնել տեղեկությամբ, կամ էլ տեղադրել լուսանկար, որը օրինակի համար ցույց է տալիս Ձեր վերաբերմունքը Կարգին Հաղորդմանը։"; +"tour_section_2_bottom_text_1" = "Դուք եք միայն որոշում ինչքան տեղեկություն կարող են իմանալ Ձեր մասին ընկերները։"; +"tour_section_2_title_2" = "Տեղադրել սեփական անվտանգության կարգավորումները։"; +"tour_section_2_text_2_1" = "Դուք կարող եք սահմանել թե ինչ տեսակի տեղեկություն կարող է երևալ Ձեր էջում։"; +"tour_section_2_text_2_2" = "Դուք իրավունք ունեք բլոկավորել հսանելիությունը Ձեր էջին որոնողական համակարգերից ու չգրանցված օգտատերերից։"; +"tour_section_2_text_2_3" = "Հիշե՛ք. հետագայում անվտանգության կարգավորումները կընդլայնվեն։"; +"tour_section_2_title_3" = "Պրոֆիլի URL"; +"tour_section_2_text_3_1" = "էջը գրանցելուն պես Դուք ստանում եք անձնական ID, ասենք ՝ @id12345"; +"tour_section_2_text_3_2" = "Սովորական ID-ն, որը ստացվում է գրանցումից հետո, մնում է անփոփոխ"; +"tour_section_2_text_3_3" = "Բայց Ձեր էջի կարգավորումներում Դուք կարող եք տեղադրել անձնական հասցեն, և այն կարող է փոխվել երբ կամենաք"; +"tour_section_2_text_3_4" = "Ակնարկ. Դուք կարող եք վերցնել ցանկացած հասցե, որը առնվազն 5 նիշանի է։ Փորձե՛ք վերցնել թույն URL :Ճ"; +"tour_section_2_bottom_text_2" = "Ցանկացած կարճ հասցե լատինատառ փոքրատառ տառերով սպասարկվում է; այն կարող է պարունակել թվեր (ոչ սկզբում), կետեր և ընդգծումնր (ոչ սկզբում կամ վերջում)"; +"tour_section_2_title_4" = "Պատ"; + + +"tour_section_3_title_1" = "Կիսվե՛ք Ձեր կյանքի պահերով"; +"tour_section_3_text_1" = ""Լուսանկարների" բաժինը հասանելի է Ձեզ անմիջապես գրանցումից հետո"; +"tour_section_3_text_2" = "Դուք կարող եք դիտել օգտատիրոջ ալբոմները կամ էլ ստեղծել ձերը"; +"tour_section_3_text_3" = "Հասանելիություն տալ բոլոր ալբոմներին ուրիշներին, ինչը կառավարվում է Ձեր էջի անվտանգության կարգավորումներով"; +"tour_section_3_bottom_text_1" = "Դուք կարող եք ստեղծել անսահմանափակ քանակությամբ ալբոմներ, ճամփորդության կամ հանգստի համար, կամ էլ զուտ մեմերի համար"; + + +"tour_section_4_title_1" = "Որոնում"; +"tour_section_4_text_1" = ""Որոնման" բաժինը թույլ է տալիս փնտրել մարդկանց և խմբերը։"; +"tour_section_4_text_2" = "Կայքի հենց այս բաժինը ժամանակի ընթացքում ընդլայնվում է"; +"tour_section_4_text_3" = "Որպեսզի սկսեք որոնելը, Դուք պետք է իմանաք օգտատիրոջ անունը (կամ ազգանունը), և եթե Ձեզ հետաքրքիր ա խումբ ճարելը, ապա պետք է ճշտել իր անունը։"; +"tour_section_4_title_2" = "Արագ որոնում"; +"tour_section_4_text_4" = "Եթե ուզում եք խնայել ժամանակ, որոնման բարը միշտ հասանելի է կայքի վերնամասում"; + + +"tour_section_5_title_1" = "Վերբեռնե՛ք և կիսվե՛ք վիդեոներով ընկերների հետ"; +"tour_section_5_text_1" = "Դուք կարող եք տեղադրել անսահմանափակ վիդեոներ և կարճ հոլովակներ"; +"tour_section_5_text_2" = ""Վիդեոների" բաժինը ղեկավարվում է անվտանգության կարգավորումներով"; +"tour_section_5_bottom_text_1" = "Վիդեոները կարող են տեղադրվել անցնելով "Վիդեոների" բաժինը, ուղղակի կցելով դրանք պատին."; +"tour_section_5_title_2" = "YouTube-ից վիդեոների ներկրում"; +"tour_section_5_text_3" = "Ուղիղ տեղադրումից բացի, նաև կարող եք ամրացնել Ձեր սիրելի YouTube–յան վիդեոների հղումները"; + + +"tour_section_6_title_1" = "Աուդիոների բաժինը, որը հլը չկա բհահահսհդ xDDD հորս արևևև"; +"tour_section_6_text_1" = "Ինչպես ասվում էր Կարգին Հաղորդումում. «ապե մի քիչ էլ պահի ստե բան չի երևում»։ Վատ չէր լինի պատմել այս բաժնի մասին, բայց Վրիսկա ախպերը բեսամթ ալարել ա էս սարքել (նենց լավ ա էլի :Ճ):"; + + +"tour_section_7_title_1" = "Հետևե՛ք թե ինչ են գրում Ձեր ընկերներ"; +"tour_section_7_text_1" = ""Լուրերի" բաժինը բաժանվում է երկու տիպի. հիմնական և ընդհանուր ժապավենների"; +"tour_section_7_text_2" = "Հիմնական ժապավենը ցուցադրում է Ձեր ընկերների ու խմբերի նորությունները"; +"tour_section_7_bottom_text_1" = "Մենք չենք սարքում Ձեր լուրերի ժապավենը։ Դուք եք ստեղծում այն։"; + + +"tour_section_8_title_1" = "Հետևե՛ք այստեղ քննարկվող թեմաներին"; +"tour_section_8_text_1" = "Ընդհանուր ժապավենը ցուցադրում է բոլոր օգտատերերի և խմբերի թեմաները"; +"tour_section_8_text_2" = "Այս բաժինը խորհուրդ չի տրվում դիտել նյարդայիններին, հղիներին ու Վարդան Ղուկասյանին լսողներին"; +"tour_section_8_bottom_text_1" = "Ընդհանուր ժապավենի տեսքը չի տարբերվում հիմնականից"; +"tour_section_8_bottom_text_2" = "Ժապավենը ունի տարատեսակ կոնտենտ, սովորական նկարներից և վիդեոներից մինչև գաղտնի գրառումներ և քվեարկություններ"; + + +"tour_section_9_title_1" = "Ստեղծե՛ք խմբե՛ր"; +"tour_section_9_text_1" = "Կայքը արդեն վաղուց ունի հազարավոր խմբեր նվիրված տարբեր թեմաներին և երկրպագումներին"; +"tour_section_9_text_2" = "Դուք կարող եք միանալ ցանկացած խմբին, եթե չեք գտնում Ձեր ուզածը, կարող եք ստեղծել այն"; +"tour_section_9_text_3" = "Ամեն խումբ ունի իր վիքի էջերը, ալբոմները, հղումները և քննարկումները"; +"tour_section_9_title_2" = "Կառավարեք խումբը ընկերների հետ"; +"tour_section_9_text_2_1" = "Կառավարեք խումբը "Խմբագրել Խումբը" բաժնում հանրության ավատարի ներքո"; +"tour_section_9_text_2_2" = "Ստեղծեք Ձեր ադմինիստրատորների ու մոդերատորների թիմը, ում Դուք վստահում եք"; +"tour_section_9_text_2_3" = "Դուք կարող եք թաքցնել ադմինիստրատորին, և նա չի երևա Ձեր խմբի ոչ մի անկյունում"; +"tour_section_9_bottom_text_1" = ""Իմ Խմբերը" բաժինը կայքի ձախ մենյույում է գտնվում"; +"tour_section_9_bottom_text_2" = "Խմբի օրինակ"; +"tour_section_9_bottom_text_3" = "Խմբերը իրական կազմակերպույուններ են, որոնց մասնակիցները ցանկանում են մնալ կապի հետ իրենց լսարանի հետ"; + + +"tour_section_10_title_1" = "Վա՛յ"; +"tour_section_10_text_1" = "Այս բաժնում էլ լավ կլիներ սարքել ծանոթություն, սակայն այն դեռ սարքվում է։ Եկե՛ք սիրուն ձևերով շրջանցեք այն և առաջ գնանք..."; + + +"tour_section_11_title_1" = "Տեսքեր"; +"tour_section_11_text_1" = "Գրանցվելուց հետո Ձեր էջում կիրառվում է սովորական տեսքը"; +"tour_section_11_text_2" = "Որոշ նորեկները կարող է չսիրեն լռելյայն տեսքը, քանի որ այն անգամ հնության զգացում է տալիս"; +"tour_section_11_text_3" = "Բայց հլը հո՛պ. Դուք կարող եք անգամ ստեղծել Ձեր տեսքը ՝ կարդալով դոկումենտացիան, կամ էլ ընտրել եղածներից մեկը"; +"tour_section_11_bottom_text_1" = "Տեսքերի ցանկը հասանելի է "Իմ Կարգավորումներ" –ի "Ինտերֆեյս" բաժնում;"; +"tour_section_11_wordart" = ""; + +"tour_section_12_title_1" = "Պրոֆիլ և խմբի ետնապատկերներ"; +"tour_section_12_text_1" = "Դուք կարող եք երկու ետնապատկեր տեղադրել"; +"tour_section_12_text_2" = "Նրանք կցուցադրվեն Ձեր էջի ծայրամասերում"; +"tour_section_12_text_3" = "Ակնարկ. նախքան ետնապատկեր տեղադրելը, փորձե՛ք էքսպերիմենտներ անել իր հետ. փոխել գույնը, հայելու էֆֆեկտ դնել կամ մի բան անել"; +"tour_section_12_title_2" = "Ավատարներ"; +"tour_section_12_text_2_1" = "Դուք կարող եք դնել տարբեր կարգավորումներ ավատարը տեսնելու համար. սովորական, շրջանաձև կամ քառակուսի (1:1)"; +"tour_section_12_text_2_2" = "Այս կարգավորումները տեսանելի են միայն Ձեզ"; +"tour_section_12_title_3" = "Ձախ մենյույի փոփոխումը"; +"tour_section_12_text_3_1" = "Եթե պետք է, կարող եք թաքցնել էջում որոշակի բաժինները"; +"tour_section_12_text_3_2" = "Հիշե՛ք. Հիմնական բաժինները (Իմ Էջը, Իմ Ընկերները, Իմ Պատասխանները, Իմ Կարգավորումները) չի լինի թաքցնել"; +"tour_section_12_title_4" = "Գրառումների դիտարկում"; +"tour_section_12_text_4_1" = "Եթե հոգնել եք պատի հին դիզայնից որը վաղեմի հայտնի ՎԿոնտակտե–ին էր հարիր, ապա միշտ էլ կարող եք փոխել այն սարքելով միկրոբլոգ"; +"tour_section_12_text_4_2" = "Գրառումների տեսքը կարող է փոփոխվել երկու տարբերակի միջև ՝ ցանկացած ժամանակ"; +"tour_section_12_text_4_3" = "Հաշվի առեք, որ եթե հին դիզայնն եք ընտրել, վերջին մեկնաբանությունները չեն ցուցադրվի"; +"tour_section_12_bottom_text_1" = "Ետնապատկերի կարգավորման էջ"; +"tour_section_12_bottom_text_2" = "Ետնապատկերներով էջերի օրինակներ"; +"tour_section_12_bottom_text_3" = "Այս հարմարանքով կարող եք ավելի շատ անհատականացնել Ձեր էջը"; +"tour_section_12_bottom_text_4" = "Հին տեսք"; +"tour_section_12_bottom_text_5" = "Միկրոբլոգ"; + + +"tour_section_13_title_1" = "Պրոմոկոդեր"; +"tour_section_13_text_1" = "OpenVK–ն ունի պրոմոկոդերի համակարգ, որը ավելացնում է որոշակի արժույթ (գնահատման տոկոսներ, ձայներ և այլն)"; +"tour_section_13_text_2" = "Որոշակի կուպոններ ստեղծվում են տոների ժամանակ։ Դրանք հայթհայթելու համար հետևե՛ք OpenVK–ի Telegram–յան ալիքին"; +"tour_section_13_text_3" = "Պրոմոկոդն ակտիվացնելուց հետո սահմանված արժույթը անմիջապես կփոխանցվի Ձեզ"; +"tour_section_13_text_4" = "Հիշե՛ք. Բոլոր պրոմոկոդերի ակտիվացման ժամկետը խիստ սահմանափակ է"; +"tour_section_13_bottom_text_1" = "Պրոմոկոդերն ունեն 24 տառ ու թիվ"; +"tour_section_13_bottom_text_2" = "Հաջողված ակտիվացիա (խոսքի ՝ մրցանակաբաշխում ենք 100 ձայնով)"; +"tour_section_13_bottom_text_3" = "Ուշադի՛ր. Պրոմոկոդի ակտիվացումից հետո կրկին չեք կարող այն օգտագործել"; + +"tour_section_14_title_1" = "Հեռախոսի տարբերակ"; +"tour_section_14_text_1" = "Այս պահին կայքի հեռախոսի վերսիան դեռևս չկա, սակայն գոյություն ունի հավելվածը Android-ի համար"; +"tour_section_14_text_2" = "OpenVK Legacy–ն OpenVK-ի ռետրո հավելվածն է, որը իմիտացնում է ՎԿոնտակտե–ի 2013թ. դիզայնը"; +"tour_section_14_text_3" = "Մինիմալ սպասարկվող տարբերակը Android 2.1 Eclair–ն է, որը անգամ վաղ 2010–ականների սարքերի վրա է աշխատում"; + +"tour_section_14_title_2" = "Որտեղի՞ց ես կարող եմ ներբեռնել այն"; +"tour_section_14_text_2_1" = "Ռելիզային տարբերակները տեղադրվում են F-Droid–ի ռեպոզիտորիայում"; +"tour_section_14_text_2_2" = "Եթե Դուք բետա թեստավորող եք, հավելվածի նոր տարբերակները հրապարակվում են առանձին թարմացումների ալիքում"; +"tour_section_14_text_2_3" = "Հաշվի՛ առեք. Հավելվածը կարող է ունենալ բագեր և խնդիրներ, որոնց մասին խնդրվում է հայտնել հավելվածի պաշտոնական խմբում"; + +"tour_section_14_bottom_text_1" = "Էկրանի նկարներ"; +"tour_section_14_bottom_text_2" = "Սա ավարտում է կայքի ճամփորդությունը։ Եթե ցանկանում եք փորձարկել հեռախոսի հավելվածը, ստեղծել Ձեր խումբը, հրավիրել ընկերներին կամ նորերին գտնել, կամ էլ ուղղակի հավես ժամանակ անցկացնել, Դուք կարող եք անել դա հենց հիմա փոքրիկ գրանցում անելով"; +"tour_section_14_bottom_text_3" = "Սա ավարտում է կայքի ճամփորդությունը"; + +/* Search */ + +"s_people" = "Մարդիկ"; +"s_groups" = "Ակումբներ"; +"s_events" = "Իրադարձություններ"; +"s_apps" = "Հավելվածներ"; +"s_questions" = "Հարցեր"; +"s_notes" = "Նշումներ"; +"s_themes" = "Տեսքեր"; +"s_posts" = "Գրառումներ"; +"s_comments" = "Մեկնաբանություններ"; +"s_videos" = "Վիդեոներ"; +"s_audios" = "Երաժշտություն"; +"s_by_people" = "մարդկանց համար"; +"s_by_groups" = "խմբերի համար"; +"s_by_posts" = "գրառումների համար"; +"s_by_comments" = "մեկնաբանությունների համար"; +"s_by_videos" = "վիդեոների համար"; +"s_by_apps" = "հավելվածների համար"; +"s_by_audios" = "երգերի համար"; + +"s_order_by" = "Դասավորել ըստ..."; + +"s_order_by_id" = "Ըստ ID-ի"; +"s_order_by_name" = "Ըստ անվան"; +"s_order_by_random" = "Ըստ պատահականության"; +"s_order_by_rating" = "Ըստ վարկանիշի"; +"s_order_invert" = "Շրջել"; + +"s_by_date" = "Ըստ ամսաթվի"; +"s_registered_before" = "Գրանցված մինչև"; +"s_registered_after" = "Գրանցված հետո"; +"s_date_before" = "Առաջ"; +"s_date_after" = "Հետո"; + +"s_main" = "Հիմնական"; + +"s_now_on_site" = "հիմա կայքում"; +"s_with_photo" = "նկարով"; +"s_only_in_names" = "միայն անուններում"; + +"s_any" = "ցանկացած"; +"reset" = "Վերականգնել"; + +"closed_group_post" = "Սա մասնավոր խմբի գրառում է"; +"deleted_target_comment" = "Այս մեկնաբանությունը ջնջված գրառման տակ է եղել"; + +"no_results" = "Արդյունք չկա"; + +/* Mobile */ +"mobile_friends" = "Ընկերներ"; +"mobile_photos" = "Նկարներ"; +"mobile_videos" = "Վիդեոներ"; +"mobile_messages" = "Նամակներ"; +"mobile_notes" = "Գրառումներ"; +"mobile_groups" = "Խմբեր"; +"mobile_search" = "Որոնում"; +"mobile_settings" = "Կարգավորումներ"; +"mobile_desktop_version" = "Համակարգչի տարբերակ"; +"mobile_log_out" = "Դուրս գալ"; +"mobile_menu" = "Մենյու"; +"mobile_like" = "Հավանել"; +"mobile_user_info_hide" = "Թաքցնել"; +"mobile_user_info_show_details" = "Ցույց տալ մանրամասն"; diff --git a/locales/ru.strings b/locales/ru.strings index b882a4b8..e453bf9b 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -132,6 +132,10 @@ "updated_at" = "Обновлено $1"; "user_banned" = "К сожалению, нам пришлось заблокировать страницу пользователя $1."; "user_banned_comment" = "Комментарий модератора:"; +"verified_page" = "Подтверждённая страница"; +"user_is_blocked" = "Пользователь заблокирован"; +"before" = "до"; +"forever" = "навсегда"; /* Wall */ @@ -182,6 +186,7 @@ "nsfw_warning" = "Данный пост может содержать 18+ контент"; "report" = "Пожаловаться"; "attach" = "Прикрепить"; +"detach" = "Открепить"; "attach_photo" = "Прикрепить фото"; "attach_video" = "Прикрепить видео"; "draw_graffiti" = "Нарисовать граффити"; @@ -191,6 +196,8 @@ "version_incompatibility" = "Не удалось отобразить это вложение. Возможно, база данных несовместима с текущей версией OpenVK."; "graffiti" = "Граффити"; "reply" = "Ответить"; +"post_is_ad" = "Этот пост был размещён за взятку."; +"edited_short" = "ред."; "all_posts" = "Все записи"; "users_posts" = "Записи $1"; @@ -383,10 +390,16 @@ /* Albums */ "create" = "Создать"; +"album" = "Альбом"; "albums" = "Альбомы"; +"photos" = "фотографий"; +"photo" = "Фотография"; "create_album" = "Создать альбом"; "edit_album" = "Редактировать альбом"; +"edit_photo" = "Изменить фотографию"; "creating_album" = "Создание альбома"; +"delete_photo" = "Удалить фотографию"; +"sure_deleting_photo" = "Вы уверены, что хотите удалить эту фотографию?"; "upload_photo" = "Загрузить фотографию"; "photo" = "Фотография"; "upload_button" = "Загрузить"; @@ -428,6 +441,39 @@ "upd_f" = "обновила фотографию на своей странице"; "upd_g" = "обновило фотографию группы"; +"add_photos" = "Добавить фотографии"; +"upload_picts" = "Загрузить фотографии"; +"end_uploading" = "Завершить загрузку"; +"photos_successfully_uploaded" = "Фотографии успешно загружены"; +"click_to_go_to_album" = "Нажмите, чтобы перейти к альбому."; +"error_uploading_photo" = "Не удалось загрузить фотографию"; +"too_many_pictures" = "Не больше 10 фотографий"; + +"drag_files_here" = "Перетащите файлы сюда"; +"only_images_accepted" = "Файл \"$1\" не является изображением"; +"max_filesize" = "Максимальный размер файла — $1 мегабайт"; + +"uploading_photos_from_computer" = "Загрузка фотографий с Вашего компьютера"; +"supported_formats" = "Поддерживаемые форматы файлов: JPG, PNG и GIF."; +"max_load_photos" = "Вы можете загружать до 10 фотографий за один раз."; +"tip" = "Подсказка"; +"tip_ctrl" = "для того, чтобы выбрать сразу несколько фотографий, удерживайте клавишу Ctrl при выборе файлов в ОС Windows или клавишу CMD в Mac OS."; +"album_poster" = "Обложка альбома"; +"select_photo" = "Выберите фотографию"; +"upload_new_photo" = "Загрузить новую фотографию"; + +"is_x_photos_zero" = "Всего ноль фотографий."; +"is_x_photos_one" = "Всего одна фотография."; +"is_x_photos_few" = "Всего $1 фотографии."; +"is_x_photos_many" = "Всего $1 фотографий."; +"is_x_photos_other" = "Всего $1 фотографий."; + +"all_photos" = "Все фотографии"; +"error_uploading_photo" = "Не удалось загрузить фотографию. Текст ошибки: "; +"too_many_photos" = "Слишком много фотографий."; + +"photo_x_from_y" = "Фотография $1 из $2"; + /* Notes */ "notes" = "Заметки"; @@ -463,6 +509,8 @@ "notes_closed" = "Вы не можете прикрепить заметку к записи, так как ваши заметки видны только вам.

Вы можете поменять это в настройках."; "do_not_attach_note" = "Не прикреплять заметку"; +"something" = "Кое-что"; +"supports_xhtml" = "из (X)HTML поддерживается."; /* Notes: Article Viewer */ "aw_legacy_ui" = "Старый интерфейс"; @@ -653,6 +701,9 @@ "two_factor_authentication_backup_codes_1" = "Резервные коды позволяют подтверждать вход, когда у вас нет доступа к телефону, например, в путешествии."; "two_factor_authentication_backup_codes_2" = "У вас есть ещё 10 кодов, каждым кодом можно воспользоваться только один раз. Распечатайте их, уберите в надежное место и используйте, когда потребуются коды для подтверждения входа."; "two_factor_authentication_backup_codes_3" = "Вы можете получить новые коды, если они заканчиваются. Действительны только последние созданные резервные коды."; +"viewing_backup_codes" = "Просмотр резервных кодов"; +"disable_2fa" = "Отключить 2FA"; +"viewing" = "Просмотреть"; /* Sorting */ @@ -678,6 +729,15 @@ "videos_many" = "$1 видеозаписей"; "videos_other" = "$1 видеозаписей"; "view_video" = "Просмотр"; +"change_video" = "Изменить видеозапись"; +"unknown_video" = "Эта видеозапись не поддерживается в вашей версии OpenVK."; + +"selecting_video" = "Выбор видеозаписей"; +"upload_new_video" = "Загрузить новое видео"; +"max_attached_videos" = "Максимум 10 видеозаписей"; +"max_attached_photos" = "Максимум 10 фотографий"; +"no_videos" = "У вас нет видео."; +"no_videos_results" = "Нет результатов."; /* Notifications */ @@ -718,6 +778,7 @@ "nt_mention_in_note" = "в обсуждении заметки"; "nt_mention_in_topic" = "в обсуждении"; "nt_post_small" = "запись"; +"nt_sent_gift" = "отправил вам подарок"; /* Time */ @@ -911,6 +972,20 @@ "text_of_the_post" = "Текст записи"; "today" = "сегодня"; +"will_be_watched" = "Скоро её рассмотрят модераторы"; + +"report_question" = "Пожаловаться?"; +"report_question_text" = "Что именно вам кажется недопустимым в этом материале?"; +"report_reason" = "Причина жалобы"; +"reason" = "Причина"; +"going_to_report_app" = "Вы собираетесь пожаловаться на данное приложение."; +"going_to_report_club" = "Вы собираетесь пожаловаться на данное сообщество."; +"going_to_report_photo" = "Вы собираетесь пожаловаться на данную фотографию."; +"going_to_report_user" = "Вы собираетесь пожаловаться на данного пользователя."; +"going_to_report_video" = "Вы собираетесь пожаловаться на данную видеозапись."; +"going_to_report_post" = "Вы собираетесь пожаловаться на данную запись."; +"going_to_report_comment" = "Вы собираетесь пожаловаться на данный комментарий."; + "comment" = "Комментарий"; "sender" = "Отправитель"; "author" = "Автор"; @@ -1050,6 +1125,7 @@ "error_repost_fail" = "Не удалось поделиться записью"; "error_data_too_big" = "Аттрибут '$1' не может быть длиннее $2 $3"; "forbidden" = "Ошибка доступа"; +"unknown_error" = "Неизвестная ошибка"; "forbidden_comment" = "Настройки приватности этого пользователя не разрешают вам смотреть на его страницу."; "changes_saved" = "Изменения сохранены"; "changes_saved_comment" = "Новые данные появятся на вашей странице"; @@ -1085,23 +1161,117 @@ "media_file_corrupted_or_too_large" = "Файл медиаконтента повреждён или слишком велик."; "post_is_empty_or_too_big" = "Пост пустой или слишком большой."; "post_is_too_big" = "Пост слишком большой."; + "error_deleting_suggested" = "Вы не можете удалить ваш принятый пост"; "error_invalid_wall_value" = "Некорректное значение стены"; +"error_sending_report" = "Не удалось подать жалобу..."; +"error_when_saving_gift" = "Не удалось сохранить подарок"; +"error_when_saving_gift_bad_image" = "Изображение подарка кривое."; +"error_when_saving_gift_no_image" = "Пожалуйста, загрузите изображение подарка."; +"video_uploads_disabled" = "Загрузки видео отключены администратором."; + +"error_when_publishing_comment" = "Не удалось опубликовать комментарий"; +"error_when_publishing_comment_description" = "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой."; +"error_comment_empty" = "Комментарий пустой или слишком большой."; +"error_comment_too_big" = "Комментарий слишком большой."; +"error_comment_file_too_big" = "Файл медиаконтента повреждён или слишком велик."; + +"comment_is_added" = "Комментарий добавлен"; +"comment_is_added_desc" = "Ваш комментарий появится на странице."; + +"error_access_denied_short" = "Ошибка доступа"; +"error_access_denied" = "У вас недостаточно прав, чтобы редактировать этот ресурс"; +"success" = "Успешно"; +"comment_will_not_appear" = "Этот комментарий больше не будет показыватся."; + +"error_when_gifting" = "Не удалось подарить"; +"error_user_not_exists" = "Пользователь или набор не существуют."; +"error_no_rights_gifts" = "Не удалось подтвердить права на подарок."; +"error_no_more_gifts" = "У вас больше не осталось таких подарков."; +"error_no_money" = "Ору нищ не пук."; + +"gift_sent" = "Подарок отправлен"; +"gift_sent_desc" = "Вы отправили подарок $1 за $2 голосов"; + +"error_on_server_side" = "Произошла ошибка на стороне сервера. Обратитесь к системному администратору."; +"error_no_group_name" = "Вы не ввели название группы."; + +"success_action" = "Операция успешна"; +"connection_error" = "Ошибка подключения"; +"connection_error_desc" = "Не удалось подключится к службе телеметрии."; + +"error_when_uploading_photo" = "Не удалось сохранить фотографию."; + +"new_changes_desc" = "Новые данные появятся в вашей группе."; +"comment_is_changed" = "Комментарий к администратору изменён"; +"comment_is_deleted" = "Комментарий к администратору удален"; +"comment_is_too_long" = "Комментарий слишком длинный ($1 символов вместо 36 символов)"; +"x_no_more_admin" = "$1 больше не администратор."; +"x_is_admin" = "$1 назначен(а) администратором."; + +"x_is_now_hidden" = "Теперь $1 будет показываться как обычный подписчик всем, кроме других администраторов"; +"x_is_now_showed" = "Теперь все будут знать, что $1 — администратор."; + +"note_is_deleted" = "Заметка удалена"; +"note_x_is_now_deleted" = "Заметка \"$1\" была успешно удалена."; +"new_data_accepted" = "Новые данные приняты."; + +"album_is_deleted" = "Альбом удалён"; +"album_x_is_deleted" = "Альбом $1 был успешно удалён."; + +"error_adding_to_deleted" = "Не удалось сохранить фотографию в DELETED."; +"error_adding_to_x" = "Не удалось сохранить фотографию в $1."; +"no_photo" = "Нету фотографии"; + +"select_file" = "Выберите файл"; +"new_description_will_appear" = "Обновлённое описание появится на странице с фоткой."; +"photo_is_deleted" = "Фотография удалена"; +"photo_is_deleted_desc" = "Эта фотография была успешно удалена."; + +"no_video" = "Нет видеозаписи"; +"no_video_desc" = "Выберите файл или укажите ссылку."; +"error_occured" = "Произошла ошибка"; +"error_video_damaged_file" = "Файл повреждён или не содержит видео."; +"error_video_incorrect_link" = "Возможно, ссылка некорректна."; +"error_video_no_title" = "Видео не может быть опубликовано без названия."; + +"new_data_video" = "Обновлённое описание появится на странице с видео."; +"error_deleting_video" = "Не удалось удалить видео"; +"login_please" = "Вы не вошли в аккаунт."; +"invalid_code" = "Не удалось подтвердить номер телефона: неверный код."; + +"error_max_pinned_clubs" = "Находится в левом меню могут максимум 10 групп"; +"error_viewing_subs" = "Вы не можете просматривать полный список подписок $1."; +"error_status_too_long" = "Статус слишком длинный ($1 символов вместо 255 символов)"; +"death" = "Смэрть..."; +"nehay" = "Нехай живе!"; +"user_successfully_banned" = "Пользователь успешно забанен."; + +"content_is_deleted" = "Контент удалён, а пользователю прилетело предупреждение."; +"report_is_ignored" = "Жалоба проигнорирована."; +"group_owner_is_banned" = "Создатель сообщества успешно забанен."; +"group_is_banned" = "Сообщество успешно забанено"; +"description_too_long" = "Описание слишком длинное."; + /* Admin actions */ "login_as" = "Войти как $1"; "manage_user_action" = "Управление пользователем"; "manage_group_action" = "Управление группой"; "ban_user_action" = "Заблокировать пользователя"; +"blocks" = "Блокировки"; +"last_actions" = "Последние действия"; "unban_user_action" = "Разблокировать пользователя"; "warn_user_action" = "Предупредить пользователя"; "ban_in_support_user_action" = "Заблокировать в поддержке"; "unban_in_support_user_action" = "Разблокировать в поддержке"; +"changes_history" = "История редактирования"; /* Admin panel */ "admin" = "Админ-панель"; +"sandbox_for_developers" = "Sandbox для разработчиков"; "admin_ownerid" = "ID владельца"; "admin_author" = "Автор"; "admin_name" = "Имя"; @@ -1176,6 +1346,12 @@ "admin_banned_link_initiator" = "Инициатор"; "admin_banned_link_not_specified" = "Ссылка не указана"; "admin_banned_link_not_found" = "Ссылка не найдена"; + +"admin_gift_moved_successfully" = "Подарок успешно перемещён"; +"admin_gift_moved_to_recycle" = "Теперь подарок находится в корзине."; + +"logs" = "Логи"; +"logs_anything" = "Любое"; "logs_adding" = "Создание"; "logs_editing" = "Редактирование"; "logs_removing" = "Удаление"; @@ -1184,6 +1360,28 @@ "logs_edited" = "отредактировал"; "logs_removed" = "удалил"; "logs_restored" = "восстановил"; +"logs_id_post" = "ID записи"; +"logs_id_object" = "ID объекта"; +"logs_uuid_user" = "UUID пользователя"; +"logs_change_type" = "Тип изменения"; +"logs_change_object" = "Тип объекта"; + +"logs_user" = "Пользователь"; +"logs_object" = "Объект"; +"logs_type" = "Тип"; +"logs_changes" = "Изменения"; +"logs_time" = "Время"; + +"bans_history" = "История блокировок"; +"bans_history_blocked" = "Забаненный"; +"bans_history_initiator" = "Инициатор"; +"bans_history_start" = "Начало"; +"bans_history_end" = "Конец"; +"bans_history_time" = "Время"; +"bans_history_reason" = "Причина"; +"bans_history_start" = "Начало"; +"bans_history_removed" = "Снята"; +"bans_history_active" = "Активная блокировка"; /* Paginator (deprecated) */ @@ -1233,6 +1431,8 @@ "close" = "Закрыть"; "warning" = "Внимание"; "question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?"; +"confirm_m" = "Подтвердить"; +"action_successfully" = "Операция успешна"; /* User alerts */ @@ -1246,6 +1446,8 @@ /* Away */ +"transition_is_blocked" = "Переход по ссылке заблокирован"; +"caution" = "Предупреждение"; "url_is_banned" = "Переход невозможен"; "url_is_banned_comment" = "Администрация $1 не рекомендует переходить по этой ссылке."; "url_is_banned_comment_r" = "Администрация $1 не рекомендует переходить по этой ссылке.

Причина: $2"; @@ -1512,6 +1714,41 @@ "no_results" = "Результатов нет"; +/* BadBrowser */ + +"deprecated_browser" = "Устаревший браузер"; +"deprecated_browser_description" = "Для просмотра этого контента вам понадобится Firefox ESR 52+ или эквивалентный по функционалу навигатор по всемирной сети интернет. Сожалеем об этом."; + +/* Statistics */ + +"coverage" = "Охват"; +"coverage_this_week" = "Этот график отображает охват за последние 7 дней."; +"views" = "Просмотры"; +"views_this_week" = "Этот график отображает просмотры постов сообщества за последние 7 дней."; + +"full_coverage" = "Полный охват"; +"all_views" = "Все просмотры"; + +"subs_coverage" = "Охват подписчиков"; +"subs_views" = "Просмотры подписчиков"; + +"viral_coverage" = "Виральный охват"; +"viral_views" = "Виральные просмотры"; + +/* Sudo */ + +"you_entered_as" = "Вы вошли как"; +"please_rights" = "пожалуйста, уважайте право на тайну переписки других людей и не злоупотребляйте подменой пользователя."; +"click_on" = "Нажмите"; +"there" = "здесь"; +"to_leave" = "чтобы выйти"; + +/* Phone number */ + +"verify_phone_number" = "Подтвердить номер телефона"; +"we_sended_first" = "Мы отправили SMS с кодом на номер"; +"we_sended_end" = "введите его сюда"; + /* Mobile */ "mobile_friends" = "Друзья"; "mobile_photos" = "Фотографии"; @@ -1527,3 +1764,40 @@ "mobile_like" = "Нравится"; "mobile_user_info_hide" = "Скрыть"; "mobile_user_info_show_details" = "Показать подробнее"; + +/* Moderation */ + +"section" = "Раздел"; +"template_ban" = "Бан по шаблону"; +"active_templates" = "Действующие шаблоны"; +"users_reports" = "Жалобы пользователей"; +"substring" = "Подстрока"; +"n_user" = "Пользователь"; +"time_before" = "Время раньше, чем"; +"time_after" = "Время позже, чем"; +"where_for_search" = "WHERE для поиска по разделу"; +"block_params" = "Параметры блокировки"; +"only_rollback" = "Только откат"; +"only_block" = "Только блокировка"; +"rollback_and_block" = "Откат и блокировка"; +"subm" = "Применить"; + +"select_section_for_start" = "Выберите раздел для начала работы"; +"results_will_be_there" = "Здесь будут отображаться результаты поиска"; +"search_results" = "Результаты поиска"; +"cnt" = "шт"; + +"link_to_page" = "Ссылка на страницу"; +"or_subnet" = "или подсеть"; +"error_when_searching" = "Ошибка при выполнении запроса"; +"no_found" = "Ничего не найдено"; +"operation_successfully" = "Операция завершена успешно"; + +"unknown_error" = "Неизвестная ошибка"; +"templates" = "Шаблоны"; +"type" = "Тип"; +"count" = "Количество"; +"time" = "Время"; + +"roll_back" = "откатить"; +"roll_backed" = "откачено";