From 4699fcbeb935deba0a52fe61ba5971597f1afe5f Mon Sep 17 00:00:00 2001 From: lalka2018 <99399973+lalka2016@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:44:12 +0300 Subject: [PATCH] Groups: Wall: add suggestions (#935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Wall: add early suggestions * Fix br * Fix empty posts * fck * Add offset for api * Add notifications of new suggestion posts * Fix mentions in suggested posts * 🤮🤢 * Change regex Теперь оно удаляет все теги а не только
* Add da koroche pohuy * Эдд апи метходс Методы нестандартные немного * Pon * Add skloneniyia * newlines * int * Update loaders and add avtopodgruzka postov * Update JOERGK.strings * Blin * Remove repeated code, fix loaded buttons on chr... ...ome and fix getting suggested posts via API.Wall.getPost * Fix polls * Fihes Теперь уведомление о принятии поста не приходит, если вы приняли свой же пост Пофикшен баг перехода в предложку Добавлен старый вид постов в предложке Теперь счётчик постов в предложке у прикреплённой группы обновляется при принятии или отклонении поста Убрано всплывающее уведомление об отклонении поста (оно раздражает) Теперь если вы посмотрели все посты на одной странице (не на первой) и на ней не осталось постов, вас телепортирует на предыдущую страницу * Remove ability to delete your accepted psto * oi blin * Improvements 2 api * g * openvk.uk Возможно, приведение кода к кодстайлу (удаление скобочек то есть) * aiaks * al_wall.js -> al_suggestions.js * 👨‍💻 Add 👨‍💻 fading 👨‍💻 * Add "owner's posts' and "other's posts" Давайте рофлить👨‍💻👨‍💻👨‍💻 * planshet openvk Add tabs for post view, add signer's object in wall get and add person icon in microblog * Simplefai ze kod * PHP 8 FIX WATAFAK * Add indesk --- ServiceAPI/Wall.php | 9 +- VKAPI/Handlers/Groups.php | 45 +++- VKAPI/Handlers/Newsfeed.php | 2 +- VKAPI/Handlers/Wall.php | 169 ++++++++++++- Web/Models/Entities/Club.php | 34 ++- .../NewSuggestedPostsNotification.php | 13 + .../PostAcceptedNotification.php | 13 + Web/Models/Entities/Poll.php | 17 +- Web/Models/Entities/Post.php | 14 ++ Web/Models/Entities/Postable.php | 2 +- Web/Models/Repositories/Posts.php | 129 +++++++++- Web/Presenters/GroupPresenter.php | 49 +++- Web/Presenters/InternalAPIPresenter.php | 2 +- Web/Presenters/WallPresenter.php | 158 +++++++++++- Web/Presenters/templates/@layout.xml | 12 +- Web/Presenters/templates/Group/Edit.xml | 7 +- Web/Presenters/templates/Group/Suggested.xml | 34 +++ Web/Presenters/templates/Group/View.xml | 12 + Web/Presenters/templates/Wall/Post.xml | 14 ++ Web/Presenters/templates/Wall/Wall.xml | 14 +- .../components/notifications/6/_14_5_.xml | 7 + .../components/notifications/7/_18_5_.xml | 5 + .../components/post/microblogpost.xml | 13 +- .../templates/components/post/oldpost.xml | 9 +- Web/Presenters/templates/components/wall.xml | 3 + Web/routes.yml | 6 + Web/static/css/main.css | 54 ++++ Web/static/img/person.png | Bin 0 -> 265 bytes Web/static/js/al_suggestions.js | 231 ++++++++++++++++++ Web/static/js/al_wall.js | 6 +- install/sqls/00043-suggest-posts.sql | 1 + locales/en.strings | 72 +++++- locales/ru.strings | 74 +++++- themepacks/midnight/stylesheet.css | 13 + themepacks/openvk_modern/stylesheet.css | 9 +- 35 files changed, 1195 insertions(+), 57 deletions(-) create mode 100644 Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php create mode 100644 Web/Models/Entities/Notifications/PostAcceptedNotification.php create mode 100644 Web/Presenters/templates/Group/Suggested.xml create mode 100644 Web/Presenters/templates/components/notifications/6/_14_5_.xml create mode 100644 Web/Presenters/templates/components/notifications/7/_18_5_.xml create mode 100644 Web/static/img/person.png create mode 100644 Web/static/js/al_suggestions.js create mode 100644 install/sqls/00043-suggest-posts.sql diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php index f25d03d4..d01922b8 100644 --- a/ServiceAPI/Wall.php +++ b/ServiceAPI/Wall.php @@ -22,7 +22,10 @@ class Wall implements Handler { $post = $this->posts->get($id); if(!$post || $post->isDeleted()) - $reject("No post with id=$id"); + $reject(53, "No post with id=$id"); + + if($post->getSuggestionType() != 0) + $reject(25, "Can't get suggested post"); $res = (object) []; $res->id = $post->getId(); @@ -96,7 +99,7 @@ class Wall implements Handler $resolve($arr); } - + function getVideos(int $page = 1, callable $resolve, callable $reject) { $videos = $this->videos->getByUser($this->user, $page, 8); @@ -129,7 +132,7 @@ class Wall implements Handler ]; foreach($videos as $video) { - $res = json_decode(json_encode($video->toVkApiStruct()), true); + $res = json_decode(json_encode($video->toVkApiStruct($this->user)), true); $res["video"]["author_name"] = $video->getOwner()->getCanonicalName(); $arr["items"][] = $res; diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index 007b68ea..5dc47316 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -2,6 +2,7 @@ namespace openvk\VKAPI\Handlers; use openvk\Web\Models\Repositories\Clubs as ClubsRepo; use openvk\Web\Models\Repositories\Users as UsersRepo; +use openvk\Web\Models\Repositories\Posts as PostsRepo; use openvk\Web\Models\Entities\Club; final class Groups extends VKAPIRequestHandler @@ -80,6 +81,19 @@ final class Groups extends VKAPIRequestHandler break; case "members_count": $rClubs[$i]->members_count = $usr->getFollowersCount(); + break; + case "can_suggest": + $rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2; + break; + # unstandard feild + case "suggested_count": + if($usr->getWallType() != 2) { + $rClubs[$i]->suggested_count = NULL; + break; + } + + $rClubs[$i]->suggested_count = $usr->getSuggestedPostsCount($this->getUser()); + break; } } @@ -188,7 +202,19 @@ final class Groups extends VKAPIRequestHandler case "description": $response[$i]->description = $clb->getDescription(); break; - case "contacts": + case "can_suggest": + $response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2; + break; + # unstandard feild + case "suggested_count": + if($clb->getWallType() != 2) { + $response[$i]->suggested_count = NULL; + break; + } + + $response[$i]->suggested_count = $clb->getSuggestedPostsCount($this->getUser()); + break; + case "contacts": $contacts; $contactTmp = $clb->getManagers(1, true); @@ -288,7 +314,7 @@ final class Groups extends VKAPIRequestHandler string $description = NULL, string $screen_name = NULL, string $website = NULL, - int $wall = NULL, + int $wall = -1, int $topics = NULL, int $adminlist = NULL, int $topicsAboveWall = NULL, @@ -308,17 +334,26 @@ final class Groups extends VKAPIRequestHandler !empty($description) ? $club->setAbout($description) : NULL; !empty($screen_name) ? $club->setShortcode($screen_name) : NULL; !empty($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL; - !empty($wall) ? $club->setWall($wall) : NULL; + + try { + $wall != -1 ? $club->setWall($wall) : NULL; + } catch(\Exception $e) { + $this->fail(50, "Invalid wall value"); + } + !empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL; !empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL; !empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL; !empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL; + in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL; try { $club->save(); } catch(\TypeError $e) { - $this->fail(8, "Nothing changed"); + $this->fail(15, "Nothing changed"); + } catch(\Exception $e) { + $this->fail(18, "An unknown error occurred: maybe you set an incorrect value?"); } return 1; @@ -472,7 +507,7 @@ final class Groups extends VKAPIRequestHandler "title" => $club->getName(), "description" => $club->getDescription() != NULL ? $club->getDescription() : "", "address" => $club->getShortcode(), - "wall" => $club->canPost() == true ? 1 : 0, + "wall" => $club->getWallType(), # отличается от вкшных но да ладно "photos" => 1, "video" => 0, "audio" => $club->isEveryoneCanUploadAudios() ? 1 : 0, diff --git a/VKAPI/Handlers/Newsfeed.php b/VKAPI/Handlers/Newsfeed.php index d9992430..4833e7be 100644 --- a/VKAPI/Handlers/Newsfeed.php +++ b/VKAPI/Handlers/Newsfeed.php @@ -51,7 +51,7 @@ final class Newsfeed extends VKAPIRequestHandler { $this->requireUser(); - $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0"; + $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0 AND `posts`.`suggested` = 0"; if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT) $queryBase .= " AND `nsfw` = 0"; diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index fde87fcb..b6082c61 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -1,7 +1,7 @@ requireUser(); @@ -29,7 +29,7 @@ final class Wall extends VKAPIRequestHandler $items = []; $profiles = []; $groups = []; - $cnt = $posts->getPostCountOnUserWall($owner_id); + $cnt = 0; if ($owner_id > 0) $wallOnwer = (new UsersRepo)->get($owner_id); @@ -43,7 +43,47 @@ final class Wall extends VKAPIRequestHandler if(!$wallOnwer) $this->fail(15, "Access denied: wall is disabled"); // Don't search for logic here pls - foreach($posts->getPostsFromUsersWall($owner_id, 1, $count, $offset) as $post) { + $iteratorv; + + switch($filter) { + case "all": + $iteratorv = $posts->getPostsFromUsersWall($owner_id, 1, $count, $offset); + $cnt = $posts->getPostCountOnUserWall($owner_id); + break; + case "owner": + $iteratorv = $posts->getOwnersPostsFromWall($owner_id, 1, $count, $offset); + $cnt = $posts->getOwnersCountOnUserWall($owner_id); + break; + case "others": + $iteratorv = $posts->getOthersPostsFromWall($owner_id, 1, $count, $offset); + $cnt = $posts->getOthersCountOnUserWall($owner_id); + break; + case "postponed": + $this->fail(42, "Postponed posts are not implemented."); + break; + case "suggests": + if($owner_id < 0) { + if($wallOnwer->getWallType() != 2) + $this->fail(125, "Group's wall type is open or closed"); + + if($wallOnwer->canBeModifiedBy($this->getUser())) { + $iteratorv = $posts->getSuggestedPosts($owner_id * -1, 1, $count, $offset); + $cnt = $posts->getSuggestedPostsCount($owner_id * -1); + } else { + $iteratorv = $posts->getSuggestedPostsByUser($owner_id * -1, $this->getUser()->getId(), 1, $count, $offset); + $cnt = $posts->getSuggestedPostsCountByUser($owner_id * -1, $this->getUser()->getId()); + } + } else { + $this->fail(528, "Suggested posts avaiable only at groups"); + } + + break; + default: + $this->fail(254, "Invalid filter"); + break; + } + + foreach($iteratorv as $post) { $from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId(); $attachments = []; @@ -118,12 +158,23 @@ final class Wall extends VKAPIRequestHandler ]; } + $postType = "post"; + $signerId = NULL; + if($post->getSuggestionType() != 0) + $postType = "suggest"; + + + if($post->isSigned()) { + $actualAuthor = $post->getOwner(false); + $signerId = $actualAuthor->getId(); + } + $items[] = (object)[ "id" => $post->getVirtualId(), "from_id" => $from_id, "owner_id" => $post->getTargetWall(), "date" => $post->getPublicationTime()->timestamp(), - "post_type" => "post", + "post_type" => $postType, "text" => $post->getText(false), "copy_history" => $repost, "can_edit" => $post->canBeEditedBy($this->getUser()), @@ -135,6 +186,7 @@ final class Wall extends VKAPIRequestHandler "is_explicit" => $post->isExplicit(), "attachments" => $attachments, "post_source" => $post_source, + "signer_id" => $signerId, "comments" => (object)[ "count" => $post->getCommentsCount(), "can_post" => 1 @@ -156,6 +208,9 @@ final class Wall extends VKAPIRequestHandler else $groups[] = $from_id * -1; + if($post->isSigned()) + $profiles[] = $post->getOwner(false)->getId(); + $attachments = NULL; # free attachments so it will not clone everythingg } @@ -298,12 +353,24 @@ final class Wall extends VKAPIRequestHandler ]; } + # TODO: $post->getVkApiType() + $postType = "post"; + $signerId = NULL; + if($post->getSuggestionType() != 0) + $postType = "suggest"; + + + if($post->isSigned()) { + $actualAuthor = $post->getOwner(false); + $signerId = $actualAuthor->getId(); + } + $items[] = (object)[ "id" => $post->getVirtualId(), "from_id" => $from_id, "owner_id" => $post->getTargetWall(), "date" => $post->getPublicationTime()->timestamp(), - "post_type" => "post", + "post_type" => $postType, "text" => $post->getText(false), "copy_history" => $repost, "can_edit" => $post->canBeEditedBy($this->getUser()), @@ -314,6 +381,7 @@ final class Wall extends VKAPIRequestHandler "is_pinned" => $post->isPinned(), "is_explicit" => $post->isExplicit(), "post_source" => $post_source, + "signer_id" => $signerId, "attachments" => $attachments, "comments" => (object)[ "count" => $post->getCommentsCount(), @@ -336,6 +404,9 @@ final class Wall extends VKAPIRequestHandler else $groups[] = $from_id * -1; + if($post->isSigned()) + $profiles[] = $post->getOwner(false)->getId(); + $attachments = NULL; # free attachments so it will not clone everything $repost = NULL; # same } @@ -391,7 +462,7 @@ final class Wall extends VKAPIRequestHandler ]; } - function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = ""): object + function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object { $this->requireUser(); $this->willExecuteWriteAction(); @@ -412,6 +483,46 @@ final class Wall extends VKAPIRequestHandler if($canPost == false) $this->fail(15, "Access denied"); + if($post_id > 0) { + if($owner_id > 0) + $this->fail(62, "Suggested posts available only at groups"); + + $post = (new PostsRepo)->getPostById($owner_id, $post_id, true); + + if(!$post || $post->isDeleted()) + $this->fail(32, "Invald post"); + + if($post->getSuggestionType() == 0) + $this->fail(20, "Post is not suggested"); + + if($post->getSuggestionType() == 2) + $this->fail(16, "Post is declined"); + + if(!$post->canBePinnedBy($this->getUser())) + $this->fail(51, "Access denied"); + + $author = $post->getOwner(); + $flags = 0; + $flags |= 0b10000000; + + if($signed == 1) + $flags |= 0b01000000; + + $post->setSuggested(0); + $post->setCreated(time()); + $post->setFlags($flags); + + if(!empty($message) && iconv_strlen($message) > 0) + $post->setContent($message); + + $post->save(); + + if($author->getId() != $this->getUser()->getId()) + (new PostAcceptedNotification($author, $post, $post->getWallOwner()))->emit(); + + return (object)["post_id" => $post->getVirtualId()]; + } + $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; if($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) { $manager = $wallOwner->getManager($this->getUser()); @@ -440,6 +551,10 @@ final class Wall extends VKAPIRequestHandler $post->setContent($message); $post->setFlags($flags); $post->setApi_Source_Name($this->getPlatform()); + + if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2) + $post->setSuggested(1); + $post->save(); } catch(\LogicException $ex) { $this->fail(100, "One of the parameters specified was missing or invalid"); @@ -528,6 +643,22 @@ final class Wall extends VKAPIRequestHandler if($wall > 0 && $wall !== $this->user->identity->getId()) (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); + if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2) { + $suggsCount = (new PostsRepo)->getSuggestedPostsCount($wallOwner->getId()); + + if($suggsCount % 10 == 0) { + $managers = $wallOwner->getManagers(); + $owner = $wallOwner->getOwner(); + (new NewSuggestedPostsNotification($owner, $wallOwner))->emit(); + + foreach($managers as $manager) { + (new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit(); + } + } + + return (object)["post_id" => "on_view"]; + } + return (object)["post_id" => $post->getVirtualId()]; } @@ -830,7 +961,31 @@ final class Wall extends VKAPIRequestHandler return 1; } + + function delete(int $owner_id, int $post_id) + { + $this->requireUser(); + $this->willExecuteWriteAction(); + $post = (new PostsRepo)->getPostById($owner_id, $post_id, true); + if(!$post || $post->isDeleted()) + $this->fail(583, "Invalid post"); + + $wallOwner = $post->getWallOwner(); + + if($post->getTargetWall() < 0 && !$post->getWallOwner()->canBeModifiedBy($this->getUser()) && $post->getWallOwner()->getWallType() != 1 && $post->getSuggestionType() == 0) + $this->fail(12, "Access denied: you can't delete your accepted post."); + + if($post->getOwnerPost() == $this->getUser()->getId() || $post->getTargetWall() == $this->getUser()->getId() || $owner_id < 0 && $wallOwner->canBeModifiedBy($this->getUser())) { + $post->unwire(); + $post->delete(); + + return 1; + } else { + $this->fail(15, "Access denied"); + } + } + function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "") { $this->requireUser(); $this->willExecuteWriteAction(); diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index 319678f3..5324efb6 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -3,7 +3,7 @@ namespace openvk\Web\Models\Entities; use openvk\Web\Util\DateTime; use openvk\Web\Models\RowModel; use openvk\Web\Models\Entities\{User, Manager}; -use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers}; +use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers, Posts}; use Nette\Database\Table\{ActiveRow, GroupedSelection}; use Chandler\Database\DatabaseConnection as DB; use Chandler\Security\User as ChandlerUser; @@ -23,6 +23,10 @@ class Club extends RowModel const NOT_RELATED = 0; const SUBSCRIBED = 1; const REQUEST_SENT = 2; + + const WALL_CLOSED = 0; + const WALL_OPEN = 1; + const WALL_LIMITED = 2; function getId(): int { @@ -45,6 +49,11 @@ class Club extends RowModel return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size); } + + function getWallType(): int + { + return $this->getRecord()->wall; + } function getAvatarLink(): string { @@ -182,6 +191,14 @@ class Club extends RowModel $this->stateChanges("shortcode", $code); return true; } + + function setWall(int $type) + { + if($type > 2 || $type < 0) + throw new \LogicException("Invalid wall"); + + $this->stateChanges("wall", $type); + } function isSubscriptionAccepted(User $user): bool { @@ -291,6 +308,21 @@ class Club extends RowModel yield $rel; } } + + function getSuggestedPostsCount(User $user = NULL) + { + $count = 0; + + if(is_null($user)) + return NULL; + + if($this->canBeModifiedBy($user)) + $count = (new Posts)->getSuggestedPostsCount($this->getId()); + else + $count = (new Posts)->getSuggestedPostsCountByUser($this->getId(), $user->getId()); + + return $count; + } function getManagers(int $page = 1, bool $ignoreHidden = false): \Traversable { diff --git a/Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php b/Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php new file mode 100644 index 00000000..e1795b08 --- /dev/null +++ b/Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php @@ -0,0 +1,13 @@ +hasEnded() && !$this->hasVoted($user); + return !$this->hasEnded() && !$this->hasVoted($user) && !is_null($this->getAttachedPost()) && $this->getAttachedPost()->getSuggestionType() == 0; } function vote(User $user, array $optionIds): void @@ -292,4 +292,17 @@ class Poll extends Attachable ]); } } + + function getAttachedPost() + { + $post = DatabaseConnection::i()->getContext()->table("attachments") + ->where( + ["attachable_type" => static::class, + "attachable_id" => $this->getId()])->fetch(); + + if(!is_null($post->target_id)) + return (new Posts)->get($post->target_id); + else + return NULL; + } } diff --git a/Web/Models/Entities/Post.php b/Web/Models/Entities/Post.php index a8d444eb..48a2191f 100644 --- a/Web/Models/Entities/Post.php +++ b/Web/Models/Entities/Post.php @@ -207,6 +207,9 @@ class Post extends Postable function canBeDeletedBy(User $user): bool { + if($this->getTargetWall() < 0 && !$this->getWallOwner()->canBeModifiedBy($user) && $this->getWallOwner()->getWallType() != 1 && $this->getSuggestionType() == 0) + return false; + return $this->getOwnerPost() === $user->getId() || $this->canBePinnedBy($user); } @@ -245,6 +248,11 @@ class Post extends Postable $this->unwire(); $this->save(); } + + function getSuggestionType() + { + return $this->getRecord()->suggested; + } function toNotifApiStruct() { @@ -273,6 +281,12 @@ class Post extends Postable if($this->getTargetWall() > 0) return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId(); + else { + if($this->isPostedOnBehalfOfGroup()) + return $this->getWallOwner()->canBeModifiedBy($user); + else + return $user->getId() == $this->getOwner(false)->getId(); + } return $user->getId() == $this->getOwner(false)->getId(); } diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php index a6478605..55e1adf7 100644 --- a/Web/Models/Entities/Postable.php +++ b/Web/Models/Entities/Postable.php @@ -33,7 +33,7 @@ abstract class Postable extends Attachable { $oid = (int) $this->getRecord()->owner; if(!$real && $this->isAnonymous()) - $oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"]; + $oid = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"]; $oid = abs($oid); if($oid > 0) diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php index c354f152..89ee58ea 100644 --- a/Web/Models/Repositories/Posts.php +++ b/Web/Models/Repositories/Posts.php @@ -58,14 +58,59 @@ class Posts } $sel = $this->posts->where([ - "wall" => $user, - "pinned" => false, - "deleted" => false, + "wall" => $user, + "pinned" => false, + "deleted" => false, + "suggested" => 0, ])->order("created DESC")->limit($perPage, $offset); foreach($sel as $post) yield new Post($post); } + + function getOwnersPostsFromWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable + { + $perPage ??= OPENVK_DEFAULT_PER_PAGE; + $offset ??= $perPage * ($page - 1); + + $sel = $this->posts->where([ + "wall" => $user, + "deleted" => false, + "suggested" => 0, + ]); + + if($user > 0) + $sel->where("owner", $user); + else + $sel->where("flags !=", 0); + + $sel->order("created DESC")->limit($perPage, $offset); + + foreach($sel as $post) + yield new Post($post); + } + + function getOthersPostsFromWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable + { + $perPage ??= OPENVK_DEFAULT_PER_PAGE; + $offset ??= $perPage * ($page - 1); + + $sel = $this->posts->where([ + "wall" => $user, + "deleted" => false, + "suggested" => 0, + ]); + + if($user > 0) + $sel->where("owner !=", $user); + else + $sel->where("flags", 0); + + $sel->order("created DESC")->limit($perPage, $offset); + + foreach($sel as $post) + yield new Post($post); + } function getPostsByHashtag(string $hashtag, int $page = 1, ?int $perPage = NULL): \Traversable { @@ -74,6 +119,7 @@ class Posts ->where("MATCH (content) AGAINST (? IN BOOLEAN MODE)", "+$hashtag") ->where("deleted", 0) ->order("created DESC") + ->where("suggested", 0) ->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); foreach($sel as $post) @@ -85,14 +131,22 @@ class Posts $hashtag = "#$hashtag"; $sel = $this->posts ->where("content LIKE ?", "%$hashtag%") - ->where("deleted", 0); + ->where("deleted", 0) + ->where("suggested", 0); return sizeof($sel); } - function getPostById(int $wall, int $post): ?Post + function getPostById(int $wall, int $post, bool $forceSuggestion = false): ?Post { - $post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post])->fetch(); + $post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post]); + + if(!$forceSuggestion) { + $post->where("suggested", 0); + } + + $post = $post->fetch(); + if(!is_null($post)) return new Post($post); else @@ -112,7 +166,7 @@ class Posts else $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; - $result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0); + $result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0)->where("suggested", 0); $nnparamsCount = sizeof($notNullParams); if($nnparamsCount > 0) { @@ -134,7 +188,66 @@ class Posts function getPostCountOnUserWall(int $user): int { - return sizeof($this->posts->where(["wall" => $user, "deleted" => 0])); + return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])); + } + + function getOwnersCountOnUserWall(int $user): int + { + if($user > 0) + return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "owner" => $user])); + else + return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])->where("flags !=", 0)); + } + + function getOthersCountOnUserWall(int $user): int + { + if($user > 0) + return sizeof($this->posts->where(["wall" => $user, "deleted" => 0])->where("owner !=", $user)); + else + return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])->where("flags", 0)); + } + + function getSuggestedPosts(int $club, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable + { + $perPage ??= OPENVK_DEFAULT_PER_PAGE; + $offset ??= $perPage * ($page - 1); + + $sel = $this->posts + ->where("deleted", 0) + ->where("wall", $club * -1) + ->order("created DESC") + ->where("suggested", 1) + ->limit($perPage, $offset); + + foreach($sel as $post) + yield new Post($post); + } + + function getSuggestedPostsCount(int $club) + { + return sizeof($this->posts->where(["wall" => $club * -1, "deleted" => 0, "suggested" => 1])); + } + + function getSuggestedPostsByUser(int $club, int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable + { + $perPage ??= OPENVK_DEFAULT_PER_PAGE; + $offset ??= $perPage * ($page - 1); + + $sel = $this->posts + ->where("deleted", 0) + ->where("wall", $club * -1) + ->where("owner", $user) + ->order("created DESC") + ->where("suggested", 1) + ->limit($perPage, $offset); + + foreach($sel as $post) + yield new Post($post); + } + + function getSuggestedPostsCountByUser(int $club, int $user): int + { + return sizeof($this->posts->where(["wall" => $club * -1, "deleted" => 0, "suggested" => 1, "owner" => $user])); } function getCount(): int diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index eb8f446c..beeede13 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -3,7 +3,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\{Club, Photo, Post}; use Nette\InvalidStateException; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; -use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios}; +use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts}; use Chandler\Security\Authenticator; final class GroupPresenter extends OpenVKPresenter @@ -35,6 +35,13 @@ final class GroupPresenter extends OpenVKPresenter $this->template->audiosCount = (new Audios)->getClubCollectionSize($club); } + if(!is_null($this->user->identity) && $club->getWallType() == 2) { + if(!$club->canBeModifiedBy($this->user->identity)) + $this->template->suggestedPostsCountByUser = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id); + else + $this->template->suggestedPostsCountByEveryone = (new Posts)->getSuggestedPostsCount($club->getId()); + } + $this->template->club = $club; } } @@ -216,7 +223,12 @@ final class GroupPresenter extends OpenVKPresenter $club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name")); $club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); - $club->setWall(empty($this->postParam("wall")) ? 0 : 1); + try { + $club->setWall(empty($this->postParam("wall")) ? 0 : (int)$this->postParam("wall")); + } catch(\Exception $e) { + $this->flashFail("err", tr("error"), tr("error_invalid_wall_value")); + } + $club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display")); $club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1); $club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1); @@ -414,4 +426,37 @@ final class GroupPresenter extends OpenVKPresenter $this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName())); } + + function renderSuggested(int $id): void + { + $this->assertUserLoggedIn(); + + $club = $this->clubs->get($id); + if(!$club) + $this->notFound(); + else + $this->template->club = $club; + + if($club->getWallType() == 0) { + $this->flash("err", tr("error_suggestions"), tr("error_suggestions_closed")); + $this->redirect("/club".$club->getId()); + } + + if($club->getWallType() == 1) { + $this->flash("err", tr("error_suggestions"), tr("error_suggestions_open")); + $this->redirect("/club".$club->getId()); + } + + if(!$club->canBeModifiedBy($this->user->identity)) { + $this->template->posts = iterator_to_array((new Posts)->getSuggestedPostsByUser($club->getId(), $this->user->id, (int) ($this->queryParam("p") ?? 1))); + $this->template->count = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id); + $this->template->type = "my"; + } else { + $this->template->posts = iterator_to_array((new Posts)->getSuggestedPosts($club->getId(), (int) ($this->queryParam("p") ?? 1))); + $this->template->count = (new Posts)->getSuggestedPostsCount($club->getId()); + $this->template->type = "everyone"; + } + + $this->template->page = (int) ($this->queryParam("p") ?? 1); + } } diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index e2e6b50e..dca5db04 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -104,7 +104,7 @@ final class InternalAPIPresenter extends OpenVKPresenter } if($this->postParam("parentType", false) == "post") { - $post = (new Posts)->getPostById($owner_id, $post_id); + $post = (new Posts)->getPostById($owner_id, $post_id, true); } else { $post = (new Comments)->get($post_id); } diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index a01c8262..d0f17d56 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -2,7 +2,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Exceptions\TooMuchOptionsException; use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User}; -use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification}; +use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification, PostAcceptedNotification, NewSuggestedPostsNotification}; use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos, Audios}; use Chandler\Database\DatabaseConnection; use Nette\InvalidStateException as ISE; @@ -66,11 +66,32 @@ final class WallPresenter extends OpenVKPresenter $this->template->oObj = $owner; if($user < 0) $this->template->club = $owner; + + $iterator = NULL; + $count = 0; + $type = $this->queryParam("type") ?? "all"; + + switch($type) { + default: + case "all": + $iterator = $this->posts->getPostsFromUsersWall($user, (int) ($_GET["p"] ?? 1)); + $count = $this->posts->getPostCountOnUserWall($user); + break; + case "owners": + $iterator = $this->posts->getOwnersPostsFromWall($user, (int) ($_GET["p"] ?? 1)); + $count = $this->posts->getOwnersCountOnUserWall($user); + break; + case "others": + $iterator = $this->posts->getOthersPostsFromWall($user, (int) ($_GET["p"] ?? 1)); + $count = $this->posts->getOthersCountOnUserWall($user); + break; + } $this->template->owner = $user; $this->template->canPost = $canPost; - $this->template->count = $this->posts->getPostCountOnUserWall($user); - $this->template->posts = iterator_to_array($this->posts->getPostsFromUsersWall($user, (int) ($_GET["p"] ?? 1))); + $this->template->count = $count; + $this->template->type = $type; + $this->template->posts = iterator_to_array($iterator); $this->template->paginatorConf = (object) [ "count" => $this->template->count, "page" => (int) ($_GET["p"] ?? 1), @@ -150,6 +171,7 @@ final class WallPresenter extends OpenVKPresenter ->select("id") ->where("wall IN (?)", $ids) ->where("deleted", 0) + ->where("suggested", 0) ->order("created DESC"); $this->template->paginatorConf = (object) [ "count" => sizeof($posts), @@ -169,7 +191,7 @@ final class WallPresenter extends OpenVKPresenter $page = (int) ($_GET["p"] ?? 1); $pPage = min((int) ($_GET["posts"] ?? OPENVK_DEFAULT_PER_PAGE), 50); - $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0"; + $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0 AND `posts`.`suggested` = 0"; if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT) $queryBase .= " AND `nsfw` = 0"; @@ -343,6 +365,10 @@ final class WallPresenter extends OpenVKPresenter $post->setAnonymous($anon); $post->setFlags($flags); $post->setNsfw($this->postParam("nsfw") === "on"); + + if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) + $post->setSuggested(1); + $post->save(); } catch (\LengthException $ex) { $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big")); @@ -371,12 +397,32 @@ final class WallPresenter extends OpenVKPresenter if($wall > 0) $excludeMentions[] = $wall; - $mentions = iterator_to_array($post->resolveMentions($excludeMentions)); - foreach($mentions as $mentionee) - if($mentionee instanceof User) - (new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit(); + if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) { + # Чтобы не было упоминаний из предложки + } else { + $mentions = iterator_to_array($post->resolveMentions($excludeMentions)); + + foreach($mentions as $mentionee) + if($mentionee instanceof User) + (new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit(); + } - $this->redirect($wallOwner->getURL()); + if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) { + $suggsCount = $this->posts->getSuggestedPostsCount($wallOwner->getId()); + + if($suggsCount % 10 == 0) { + $managers = $wallOwner->getManagers(); + $owner = $wallOwner->getOwner(); + (new NewSuggestedPostsNotification($owner, $wallOwner))->emit(); + + foreach($managers as $manager) + (new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit(); + } + + $this->redirect("/club".$wallOwner->getId()."/suggested"); + } else { + $this->redirect($wallOwner->getURL()); + } } function renderPost(int $wall, int $post_id): void @@ -487,7 +533,7 @@ final class WallPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); - $post = $this->posts->getPostById($wall, $post_id); + $post = $this->posts->getPostById($wall, $post_id, true); if(!$post) $this->notFound(); $user = $this->user->id; @@ -502,6 +548,9 @@ final class WallPresenter extends OpenVKPresenter else $canBeDeletedByOtherUser = false; if(!is_null($user)) { + if($post->getTargetWall() < 0 && !$post->getWallOwner()->canBeModifiedBy($this->user->identity) && $post->getWallOwner()->getWallType() != 1 && $post->getSuggestionType() == 0) + $this->flashFail("err", tr("failed_to_delete_post"), tr("error_deleting_suggested")); + if($post->getOwnerPost() == $user || $post->getTargetWall() == $user || $canBeDeletedByOtherUser) { $post->unwire(); $post->delete(); @@ -597,4 +646,93 @@ final class WallPresenter extends OpenVKPresenter "avatar" => $post->getOwner()->getAvatarUrl() ]]); } + + function renderAccept() { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(true); + + if($_SERVER["REQUEST_METHOD"] !== "POST") { + header("HTTP/1.1 405 Method Not Allowed"); + exit("Ты дебил, это точка апи."); + } + + $id = $this->postParam("id"); + $sign = $this->postParam("sign") == 1; + $content = $this->postParam("new_content"); + + $post = (new Posts)->get((int)$id); + + if(!$post || $post->isDeleted()) + $this->flashFail("err", "Error", tr("error_accepting_invalid_post"), NULL, true); + + if($post->getSuggestionType() == 0) + $this->flashFail("err", "Error", tr("error_accepting_not_suggested_post"), NULL, true); + + if($post->getSuggestionType() == 2) + $this->flashFail("err", "Error", tr("error_accepting_declined_post"), NULL, true); + + if(!$post->canBePinnedBy($this->user->identity)) + $this->flashFail("err", "Error", "Can't accept this post.", NULL, true); + + $author = $post->getOwner(); + + $flags = 0; + $flags |= 0b10000000; + + if($sign) + $flags |= 0b01000000; + + $post->setSuggested(0); + $post->setCreated(time()); + $post->setApi_Source_Name(NULL); + $post->setFlags($flags); + + if(mb_strlen($content) > 0) + $post->setContent($content); + + $post->save(); + + if($author->getId() != $this->user->id) + (new PostAcceptedNotification($author, $post, $post->getWallOwner()))->emit(); + + $this->returnJson([ + "success" => true, + "id" => $post->getPrettyId(), + "new_count" => (new Posts)->getSuggestedPostsCount($post->getWallOwner()->getId()) + ]); + } + + function renderDecline() { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(true); + + if($_SERVER["REQUEST_METHOD"] !== "POST") { + header("HTTP/1.1 405 Method Not Allowed"); + exit("Ты дебил, это метод апи."); + } + + $id = $this->postParam("id"); + $post = (new Posts)->get((int)$id); + + if(!$post || $post->isDeleted()) + $this->flashFail("err", "Error", tr("error_declining_invalid_post"), NULL, true); + + if($post->getSuggestionType() == 0) + $this->flashFail("err", "Error", tr("error_declining_not_suggested_post"), NULL, true); + + if($post->getSuggestionType() == 2) + $this->flashFail("err", "Error", tr("error_declining_declined_post"), NULL, true); + + if(!$post->canBePinnedBy($this->user->identity)) + $this->flashFail("err", "Error", "Can't decline this post.", NULL, true); + + $post->setSuggested(2); + $post->setDeleted(1); + $post->save(); + + $this->returnJson([ + "success" => true, + "new_count" => (new Posts)->getSuggestedPostsCount($post->getWallOwner()->getId()) + ]); + } } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 8f1a35f0..d636ad42 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -233,7 +233,16 @@
- {$club->getName()} + + + {ovk_proc_strtr($club->getName(), 14)} + + + + ({$club->getSuggestedPostsCount($thisUser)}) + + +
@@ -388,6 +397,7 @@ {script "js/al_api.js"} {script "js/al_mentions.js"} {script "js/al_polls.js"} + {script "js/al_suggestions.js"} {ifset $thisUser} {script "js/al_notifs.js"} diff --git a/Web/Presenters/templates/Group/Edit.xml b/Web/Presenters/templates/Group/Edit.xml index 8f85e7e7..3b865fd9 100644 --- a/Web/Presenters/templates/Group/Edit.xml +++ b/Web/Presenters/templates/Group/Edit.xml @@ -77,7 +77,12 @@ {_wall}: - {_group_allow_post_for_everyone}
+ + {_group_hide_from_global_feed} diff --git a/Web/Presenters/templates/Group/Suggested.xml b/Web/Presenters/templates/Group/Suggested.xml new file mode 100644 index 00000000..1c882675 --- /dev/null +++ b/Web/Presenters/templates/Group/Suggested.xml @@ -0,0 +1,34 @@ +{extends "../@layout.xml"} + +{block title}{_suggested} {$club->getCanonicalName()}{/block} + +{block header} + {$club->getName()} + » {if $type == "my"}{_suggested_posts_by_you}{else}{_suggested_posts_by_everyone}{/if} +{/block} + +{block content} + {if $count < 1} + {include "../components/error.xml", title => "", description => $type == "my" ? tr("no_suggested_posts_by_you") : tr("no_suggested_posts_by_people")} + {else} +

{if $type == "my"}{tr("suggested_posts_in_group_by_you", $count)}{else}{tr("suggested_posts_in_group", $count)}{/if}

+
+ {var $microblog = $thisUser->hasMicroblogEnabled()} +
+ {if $microblog} + {include "../components/post/microblogpost.xml", post => $post, commentSection => false, suggestion => true, forceNoCommentsLink => true, forceNoPinLink => true, forceNoLike => true, forceNoShareLink => true, forceNoDeleteLink => false} + {else} + {include "../components/post/oldpost.xml", post => $post, commentSection => false, suggestion => true, forceNoCommentsLink => true, forceNoPinLink => true, forceNoLike => true, forceNoShareLink => true, forceNoDeleteLink => false} + {/if} +
+ + {include "../components/paginator.xml", conf => (object) [ + "page" => $page, + "count" => $count, + "amount" => sizeof($posts), + "perPage" => OPENVK_DEFAULT_PER_PAGE, + "atBottom" => true, + ]} +
+ {/if} +{/block} diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml index de00d9b0..7d2066c2 100644 --- a/Web/Presenters/templates/Group/View.xml +++ b/Web/Presenters/templates/Group/View.xml @@ -110,7 +110,19 @@
+
+ {tr("suggested_by_you", $suggestedPostsCountByUser)} +
+ +
+ {tr("suggested_by_everyone", $suggestedPostsCountByEveryone)} +
+ {presenter "openvk!Wall->wallEmbedded", -$club->getId()} + +
{var $avatarPhoto = $club->getAvatarPhoto()} diff --git a/Web/Presenters/templates/Wall/Post.xml b/Web/Presenters/templates/Wall/Post.xml index 6ce9edd9..3d136ebd 100644 --- a/Web/Presenters/templates/Wall/Post.xml +++ b/Web/Presenters/templates/Wall/Post.xml @@ -14,6 +14,20 @@ {/block} {block content} +
+ + + +
+ {_post} +
+
{include "../components/post.xml", post => $post, forceNoCommentsLink => TRUE, forceNoDeleteLink => TRUE}
diff --git a/Web/Presenters/templates/Wall/Wall.xml b/Web/Presenters/templates/Wall/Wall.xml index a9f8845f..516d5b8f 100644 --- a/Web/Presenters/templates/Wall/Wall.xml +++ b/Web/Presenters/templates/Wall/Wall.xml @@ -15,7 +15,19 @@ {block content}
-
+ + +
{include "../components/textArea.xml", route => "/wall$owner/makePost"}
diff --git a/Web/Presenters/templates/components/notifications/6/_14_5_.xml b/Web/Presenters/templates/components/notifications/6/_14_5_.xml new file mode 100644 index 00000000..09b03c70 --- /dev/null +++ b/Web/Presenters/templates/components/notifications/6/_14_5_.xml @@ -0,0 +1,7 @@ +{var $club = $notification->getModel(1)} +{var $post = $notification->getModel(0)} + +{_group} +{$club->getName()} +{_nt_accepted_your_post} +{_nt_post_small}. diff --git a/Web/Presenters/templates/components/notifications/7/_18_5_.xml b/Web/Presenters/templates/components/notifications/7/_18_5_.xml new file mode 100644 index 00000000..b9cda475 --- /dev/null +++ b/Web/Presenters/templates/components/notifications/7/_18_5_.xml @@ -0,0 +1,5 @@ +{var $club = $notification->getModel(1)} + +{_nt_in_club} +{$club->getName()} +{_nt_new_suggested_posts} diff --git a/Web/Presenters/templates/components/post/microblogpost.xml b/Web/Presenters/templates/components/post/microblogpost.xml index 8dba32f4..0b0978bc 100644 --- a/Web/Presenters/templates/components/post/microblogpost.xml +++ b/Web/Presenters/templates/components/post/microblogpost.xml @@ -71,7 +71,7 @@ {/if}
-
+
{$post->getText()|noescape} {var $width = ($GLOBALS["_bigWall"] ?? false) ? 550 : 320} @@ -98,17 +98,18 @@
{var $actualAuthor = $post->getOwner(false)} - {_author}: - +
+
{$actualAuthor->getCanonicalName()}
- {$post->getPublicationTime()} + {$post->getPublicationTime()} ({_edited_short}) + @@ -144,6 +145,10 @@ {include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club}
+
+ + +
diff --git a/Web/Presenters/templates/components/post/oldpost.xml b/Web/Presenters/templates/components/post/oldpost.xml index 3223e6b7..6f5e16d8 100644 --- a/Web/Presenters/templates/components/post/oldpost.xml +++ b/Web/Presenters/templates/components/post/oldpost.xml @@ -52,15 +52,16 @@ {/if}
- + {$post->getPublicationTime()} ({_edited_short}){if $post->isPinned()}, {_pinned}{/if} +
-
+
{var $owner = $author->getId()} {$post->getText()|noescape} @@ -82,6 +83,10 @@
+
+ + +

 ! {_post_is_ad} diff --git a/Web/Presenters/templates/components/wall.xml b/Web/Presenters/templates/components/wall.xml index 7d205781..99d9604b 100644 --- a/Web/Presenters/templates/components/wall.xml +++ b/Web/Presenters/templates/components/wall.xml @@ -7,6 +7,8 @@
+
+
{include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true, polls => true, notes => true}
@@ -24,6 +26,7 @@ {/if}
+
{if isset($thisUser) && $thisUser->hasMicroblogEnabled()} diff --git a/Web/routes.yml b/Web/routes.yml index 2acc6496..d4496f03 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -141,6 +141,10 @@ routes: handler: "Wall->delete" - url: "/wall{num}_{num}/pin" handler: "Wall->pin" + - url: "/wall/accept" + handler: "Wall->accept" + - url: "/wall/decline" + handler: "Wall->decline" - url: "/blob_{text}/{?path}.{text}" handler: "Blob->file" placeholders: @@ -235,6 +239,8 @@ routes: handler: "Group->admin" - url: "/club{num}/setAdmin" handler: "Group->modifyAdmin" + - url: "/club{num}/suggested" + handler: "Group->suggested" - url: "/groups{num}" handler: "User->groups" - url: "/groups_pin" diff --git a/Web/static/css/main.css b/Web/static/css/main.css index 7a087705..0e8b06d9 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -2849,6 +2849,59 @@ body.article .floating_sidebar, body.article .page_content { font-size: 12px; } +.sugglist { + padding-bottom: 5px; + padding-top: 5px; + margin-bottom: -5px; + padding-left: 9px; + border-top: 1px solid gray; + background-color: #e6e6e6; + margin-top: 5px; +} + +.sugglist a { + color: #626262; +} + +.suggestionControls { + text-align: center; +} + +.button.loaded { + background: #595959 url("/assets/packages/static/openvk/img/loading_mini.gif") no-repeat 50% 50%; + padding: 2px 9px 7px 39px; + margin-bottom: -6px; +} + +@-moz-document url-prefix() { + .button.loaded { + padding: 16px 40px 6px 4px !important; + } +} + +.sugglist a:hover { + text-decoration: underline; +} + +.sugglist a[data-toogled="true"] { + text-decoration: underline; + color:#4a4a4a; +} + +.authorIcon { + height: 11px; + margin-top: 1px; + width: 10px; + float: left; + background: url("/assets/packages/static/openvk/img/person.png") no-repeat 0 0; +} + +.authorName { + margin-left: 4px; + font-weight: normal !important; + font-size: 11px; +} + .topGrayBlock { background: #F0F0F0; height: 37px; @@ -3002,3 +3055,4 @@ hr { background-repeat: no-repeat !important; background-position: 50% !important; } + diff --git a/Web/static/img/person.png b/Web/static/img/person.png new file mode 100644 index 0000000000000000000000000000000000000000..9706c1a1771df7ae8595a0b966138e38262470d3 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^AT~D#8<2F%laT;YjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!Fir8jv*Ssxf2Ze+7x))xSJ<3Z;)VnoO&bS z`I0#e%oYzg?|4X*JP*%c_*;8<@n5G~LKPd=Wq-QIoXoT3+Sd4^meaT89u9ZpU<^7n zXW`DGjqk0al#g{>V{K1#$PS3Qtt;W=kUi&l=QY;%5AXLp21@iA){DQ$&h6+r3f8>2sDEOF4d8eVJW3?;*R+34M(oVU4Fimoj*|`njxg HN@xNAN4;aR literal 0 HcmV?d00001 diff --git a/Web/static/js/al_suggestions.js b/Web/static/js/al_suggestions.js new file mode 100644 index 00000000..1c3da3e8 --- /dev/null +++ b/Web/static/js/al_suggestions.js @@ -0,0 +1,231 @@ +function endSuggestAction(new_count, post_node) { + if(document.getElementById("cound") != null) + document.getElementById("cound").innerHTML = tr("suggested_posts_in_group", new_count) + else + document.getElementById("cound_r").innerHTML = tr("suggested_by_everyone", new_count) + + if(document.querySelector("object a[href='"+location.pathname+"'] b") != null) { + document.querySelector("object a[href='"+location.pathname+"'] b").innerHTML = new_count + + if(new_count < 1) { + u("object a[href='"+location.pathname+"']").remove() + } + } + + if(new_count < 1 && document.querySelector(".sugglist") != null) { + $(".sugglist a").click() + $(".sugglist").remove() + } + + post_node.style.transition = "opacity 300ms ease-in-out"; + post_node.style.opacity = "0"; + post_node.classList.remove("post") + + setTimeout(() => {post_node.outerHTML = ""}, 300) + + if(document.querySelectorAll("#postz .post").length < 1 && new_count > 0 && document.querySelector(".paginator") != null) + loadMoreSuggestedPosts() +} + +// "Опубликовать запись" +$(document).on("click", "#publish_post", async (e) => { + let id = Number(e.currentTarget.dataset.id) + let post; + let body = ` + + + ` + + MessageBox(tr("publishing_suggested_post"), body, [tr("publish"), tr("cancel")], [(async () => { + let id = Number(e.currentTarget.dataset.id) + let post; + + let formData = new FormData() + formData.append("id", id) + formData.append("sign", document.getElementById("signatr").checked ? 1 : 0) + formData.append("new_content", document.getElementById("pooblish").value) + formData.append("hash", u("meta[name=csrf]").attr("value")) + + ky.post("/wall/accept", { + hooks: { + beforeRequest: [ + (_request) => { + e.currentTarget.classList.add("loaded") + e.currentTarget.setAttribute("value", "") + e.currentTarget.setAttribute("id", "") + } + ], + afterResponse: [ + async (_request, _options, response) => { + json = await response.json() + + if(json.success) { + NewNotification(tr("suggestion_succefully_published"), tr("suggestion_press_to_go"), null, () => {window.location.assign("/wall" + json.id)}); + endSuggestAction(json.new_count, e.currentTarget.closest("table")) + } else { + MessageBox(tr("error"), json.flash.message, [tr("ok")], [Function.noop]); + } + + e.currentTarget.setAttribute("value", tr("publish_suggested")) + e.currentTarget.classList.remove("loaded") + e.currentTarget.setAttribute("id", "publish_post") + } + ] + }, + body: formData + }) + }), Function.noop]); + + document.getElementById("pooblish").innerHTML = e.currentTarget.closest("table").querySelector(".really_text").dataset.text + document.querySelector(".ovk-diag-body").style.padding = "9px"; +}) + +// "Отклонить" +$(document).on("click", "#decline_post", async (e) => { + let id = Number(e.currentTarget.dataset.id) + + let formData = new FormData() + formData.append("id", id) + formData.append("hash", u("meta[name=csrf]").attr("value")) + + ky.post("/wall/decline", { + hooks: { + beforeRequest: [ + (_request) => { + e.currentTarget.classList.add("loaded") + e.currentTarget.setAttribute("value", "") + e.currentTarget.setAttribute("id", "") + } + ], + afterResponse: [ + async (_request, _options, response) => { + json = await response.json() + + if(json.success) { + endSuggestAction(json.new_count, e.currentTarget.closest("table")) + } else { + MessageBox(tr("error"), json.flash.message, [tr("ok")], [Function.noop]); + } + + e.currentTarget.setAttribute("value", tr("decline_suggested")) + e.currentTarget.setAttribute("id", "decline_post") + e.currentTarget.classList.remove("loaded") + } + ] + }, + body: formData + }) +}) + +function loadMoreSuggestedPosts() { + let link = location.href + + if(!link.includes("/suggested")) { + link += "/suggested" + } + + ky.get(link, { + hooks: { + beforeRequest: [ + (_request) => { + document.getElementById("postz").innerHTML = `` + } + ], + afterResponse: [ + async (_request, _options, response) => { + let text = await response.text() + let parser = new DOMParser() + let body = parser.parseFromString(text, "text/html") + + if(body.querySelectorAll(".post").length < 1) { + let url = new URL(location.href) + url.searchParams.set("p", url.searchParams.get("p") - 1) + + if(url.searchParams.get("p") < 1) { + return 0; + } + + history.pushState({}, "", url) + + loadMoreSuggestedPosts() + } + + body.querySelectorAll(".bsdn").forEach(bsdnInitElement) + document.getElementById("postz").innerHTML = body.getElementById("postz").innerHTML + } + ] + } + }) +} + +// нажатие на "x предложенных записей" +$(document).on("click", ".sugglist a", (e) => { + e.preventDefault() + + if(e.currentTarget.getAttribute("data-toogled") == null || e.currentTarget.getAttribute("data-toogled") == "false") { + e.currentTarget.setAttribute("data-toogled", "true") + document.getElementById("underHeader").style.display = "none" + document.querySelector(".insertThere").style.display = "block" + document.querySelector(".insertThere").classList.add("infContainer") + history.pushState({}, "", e.currentTarget.href) + + // если ещё ничего не подгружалось + if(document.querySelector(".insertThere").innerHTML == "") { + ky(e.currentTarget.href, { + hooks: { + beforeRequest: [ + (_request) => { + document.querySelector(".insertThere").insertAdjacentHTML("afterbegin", ``) + } + ], + afterResponse: [ + async (_request, _options, response) => { + let parser = new DOMParser + let result = parser.parseFromString(await response.text(), 'text/html').querySelector(".infContainer") + + result.querySelectorAll(".bsdn").forEach(bsdnInitElement) + document.querySelector(".insertThere").innerHTML = result.innerHTML + } + ] + } + }) + } + } else { + // переключение на нормальную стену + e.currentTarget.setAttribute("data-toogled", "false") + document.getElementById("underHeader").style.display = "block" + document.querySelector(".insertThere").style.display = "none" + document.querySelector(".insertThere").classList.remove("infContainer") + history.pushState({}, "", e.currentTarget.href.replace("/suggested", "")) + } +}) + +// нажатие на пагинатор у постов предложки +$(document).on("click", "#postz .paginator a", (e) => { + e.preventDefault() + + ky(e.currentTarget.href, { + hooks: { + beforeRequest: [ + (_request) => { + if(document.querySelector(".sugglist") != null) { + document.querySelector(".sugglist").scrollIntoView({behavior: "smooth"}) + } else { + document.querySelector(".infContainer").scrollIntoView({behavior: "smooth"}) + } + + setTimeout(() => {document.getElementById("postz").innerHTML = ``}, 500) + } + ], + afterResponse: [ + async (_request, _options, response) => { + let result = (new DOMParser).parseFromString(await response.text(), "text/html").querySelector(".infContainer") + result.querySelectorAll(".bsdn").forEach(bsdnInitElement) + + document.getElementById("postz").innerHTML = result.innerHTML + history.pushState({}, "", e.currentTarget.href) + } + ] + } + }) +}) diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index 3fc96ce0..dc4cb47f 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -445,7 +445,7 @@ tippy(".client_app", { function addNote(textareaId, nid) { if(nid > 0) { - note.value = nid + document.getElementById("note").value = nid let noteObj = document.querySelector("#nd"+nid) let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note"); @@ -453,7 +453,7 @@ function addNote(textareaId, nid) nortd.innerHTML = `${tr("note")} ${escapeHtml(noteObj.dataset.name)}` } else { - note.value = "none" + document.getElementById("note").value = "none" let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note"); nortd.style.display = "none" @@ -481,7 +481,7 @@ async function attachNote(id) ${tr("select_or_create_new")}
` - if(note.value != "none") { + if(document.getElementById("note").value != "none") { body += `
${tr("do_not_attach_note")} diff --git a/install/sqls/00043-suggest-posts.sql b/install/sqls/00043-suggest-posts.sql new file mode 100644 index 00000000..1188f559 --- /dev/null +++ b/install/sqls/00043-suggest-posts.sql @@ -0,0 +1 @@ +ALTER TABLE `posts` ADD `suggested` TINYINT(2) UNSIGNED NOT NULL DEFAULT '0' AFTER `deleted`, ADD INDEX (`suggested`); diff --git a/locales/en.strings b/locales/en.strings index 3804c5f8..802ebb15 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -230,6 +230,11 @@ "edited_short" = "edited"; +"all_posts" = "All posts"; +"users_posts" = "Posts by $1"; +"clubs_posts" = "Group's posts"; +"others_posts" = "Others posts"; + /* Friends */ "friends" = "Friends"; @@ -276,6 +281,7 @@ /* Group */ +"group" = "Group"; "name_group" = "Name"; "subscribe" = "Subscribe"; "unsubscribe" = "Unsubscribe"; @@ -313,8 +319,60 @@ "set_comment" = "Set comment"; "hidden_yes" = "Hidden: Yes"; "hidden_no" = "Hidden: No"; -"group_allow_post_for_everyone" = "Allow posting for everyone"; +"group_allow_post_for_everyone" = "Open"; +"group_limited_post" = "Suggestions"; +"group_closed_post" = "Closed"; +"suggest_new" = "Suggest post"; + +"suggested_by_you_zero" = "$1 suggested posts by you"; +"suggested_by_you_one" = "One suggested post by you"; +"suggested_by_you_few" = "$1 suggested posts by you"; +"suggested_by_you_many" = "$1 suggested posts by you"; +"suggested_by_you_other" = "$1 suggested posts by you"; + +"suggested_by_everyone_zero" = "$1 suggested posts"; +"suggested_by_everyone_one" = "One suggested post"; +"suggested_by_everyone_few" = "$1 suggested posts"; +"suggested_by_everyone_many" = "$1 suggested posts"; +"suggested_by_everyone_other" = "$1 suggested posts"; + "group_hide_from_global_feed" = "Don't display posts in the global feed"; +"suggested_posts_by_you" = "Suggested posts by you"; +"suggested_posts_by_everyone" = "Suggested posts"; +"suggested" = "Suggested"; +"suggested_posts_everyone" = "Suggested by users posts"; +"no_suggested_posts_by_you" = "You haven't suggested posts to this group yet."; +"no_suggested_posts_by_people" = "No posts have been suggested to this group yet."; + +"publish_suggested" = "Accept"; +"decline_suggested" = "Decline"; + +"error_loading_suggest" = "Error when loading new posts"; + +"publishing_suggested_post" = "Publishing suggested post"; +"suggested_posts_in_group_zero" = "You've looked at all the suggested posts, congratulations!"; +"suggested_posts_in_group_one" = "This group has one suggested post"; +"suggested_posts_in_group_few" = "This group has $1 suggested posts"; +"suggested_posts_in_group_many" = "This group has $1 suggested posts"; +"suggested_posts_in_group_other" = "This group has $1 suggested posts"; + +"suggested_posts_in_group_by_you_zero" = "You haven't suggested any posts to this group"; +"suggested_posts_in_group_by_you_one" = "You suggested one post to this group"; +"suggested_posts_in_group_by_you_few" = "You suggested $1 posts to this group"; +"suggested_posts_in_group_by_you_many" = "You suggested $1 posts to this group"; +"suggested_posts_in_group_by_you_other" = "You suggested $1 posts to this group"; + +"suggestion_succefully_published" = "Post successfully published"; +"suggestion_succefully_declined" = "Post successfully declined"; +"suggestion_press_to_go" = "Click to show it"; + +"error_declining_invalid_post" = "Error when declining post: post does not exists"; +"error_declining_not_suggested_post" = "Error when declining post: post is not suggested"; +"error_declining_declined_post" = "Error when declining post: post is already declined"; + +"error_accepting_invalid_post" = "Error when accepting post: post does not exists"; +"error_accepting_not_suggested_post" = "Error when accepting post: post is not suggested"; +"error_accepting_declined_post" = "Error when accepting post: cant accept declined post"; "statistics" = "Statistics"; "group_administrators_list" = "Admins list"; "group_display_only_creator" = "Display only group creator"; @@ -347,6 +405,11 @@ "search_by_groups" = "Explore Groups"; "search_group_desc" = "Browse through existing groups and find the one that suits your needs..."; +"error_suggestions" = "Error accessing to suggestions"; +"error_suggestions_closed" = "This group has closed wall."; +"error_suggestions_open" = "This group has open wall."; +"error_suggestions_access" = "Only group's administrators can view all suggested posts."; + "group_banned" = "Unfortunately, we had to block the $1 group."; /* Albums */ @@ -870,6 +933,9 @@ "nt_shared_yours" = "shared your"; "nt_commented_yours" = "commented"; "nt_written_on_your_wall" = "wrote on your wall"; +"nt_accepted_your_post" = "accepted your suggested"; +"nt_in_club" = "In group"; +"nt_new_suggested_posts" = "new posts in suggestions"; "nt_made_you_admin" = "appointed you in the community"; "nt_from" = "from"; @@ -890,6 +956,8 @@ "nt_mention_in_topic" = "in the discussion"; "nt_sent_gift" = "sent you a gift"; +"nt_post_small" = "post"; + /* Time */ "time_at_sp" = " at "; @@ -1325,6 +1393,8 @@ "media_file_corrupted_or_too_large" = "The media content file is corrupted or too large."; "post_is_empty_or_too_big" = "The post is empty or too big."; "post_is_too_big" = "The post is too big."; +"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..."; diff --git a/locales/ru.strings b/locales/ru.strings index a04af131..3977fa3a 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -206,6 +206,11 @@ "post_is_ad" = "Этот пост был размещён за взятку."; "edited_short" = "ред."; +"all_posts" = "Все записи"; +"users_posts" = "Записи $1"; +"clubs_posts" = "Записи сообщества"; +"others_posts" = "Чужие записи"; + /* Friends */ "friends" = "Друзья"; @@ -259,6 +264,7 @@ /* Group */ +"group" = "Сообщество"; "name_group" = "Название"; "subscribe" = "Подписаться"; "unsubscribe" = "Отписаться"; @@ -295,8 +301,61 @@ "set_comment" = "Изменить комментарий"; "hidden_yes" = "Скрыт: Да"; "hidden_no" = "Скрыт: Нет"; -"group_allow_post_for_everyone" = "Разрешить публиковать записи всем"; + +"group_allow_post_for_everyone" = "Открытая"; +"group_limited_post" = "Предложка"; +"group_closed_post" = "Закрытая"; +"suggest_new" = "Предложить новость"; +"suggested_by_you_zero" = "$1 предложенных вами записей"; +"suggested_by_you_one" = "Одна предложенная вами запись"; +"suggested_by_you_few" = "$1 предложенные вами записи"; +"suggested_by_you_many" = "$1 предложенных вами записей"; +"suggested_by_you_other" = "$1 предложенных вами записей"; + +"suggested_by_everyone_zero" = "$1 предложенных записей"; +"suggested_by_everyone_one" = "Одна предложенная запись"; +"suggested_by_everyone_few" = "$1 предложенные записи"; +"suggested_by_everyone_many" = "$1 предложенных записей"; +"suggested_by_everyone_other" = "$1 предложенных записей"; + "group_hide_from_global_feed" = "Не отображать публикации в глобальной ленте"; +"suggested_posts_by_you" = "Предложенные вами записи"; +"suggested_posts_by_everyone" = "Предложенные записи"; +"suggested" = "Предложено"; +"suggested_posts_everyone" = "Предложенные пользователями записи"; +"no_suggested_posts_by_you" = "Вы ещё не предлагали записей в эту группу."; +"no_suggested_posts_by_people" = "В эту группу ещё не предлагали записей."; + +"publish_suggested" = "Опубликовать запись"; +"decline_suggested" = "Отклонить"; + +"error_loading_suggest" = "Не удалось подгрузить новые посты"; + +"publishing_suggested_post" = "Публикация предложенной записи"; +"suggested_posts_in_group_zero" = "Вы посмотрели всю предложку, поздравляю!"; +"suggested_posts_in_group_one" = "В эту группу предложили одну запись"; +"suggested_posts_in_group_few" = "В эту группу предложили $1 записи"; +"suggested_posts_in_group_many" = "В эту группу предложили $1 записей"; +"suggested_posts_in_group_other" = "В эту группу предложили $1 записей"; + +"suggested_posts_in_group_by_you_zero" = "Вы не предлагали в эту группу никаких записей"; +"suggested_posts_in_group_by_you_one" = "Вы предложили в эту группу одну запись"; +"suggested_posts_in_group_by_you_few" = "Вы предложили в эту группу $1 записи"; +"suggested_posts_in_group_by_you_many" = "Вы предложили в эту группу $1 записей"; +"suggested_posts_in_group_by_you_other" = "Вы предложили в эту группу $1 записей"; + +"suggestion_succefully_published" = "Запись успешно опубликована"; +"suggestion_succefully_declined" = "Запись успешно отклонена"; +"suggestion_press_to_go" = "Нажмите, чтобы перейти к ней"; + +"error_declining_invalid_post" = "Не удалость отклонить пост: поста не существует"; +"error_declining_not_suggested_post" = "Не удалость отклонить пост: пост не из предложки"; +"error_declining_declined_post" = "Не удалость отклонить пост: пост уже отклонён"; + +"error_accepting_invalid_post" = "Не удалость принять пост: поста не существует"; +"error_accepting_not_suggested_post" = "Не удалость принять пост: пост не из предложки"; +"error_accepting_declined_post" = "Не удалость принять пост: пост отклонён"; + "statistics" = "Статистика"; "group_administrators_list" = "Список админов"; "group_display_only_creator" = "Отображать только создателя группы"; @@ -330,6 +389,11 @@ "search_group_desc" = "Здесь Вы можете просмотреть существующие группы и выбрать группу себе по вкусу..."; "group_banned" = "К сожалению, нам пришлось заблокировать сообщество $1."; +"error_suggestions" = "Ошибка доступа к предложенным"; +"error_suggestions_closed" = "У этой группы закрытая стена."; +"error_suggestions_open" = "У этой группы открытая стена."; +"error_suggestions_access" = "Просматривать все предложенные записи могут только администраторы группы."; + /* Albums */ "create" = "Создать"; @@ -821,6 +885,9 @@ "nt_shared_yours" = "поделился(-ась) вашим"; "nt_commented_yours" = "оставил(а) комментарий под"; "nt_written_on_your_wall" = "написал(а) на вашей стене"; +"nt_accepted_your_post" = "опубликовало вашу предложенную"; +"nt_in_club" = "В сообществе"; +"nt_new_suggested_posts" = "новые записи в предложке"; "nt_made_you_admin" = "назначил(а) вас руководителем сообщества"; "nt_from" = "от"; "nt_yours_adjective" = "вашим"; @@ -838,6 +905,7 @@ "nt_mention_in_video" = "в обсуждении видеозаписи"; "nt_mention_in_note" = "в обсуждении заметки"; "nt_mention_in_topic" = "в обсуждении"; +"nt_post_small" = "запись"; "nt_sent_gift" = "отправил вам подарок"; /* Time */ @@ -1226,6 +1294,10 @@ "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" = "Изображение подарка кривое."; diff --git a/themepacks/midnight/stylesheet.css b/themepacks/midnight/stylesheet.css index 73030b40..88e1d30b 100644 --- a/themepacks/midnight/stylesheet.css +++ b/themepacks/midnight/stylesheet.css @@ -243,6 +243,19 @@ input[type="radio"] { border-color: #473e66 !important; } +.sugglist { + background-color: #231e33; + border-color: #2c2640 !important; +} + +.sugglist a { + color: #7c94c5; +} + +.button.loaded { + background: #383052 url("/assets/packages/static/openvk/img/loading_mini.gif") no-repeat 50% 50% !important; +} + .bigPlayer { background-color: rgb(30, 26, 43) !important; } diff --git a/themepacks/openvk_modern/stylesheet.css b/themepacks/openvk_modern/stylesheet.css index 841e92ec..cd3a3b3a 100644 --- a/themepacks/openvk_modern/stylesheet.css +++ b/themepacks/openvk_modern/stylesheet.css @@ -47,13 +47,13 @@ body { .content_title_expanded { cursor: pointer; - background-image: unset; + background-image: unset !important; padding: 3px 10px; border-top: #e6e6e6 solid 1px; } .content_title_unexpanded { - background-image: unset; + background-image: unset !important; padding: 3px 10px; border-top: #eee solid 1px; } @@ -329,6 +329,11 @@ input[type=checkbox] { border-top: 1px solid #2f2f2f; } +.sugglist { + border-top: unset; + border-bottom: 1px solid gray; +} + .musicIcon { filter: contrast(202%) !important; }