Merge branch 'master' into other-gender-options
1
.gitignore
vendored
|
@ -10,5 +10,6 @@ tmp/*
|
|||
themepacks/*
|
||||
!themepacks/.gitkeep
|
||||
!themepacks/openvk_modern
|
||||
!themepacks/midnight
|
||||
storage/*
|
||||
!storage/.gitkeep
|
||||
|
|
50
ServiceAPI/Mentions.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\ServiceAPI;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs};
|
||||
|
||||
class Mentions implements Handler
|
||||
{
|
||||
protected $user;
|
||||
|
||||
function __construct(?User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
function resolve(int $id, callable $resolve, callable $reject): void
|
||||
{
|
||||
if($id > 0) {
|
||||
$user = (new Users)->get($id);
|
||||
if(!$user) {
|
||||
$reject("Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
$resolve([
|
||||
"url" => $user->getURL(),
|
||||
"name" => $user->getFullName(),
|
||||
"ava" => $user->getAvatarURL("miniscule"),
|
||||
"about" => $user->getStatus() ?? "",
|
||||
"online" => ($user->isFemale() ? tr("was_online_f") : tr("was_online_m")) . " " . $user->getOnline(),
|
||||
"verif" => $user->isVerified(),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$club = (new Clubs)->get(abs($id));
|
||||
if(!$club) {
|
||||
$reject("Not found");
|
||||
return;
|
||||
}
|
||||
|
||||
$resolve([
|
||||
"url" => $club->getURL(),
|
||||
"name" => $club->getName(),
|
||||
"ava" => $club->getAvatarURL("miniscule"),
|
||||
"about" => $club->getDescription() ?? "",
|
||||
"online" => tr("participants", $club->getFollowersCount()),
|
||||
"verif" => $club->isVerified(),
|
||||
]);
|
||||
}
|
||||
}
|
70
ServiceAPI/Polls.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\ServiceAPI;
|
||||
use Chandler\MVC\Routing\Router;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Exceptions\{AlreadyVotedException, InvalidOptionException, PollLockedException};
|
||||
use openvk\Web\Models\Repositories\Polls as PollRepo;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class Polls implements Handler
|
||||
{
|
||||
protected $user;
|
||||
protected $polls;
|
||||
|
||||
function __construct(?User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->polls = new PollRepo;
|
||||
}
|
||||
|
||||
private function getPollHtml(int $poll): string
|
||||
{
|
||||
return Router::i()->execute("/poll$poll", "SAPI");
|
||||
}
|
||||
|
||||
function vote(int $pollId, string $options, callable $resolve, callable $reject): void
|
||||
{
|
||||
$poll = $this->polls->get($pollId);
|
||||
if(!$poll) {
|
||||
$reject("Poll not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$options = explode(",", $options);
|
||||
$poll->vote($this->user, $options);
|
||||
} catch(AlreadyVotedException $ex) {
|
||||
$reject("Poll state changed: user has already voted.");
|
||||
return;
|
||||
} catch(PollLockedException $ex) {
|
||||
$reject("Poll state changed: poll has ended.");
|
||||
return;
|
||||
} catch(InvalidOptionException $ex) {
|
||||
$reject("Foreign options passed.");
|
||||
return;
|
||||
} catch(UnexpectedValueException $ex) {
|
||||
$reject("Too much options passed.");
|
||||
return;
|
||||
}
|
||||
|
||||
$resolve(["html" => $this->getPollHtml($pollId)]);
|
||||
}
|
||||
|
||||
function unvote(int $pollId, callable $resolve, callable $reject): void
|
||||
{
|
||||
$poll = $this->polls->get($pollId);
|
||||
if(!$poll) {
|
||||
$reject("Poll not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$poll->revokeVote($this->user);
|
||||
} catch(PollLockedException $ex) {
|
||||
$reject("Votes can't be revoked from this poll.");
|
||||
return;
|
||||
}
|
||||
|
||||
$resolve(["html" => $this->getPollHtml($pollId)]);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ final class Account extends VKAPIRequestHandler
|
|||
"last_name" => $this->getUser()->getLastName(),
|
||||
"home_town" => $this->getUser()->getHometown(),
|
||||
"status" => $this->getUser()->getStatus(),
|
||||
"bdate" => $this->getUser()->getBirthday()->format('%e.%m.%Y'),
|
||||
"bdate" => is_null($this->getUser()->getBirthday()) ? '01.01.1970' : $this->getUser()->getBirthday()->format('%e.%m.%Y'),
|
||||
"bdate_visibility" => $this->getUser()->getBirthdayPrivacy(),
|
||||
"phone" => "+420 ** *** 228", # TODO
|
||||
"relation" => $this->getUser()->getMaritalStatus(),
|
||||
|
|
|
@ -133,15 +133,18 @@ final class Friends extends VKAPIRequestHandler
|
|||
return $response;
|
||||
}
|
||||
|
||||
function getRequests(string $fields = "", int $offset = 0, int $count = 100): object
|
||||
function getRequests(string $fields = "", int $offset = 0, int $count = 100, int $extended = 0): object
|
||||
{
|
||||
if ($count >= 1000)
|
||||
$this->fail(100, "One of the required parameters was not passed or is invalid.");
|
||||
|
||||
$this->requireUser();
|
||||
|
||||
$i = 0;
|
||||
$offset++;
|
||||
$followers = [];
|
||||
|
||||
foreach($this->getUser()->getFollowers() as $follower) {
|
||||
foreach($this->getUser()->getFollowers($offset, $count) as $follower) {
|
||||
$followers[$i] = $follower->getId();
|
||||
$i++;
|
||||
}
|
||||
|
@ -149,8 +152,10 @@ final class Friends extends VKAPIRequestHandler
|
|||
$response = $followers;
|
||||
$usersApi = new Users($this->getUser());
|
||||
|
||||
if(!is_null($fields))
|
||||
$response = $usersApi->get(implode(',', $followers), $fields, 0, $count); # FIXME
|
||||
if($extended == 1)
|
||||
$response = $usersApi->get(implode(',', $followers), $fields, 0, $count);
|
||||
else
|
||||
$response = $usersApi->get(implode(',', $followers), "", 0, $count);
|
||||
|
||||
foreach($response as $user)
|
||||
$user->user_id = $user->id;
|
||||
|
|
|
@ -10,7 +10,7 @@ final class Groups extends VKAPIRequestHandler
|
|||
$this->requireUser();
|
||||
|
||||
if($user_id == 0) {
|
||||
foreach($this->getUser()->getClubs($offset+1) as $club)
|
||||
foreach($this->getUser()->getClubs($offset, false, $count, true) as $club)
|
||||
$clbs[] = $club;
|
||||
$clbsCount = $this->getUser()->getClubCount();
|
||||
} else {
|
||||
|
@ -20,7 +20,7 @@ final class Groups extends VKAPIRequestHandler
|
|||
if(is_null($user))
|
||||
$this->fail(15, "Access denied");
|
||||
|
||||
foreach($user->getClubs($offset+1) as $club)
|
||||
foreach($user->getClubs($offset, false, $count, true) as $club)
|
||||
$clbs[] = $club;
|
||||
|
||||
$clbsCount = $user->getClubCount();
|
||||
|
@ -33,17 +33,9 @@ final class Groups extends VKAPIRequestHandler
|
|||
$ic = $count;
|
||||
|
||||
if(!empty($clbs)) {
|
||||
$clbs = array_slice($clbs, $offset * $count);
|
||||
|
||||
for($i=0; $i < $ic; $i++) {
|
||||
$usr = $clbs[$i];
|
||||
if(is_null($usr)) {
|
||||
$rClubs[$i] = (object)[
|
||||
"id" => $clbs[$i],
|
||||
"name" => "DELETED",
|
||||
"deactivated" => "deleted"
|
||||
];
|
||||
} else if($clbs[$i] == NULL) {
|
||||
if(is_null($usr)) {
|
||||
|
||||
} else {
|
||||
$rClubs[$i] = (object) [
|
||||
|
@ -102,23 +94,32 @@ final class Groups extends VKAPIRequestHandler
|
|||
];
|
||||
}
|
||||
|
||||
function getById(string $group_ids = "", string $group_id = "", string $fields = ""): ?array
|
||||
function getById(string $group_ids = "", string $group_id = "", string $fields = "", int $offset = 0, int $count = 500): ?array
|
||||
{
|
||||
/* Both offset and count SHOULD be used only in OpenVK code,
|
||||
not in your app or script, since it's not oficially documented by VK */
|
||||
|
||||
$clubs = new ClubsRepo;
|
||||
|
||||
if($group_ids == NULL && $group_id != NULL)
|
||||
if(empty($group_ids) && !empty($group_id))
|
||||
$group_ids = $group_id;
|
||||
|
||||
if($group_ids == NULL && $group_id == NULL)
|
||||
if(empty($group_ids) && empty($group_id))
|
||||
$this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined");
|
||||
|
||||
$clbs = explode(',', $group_ids);
|
||||
$response;
|
||||
$response = array();
|
||||
|
||||
$ic = sizeof($clbs);
|
||||
|
||||
if(sizeof($clbs) > $count)
|
||||
$ic = $count;
|
||||
|
||||
$clbs = array_slice($clbs, $offset * $count);
|
||||
|
||||
|
||||
for($i=0; $i < $ic; $i++) {
|
||||
if($i > 500)
|
||||
if($i > 500 || $clbs[$i] == 0)
|
||||
break;
|
||||
|
||||
if($clbs[$i] < 0)
|
||||
|
@ -142,6 +143,7 @@ final class Groups extends VKAPIRequestHandler
|
|||
"screen_name" => $clb->getShortCode() ?? "club".$clb->getId(),
|
||||
"is_closed" => false,
|
||||
"type" => "group",
|
||||
"is_member" => !is_null($this->getUser()) ? (int) $clb->getSubscriptionStatus($this->getUser()) : 0,
|
||||
"can_access_closed" => true,
|
||||
];
|
||||
|
||||
|
@ -204,10 +206,6 @@ final class Groups extends VKAPIRequestHandler
|
|||
else
|
||||
$response[$i]->can_post = $clb->canPost();
|
||||
break;
|
||||
case "is_member":
|
||||
if(!is_null($this->getUser()))
|
||||
$response[$i]->is_member = (int) $clb->getSubscriptionStatus($this->getUser());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,4 +213,24 @@ final class Groups extends VKAPIRequestHandler
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
function search(string $q, int $offset = 0, int $count = 100)
|
||||
{
|
||||
$clubs = new ClubsRepo;
|
||||
|
||||
$array = [];
|
||||
$find = $clubs->find($q);
|
||||
|
||||
foreach ($find as $group)
|
||||
$array[] = $group->getId();
|
||||
|
||||
return (object) [
|
||||
"count" => $find->size(),
|
||||
"items" => $this->getById(implode(',', $array), "", "is_admin,is_member,is_advertiser,photo_50,photo_100,photo_200", $offset, $count)
|
||||
/*
|
||||
* As there is no thing as "fields" by the original documentation
|
||||
* i'll just bake this param by the example shown here: https://dev.vk.com/method/groups.search
|
||||
*/
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,17 @@ final class Wall extends VKAPIRequestHandler
|
|||
$groups = [];
|
||||
$cnt = $posts->getPostCountOnUserWall($owner_id);
|
||||
|
||||
$wallOnwer = (new UsersRepo)->get($owner_id);
|
||||
if ($owner_id > 0)
|
||||
$wallOnwer = (new UsersRepo)->get($owner_id);
|
||||
else
|
||||
$wallOnwer = (new ClubsRepo)->get($owner_id * -1);
|
||||
|
||||
if(!$wallOnwer || $wallOnwer->isDeleted() || $wallOnwer->isDeleted())
|
||||
$this->fail(18, "User was deleted or banned");
|
||||
if ($owner_id > 0)
|
||||
if(!$wallOnwer || $wallOnwer->isDeleted())
|
||||
$this->fail(18, "User was deleted or banned");
|
||||
else
|
||||
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) {
|
||||
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
|
||||
|
@ -37,12 +44,14 @@ final class Wall extends VKAPIRequestHandler
|
|||
continue;
|
||||
|
||||
$attachments[] = $this->getApiPhoto($attachment);
|
||||
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
|
||||
$attachments[] = $this->getApiPoll($attachment, $this->getUser());
|
||||
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
|
||||
$repostAttachments = [];
|
||||
|
||||
foreach($attachment->getChildren() as $repostAttachment) {
|
||||
if($repostAttachment instanceof \openvk\Web\Models\Entities\Photo) {
|
||||
if($attachment->isDeleted())
|
||||
if($repostAttachment->isDeleted())
|
||||
continue;
|
||||
|
||||
$repostAttachments[] = $this->getApiPhoto($repostAttachment);
|
||||
|
@ -178,6 +187,8 @@ final class Wall extends VKAPIRequestHandler
|
|||
foreach($post->getChildren() as $attachment) {
|
||||
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
|
||||
$attachments[] = $this->getApiPhoto($attachment);
|
||||
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
|
||||
$attachments[] = $this->getApiPoll($attachment, $user);
|
||||
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
|
||||
$repostAttachments = [];
|
||||
|
||||
|
@ -561,7 +572,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
private function getApiPhoto($attachment) {
|
||||
return [
|
||||
"type" => "photo",
|
||||
|
@ -576,4 +587,44 @@ final class Wall extends VKAPIRequestHandler
|
|||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function getApiPoll($attachment, $user) {
|
||||
$answers = array();
|
||||
foreach($attachment->getResults()->options as $answer) {
|
||||
$answers[] = (object)[
|
||||
"id" => $answer->id,
|
||||
"rate" => $answer->pct,
|
||||
"text" => $answer->name,
|
||||
"votes" => $answer->votes
|
||||
];
|
||||
}
|
||||
|
||||
$userVote = array();
|
||||
foreach($attachment->getUserVote($user) as $vote)
|
||||
$userVote[] = $vote[0];
|
||||
|
||||
return [
|
||||
"type" => "poll",
|
||||
"poll" => [
|
||||
"multiple" => $attachment->isMultipleChoice(),
|
||||
"end_date" => $attachment->endsAt() == NULL ? 0 : $attachment->endsAt()->timestamp(),
|
||||
"closed" => $attachment->hasEnded(),
|
||||
"is_board" => false,
|
||||
"can_edit" => false,
|
||||
"can_vote" => $attachment->canVote($user),
|
||||
"can_report" => false,
|
||||
"can_share" => true,
|
||||
"created" => 0,
|
||||
"id" => $attachment->getId(),
|
||||
"owner_id" => $attachment->getOwner()->getId(),
|
||||
"question" => $attachment->getTitle(),
|
||||
"votes" => $attachment->getVoterCount(),
|
||||
"disable_unvote" => $attachment->isRevotable(),
|
||||
"anonymous" => $attachment->isAnonymous(),
|
||||
"answer_ids" => $userVote,
|
||||
"answers" => $answers,
|
||||
"author_id" => $attachment->getOwner()->getId(),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,14 +27,23 @@ class NewMessageEvent implements ILPEmitable
|
|||
if($peer === $userId)
|
||||
$peer = $msg->getRecipient()->getId();
|
||||
|
||||
/*
|
||||
* Source:
|
||||
* https://github.com/danyadev/longpoll-doc
|
||||
*/
|
||||
|
||||
return [
|
||||
4, # event type
|
||||
$msg->getId(), # messageId
|
||||
256, # checked for spam flag
|
||||
$peer, # TODO calculate peer correctly
|
||||
$msg->getSendTime()->timestamp(), # creation time in unix
|
||||
$msg->getText(), # text (formatted)
|
||||
[], # empty additional info
|
||||
[], # empty attachments
|
||||
$msg->getId() << 2, # id as random_id
|
||||
$peer, # conversation id
|
||||
0 # not edited yet
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
34
Web/Models/Entities/Alias.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\{User, Club};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs};
|
||||
|
||||
class Alias extends RowModel
|
||||
{
|
||||
protected $tableName = "aliases";
|
||||
|
||||
function getOwnerId(): int
|
||||
{
|
||||
return $this->getRecord()->owner_id;
|
||||
}
|
||||
|
||||
function getType(): string
|
||||
{
|
||||
if ($this->getOwnerId() < 0)
|
||||
return "club";
|
||||
|
||||
return "user";
|
||||
}
|
||||
|
||||
function getUser(): ?User
|
||||
{
|
||||
return (new Users)->get($this->getOwnerId());
|
||||
}
|
||||
|
||||
function getClub(): ?Club
|
||||
{
|
||||
return (new Clubs)->get($this->getOwnerId() * -1);
|
||||
}
|
||||
}
|
295
Web/Models/Entities/Poll.php
Normal file
|
@ -0,0 +1,295 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
|
||||
use openvk\Web\Util\DateTime;
|
||||
use \UnexpectedValueException;
|
||||
use Nette\InvalidStateException;
|
||||
use openvk\Web\Models\Repositories\Users;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use openvk\Web\Models\Exceptions\PollLockedException;
|
||||
use openvk\Web\Models\Exceptions\AlreadyVotedException;
|
||||
use openvk\Web\Models\Exceptions\InvalidOptionException;
|
||||
|
||||
class Poll extends Attachable
|
||||
{
|
||||
protected $tableName = "polls";
|
||||
|
||||
private $choicesToPersist = [];
|
||||
|
||||
function getTitle(): string
|
||||
{
|
||||
return $this->getRecord()->title;
|
||||
}
|
||||
|
||||
function getMetaDescription(): string
|
||||
{
|
||||
$props = [];
|
||||
$props[] = tr($this->isAnonymous() ? "poll_anon" : "poll_public");
|
||||
if($this->isMultipleChoice()) $props[] = tr("poll_multi");
|
||||
if(!$this->isRevotable()) $props[] = tr("poll_lock");
|
||||
if(!is_null($this->endsAt())) $props[] = tr("poll_until", $this->endsAt());
|
||||
|
||||
return implode(" • ", $props);
|
||||
}
|
||||
|
||||
function getOwner(): User
|
||||
{
|
||||
return (new Users)->get($this->getRecord()->owner);
|
||||
}
|
||||
|
||||
function getOptions(): array
|
||||
{
|
||||
$options = $this->getRecord()->related("poll_options.poll");
|
||||
$res = [];
|
||||
foreach($options as $opt)
|
||||
$res[$opt->id] = $opt->name;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function getUserVote(User $user): ?array
|
||||
{
|
||||
$ctx = DatabaseConnection::i()->getContext();
|
||||
$votedOpts = $ctx->table("poll_votes")
|
||||
->where(["user" => $user->getId(), "poll" => $this->getId()]);
|
||||
|
||||
if($votedOpts->count() == 0)
|
||||
return NULL;
|
||||
|
||||
$res = [];
|
||||
foreach($votedOpts as $votedOpt) {
|
||||
$option = $ctx->table("poll_options")->get($votedOpt->option);
|
||||
$res[] = [$option->id, $option->name];
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function getVoters(int $optionId, int $page = 1, ?int $perPage = NULL): array
|
||||
{
|
||||
$res = [];
|
||||
$ctx = DatabaseConnection::i()->getContext();
|
||||
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
|
||||
$voters = $ctx->table("poll_votes")->where(["poll" => $this->getId(), "option" => $optionId]);
|
||||
foreach($voters->page($page, $perPage) as $vote)
|
||||
$res[] = (new Users)->get($vote->user);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function getVoterCount(?int $optionId = NULL): int
|
||||
{
|
||||
$votes = DatabaseConnection::i()->getContext()->table("poll_votes");
|
||||
if(!$optionId)
|
||||
return $votes->select("COUNT(DISTINCT user) AS c")->where("poll", $this->getId())->fetch()->c;
|
||||
|
||||
return $votes->where(["poll" => $this->getId(), "option" => $optionId])->count();
|
||||
}
|
||||
|
||||
function getResults(?User $user = NULL): object
|
||||
{
|
||||
$ctx = DatabaseConnection::i()->getContext();
|
||||
$voted = NULL;
|
||||
if(!is_null($user))
|
||||
$voted = $this->getUserVote($user);
|
||||
|
||||
$result = (object) [];
|
||||
$result->totalVotes = $this->getVoterCount();
|
||||
|
||||
$unsOptions = [];
|
||||
foreach($this->getOptions() as $id => $title) {
|
||||
$option = (object) [];
|
||||
$option->id = $id;
|
||||
$option->name = $title;
|
||||
|
||||
$option->votes = $this->getVoterCount($id);
|
||||
$option->pct = $result->totalVotes == 0 ? 0 : min(100, floor(($option->votes / $result->totalVotes) * 100));
|
||||
$option->voters = $this->getVoters($id, 1, 10);
|
||||
if(!$user || !$voted)
|
||||
$option->voted = NULL;
|
||||
else
|
||||
$option->voted = in_array([$id, $title], $voted);
|
||||
|
||||
$unsOptions[$id] = $option;
|
||||
}
|
||||
|
||||
$optionsC = sizeof($unsOptions);
|
||||
$sOptions = $unsOptions;
|
||||
usort($sOptions, function($a, $b) { return $a->votes <=> $b->votes; });
|
||||
for($i = 0; $i < $optionsC; $i++)
|
||||
$unsOptions[$id]->rate = $optionsC - $i - 1;
|
||||
|
||||
$result->options = array_values($unsOptions);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function isAnonymous(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->is_anonymous;
|
||||
}
|
||||
|
||||
function isMultipleChoice(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->allows_multiple;
|
||||
}
|
||||
|
||||
function isRevotable(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->can_revote;
|
||||
}
|
||||
|
||||
function endsAt(): ?DateTime
|
||||
{
|
||||
if(!$this->getRecord()->until)
|
||||
return NULL;
|
||||
|
||||
return new DateTime($this->getRecord()->until);
|
||||
}
|
||||
|
||||
function hasEnded(): bool
|
||||
{
|
||||
if($this->getRecord()->ended)
|
||||
return true;
|
||||
|
||||
if(!is_null($this->getRecord()->until))
|
||||
return time() >= $this->getRecord()->until;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasVoted(User $user): bool
|
||||
{
|
||||
return !is_null($this->getUserVote($user));
|
||||
}
|
||||
|
||||
function canVote(User $user): bool
|
||||
{
|
||||
return !$this->hasEnded() && !$this->hasVoted($user);
|
||||
}
|
||||
|
||||
function vote(User $user, array $optionIds): void
|
||||
{
|
||||
if($this->hasEnded())
|
||||
throw new PollLockedException;
|
||||
|
||||
if($this->hasVoted($user))
|
||||
throw new AlreadyVotedException;
|
||||
|
||||
$optionIds = array_map(function($x) { return (int) $x; }, array_unique($optionIds));
|
||||
$validOpts = array_keys($this->getOptions());
|
||||
if(empty($optionIds) || (sizeof($optionIds) > 1 && !$this->isMultipleChoice()))
|
||||
throw new UnexpectedValueException;
|
||||
|
||||
if(sizeof(array_diff($optionIds, $validOpts)) > 0)
|
||||
throw new InvalidOptionException;
|
||||
|
||||
foreach($optionIds as $opt) {
|
||||
DatabaseConnection::i()->getContext()->table("poll_votes")->insert([
|
||||
"user" => $user->getId(),
|
||||
"poll" => $this->getId(),
|
||||
"option" => $opt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function revokeVote(User $user): void
|
||||
{
|
||||
if(!$this->isRevotable())
|
||||
throw new PollLockedException;
|
||||
|
||||
$this->getRecord()->related("poll_votes.poll")
|
||||
->where("user", $user->getId())->delete();
|
||||
}
|
||||
|
||||
function setOwner(User $owner): void
|
||||
{
|
||||
$this->stateChanges("owner", $owner->getId());
|
||||
}
|
||||
|
||||
function setEndDate(int $timestamp): void
|
||||
{
|
||||
if(!is_null($this->getRecord()))
|
||||
throw new PollLockedException;
|
||||
|
||||
$this->stateChanges("until", $timestamp);
|
||||
}
|
||||
|
||||
function setEnded(): void
|
||||
{
|
||||
$this->stateChanges("ended", 1);
|
||||
}
|
||||
|
||||
function setOptions(array $options): void
|
||||
{
|
||||
if(!is_null($this->getRecord()))
|
||||
throw new PollLockedException;
|
||||
|
||||
if(sizeof($options) > ovkGetQuirk("polls.max-opts"))
|
||||
throw new TooMuchOptionsException;
|
||||
|
||||
$this->choicesToPersist = $options;
|
||||
}
|
||||
|
||||
function setRevotability(bool $canReVote): void
|
||||
{
|
||||
if(!is_null($this->getRecord()))
|
||||
throw new PollLockedException;
|
||||
|
||||
$this->stateChanges("can_revote", $canReVote);
|
||||
}
|
||||
|
||||
function setAnonymity(bool $anonymous): void
|
||||
{
|
||||
$this->stateChanges("is_anonymous", $anonymous);
|
||||
}
|
||||
|
||||
function setMultipleChoice(bool $mc): void
|
||||
{
|
||||
$this->stateChanges("allows_multiple", $mc);
|
||||
}
|
||||
|
||||
function importXML(User $owner, string $xml): void
|
||||
{
|
||||
$xml = simplexml_load_string($xml);
|
||||
$this->setOwner($owner);
|
||||
$this->setTitle($xml["title"] ?? "Untitled");
|
||||
$this->setMultipleChoice(($xml["multiple"] ?? "no") == "yes");
|
||||
$this->setAnonymity(($xml["anonymous"] ?? "no") == "yes");
|
||||
$this->setRevotability(($xml["locked"] ?? "no") == "no");
|
||||
if(ctype_digit((string) ($xml["duration"] ?? "")))
|
||||
$this->setEndDate(time() + ((86400 * (int) $xml["duration"])));
|
||||
|
||||
$options = [];
|
||||
foreach($xml->options->option as $opt)
|
||||
$options[] = (string) $opt;
|
||||
|
||||
if(empty($options))
|
||||
throw new UnexpectedValueException;
|
||||
|
||||
$this->setOptions($options);
|
||||
}
|
||||
|
||||
static function import(User $owner, string $xml): Poll
|
||||
{
|
||||
$poll = new Poll;
|
||||
$poll->importXML($owner, $xml);
|
||||
$poll->save();
|
||||
|
||||
return $poll;
|
||||
}
|
||||
|
||||
function save(): void
|
||||
{
|
||||
if(empty($this->choicesToPersist))
|
||||
throw new InvalidStateException;
|
||||
|
||||
parent::save();
|
||||
foreach($this->choicesToPersist as $option) {
|
||||
DatabaseConnection::i()->getContext()->table("poll_options")->insert([
|
||||
"poll" => $this->getId(),
|
||||
"name" => $option,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\Web\Models\Repositories\Clubs;
|
||||
use openvk\Web\Models\Repositories\{Clubs, Users};
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\Notifications\LikeNotification;
|
||||
|
||||
|
@ -55,6 +55,15 @@ class Post extends Postable
|
|||
{
|
||||
return $this->getRecord()->wall;
|
||||
}
|
||||
|
||||
function getWallOwner()
|
||||
{
|
||||
$w = $this->getRecord()->wall;
|
||||
if($w < 0)
|
||||
return (new Clubs)->get(abs($w));
|
||||
|
||||
return (new Users)->get($w);
|
||||
}
|
||||
|
||||
function getRepostCount(): int
|
||||
{
|
||||
|
|
|
@ -535,12 +535,15 @@ class User extends RowModel
|
|||
return sizeof(DatabaseConnection::i()->getContext()->table("messages")->where(["recipient_id" => $this->getId(), "unread" => 1]));
|
||||
}
|
||||
|
||||
function getClubs(int $page = 1, bool $admin = false): \Traversable
|
||||
function getClubs(int $page = 1, bool $admin = false, int $count = OPENVK_DEFAULT_PER_PAGE, bool $offset = false): \Traversable
|
||||
{
|
||||
if(!$offset)
|
||||
$page = ($page - 1) * $count;
|
||||
|
||||
if($admin) {
|
||||
$id = $this->getId();
|
||||
$query = "SELECT `id` FROM `groups` WHERE `owner` = ? UNION SELECT `club` as `id` FROM `group_coadmins` WHERE `user` = ?";
|
||||
$query .= " LIMIT " . OPENVK_DEFAULT_PER_PAGE . " OFFSET " . ($page - 1) * OPENVK_DEFAULT_PER_PAGE;
|
||||
$query .= " LIMIT " . $count . " OFFSET " . $page;
|
||||
|
||||
$sel = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
|
||||
foreach($sel as $target) {
|
||||
|
@ -550,7 +553,7 @@ class User extends RowModel
|
|||
yield $target;
|
||||
}
|
||||
} else {
|
||||
$sel = $this->getRecord()->related("subscriptions.follower")->page($page, OPENVK_DEFAULT_PER_PAGE);
|
||||
$sel = $this->getRecord()->related("subscriptions.follower")->limit($count, $page);
|
||||
foreach($sel->where("model", "openvk\\Web\\Models\\Entities\\Club") as $target) {
|
||||
$target = (new Clubs)->get($target->target);
|
||||
if(!$target) continue;
|
||||
|
@ -926,6 +929,10 @@ class User extends RowModel
|
|||
$pClub = DatabaseConnection::i()->getContext()->table("groups")->where("shortcode", $code)->fetch();
|
||||
if(!is_null($pClub))
|
||||
return false;
|
||||
|
||||
$pAlias = DatabaseConnection::i()->getContext()->table("aliases")->where("shortcode", $code)->fetch();
|
||||
if(!is_null($pAlias))
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->stateChanges("shortcode", $code);
|
||||
|
|
7
Web/Models/Exceptions/AlreadyVotedException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Exceptions;
|
||||
|
||||
final class AlreadyVotedException extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
7
Web/Models/Exceptions/InvalidOptionException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Exceptions;
|
||||
|
||||
final class InvalidOptionException extends \UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
8
Web/Models/Exceptions/PollLockedException.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Exceptions;
|
||||
use Nette\InvalidStateException;
|
||||
|
||||
final class PollLockedException extends InvalidStateException
|
||||
{
|
||||
|
||||
}
|
7
Web/Models/Exceptions/TooMuchOptionsException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Exceptions;
|
||||
|
||||
final class TooMuchOptionsException extends \UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
35
Web/Models/Repositories/Aliases.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
|
||||
use openvk\Web\Models\Entities\Alias;
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\Web\Models\Entities\{Club, User};
|
||||
use openvk\Web\Models\Repositories\{Clubs, Users};
|
||||
|
||||
class Aliases
|
||||
{
|
||||
private $context;
|
||||
private $aliases;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->context = DB::i()->getContext();
|
||||
$this->aliases = $this->context->table("aliases");
|
||||
}
|
||||
|
||||
private function toAlias(?ActiveRow $ar): ?Alias
|
||||
{
|
||||
return is_null($ar) ? NULL : new Alias($ar);
|
||||
}
|
||||
|
||||
function get(int $id): ?Alias
|
||||
{
|
||||
return $this->toAlias($this->aliases->get($id));
|
||||
}
|
||||
|
||||
function getByShortcode(string $shortcode): ?Alias
|
||||
{
|
||||
return $this->toAlias($this->aliases->where("shortcode", $shortcode)->fetch());
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use openvk\Web\Models\Entities\Club;
|
||||
use openvk\Web\Models\Repositories\Aliases;
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
||||
|
@ -22,7 +23,17 @@ class Clubs
|
|||
|
||||
function getByShortURL(string $url): ?Club
|
||||
{
|
||||
return $this->toClub($this->clubs->where("shortcode", $url)->fetch());
|
||||
$shortcode = $this->toClub($this->clubs->where("shortcode", $url)->fetch());
|
||||
|
||||
if ($shortcode)
|
||||
return $shortcode;
|
||||
|
||||
$alias = (new Aliases)->getByShortcode($url);
|
||||
|
||||
if (!$alias) return NULL;
|
||||
if ($alias->getType() !== "club") return NULL;
|
||||
|
||||
return $alias->getClub();
|
||||
}
|
||||
|
||||
function get(int $id): ?Club
|
||||
|
@ -45,6 +56,9 @@ class Clubs
|
|||
|
||||
function getPopularClubs(): \Traversable
|
||||
{
|
||||
// TODO rewrite
|
||||
|
||||
/*
|
||||
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 30;";
|
||||
$entries = DatabaseConnection::i()->getConnection()->query($query);
|
||||
|
||||
|
@ -54,6 +68,7 @@ class Clubs
|
|||
"club" => $this->get($entry["id"]),
|
||||
"subscriptions" => $entry["subscriptions"],
|
||||
];
|
||||
*/
|
||||
}
|
||||
|
||||
use \Nette\SmartObject;
|
||||
|
|
23
Web/Models/Repositories/Polls.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use openvk\Web\Models\Entities\Poll;
|
||||
|
||||
class Polls
|
||||
{
|
||||
private $polls;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->polls = DatabaseConnection::i()->getContext()->table("polls");
|
||||
}
|
||||
|
||||
function get(int $id): ?Poll
|
||||
{
|
||||
$poll = $this->polls->get($id);
|
||||
if(!$poll)
|
||||
return NULL;
|
||||
|
||||
return new Poll($poll);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\Aliases;
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use Chandler\Security\User as ChandlerUser;
|
||||
|
@ -9,11 +10,13 @@ class Users
|
|||
{
|
||||
private $context;
|
||||
private $users;
|
||||
private $aliases;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->context = DatabaseConnection::i()->getContext();
|
||||
$this->users = $this->context->table("profiles");
|
||||
$this->aliases = $this->context->table("aliases");
|
||||
}
|
||||
|
||||
private function toUser(?ActiveRow $ar): ?User
|
||||
|
@ -28,7 +31,17 @@ class Users
|
|||
|
||||
function getByShortURL(string $url): ?User
|
||||
{
|
||||
return $this->toUser($this->users->where("shortcode", $url)->fetch());
|
||||
$shortcode = $this->toUser($this->users->where("shortcode", $url)->fetch());
|
||||
|
||||
if ($shortcode)
|
||||
return $shortcode;
|
||||
|
||||
$alias = (new Aliases)->getByShortcode($url);
|
||||
|
||||
if (!$alias) return NULL;
|
||||
if ($alias->getType() !== "user") return NULL;
|
||||
|
||||
return $alias->getUser();
|
||||
}
|
||||
|
||||
function getByChandlerUser(ChandlerUser $user): ?User
|
||||
|
|
|
@ -64,7 +64,7 @@ final class AboutPresenter extends OpenVKPresenter
|
|||
$this->template->usersStats = (new Users)->getStatistics();
|
||||
$this->template->clubsCount = (new Clubs)->getCount();
|
||||
$this->template->postsCount = (new Posts)->getCount();
|
||||
$this->template->popularClubs = iterator_to_array((new Clubs)->getPopularClubs());
|
||||
$this->template->popularClubs = [];
|
||||
$this->template->admins = iterator_to_array((new Users)->getInstanceAdmins());
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use openvk\Web\Models\Repositories\Applications;
|
|||
final class AppsPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $apps;
|
||||
|
||||
protected $presenterName = "apps";
|
||||
function __construct(Applications $apps)
|
||||
{
|
||||
$this->apps = $apps;
|
||||
|
|
|
@ -6,6 +6,7 @@ use openvk\Web\Models\Repositories\{Comments, Clubs};
|
|||
|
||||
final class CommentPresenter extends OpenVKPresenter
|
||||
{
|
||||
protected $presenterName = "comment";
|
||||
private $models = [
|
||||
"posts" => "openvk\\Web\\Models\\Repositories\\Posts",
|
||||
"photos" => "openvk\\Web\\Models\\Repositories\\Photos",
|
||||
|
|
|
@ -7,6 +7,7 @@ final class GiftsPresenter extends OpenVKPresenter
|
|||
{
|
||||
private $gifts;
|
||||
private $users;
|
||||
protected $presenterName = "gifts";
|
||||
|
||||
function __construct(Gifts $gifts, Users $users)
|
||||
{
|
||||
|
|
|
@ -8,7 +8,8 @@ use Chandler\Security\Authenticator;
|
|||
final class GroupPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $clubs;
|
||||
|
||||
protected $presenterName = "group";
|
||||
|
||||
function __construct(Clubs $clubs)
|
||||
{
|
||||
$this->clubs = $clubs;
|
||||
|
|
35
Web/Presenters/MaintenancePresenter.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace openvk\Web\Presenters;
|
||||
|
||||
final class MaintenancePresenter extends OpenVKPresenter
|
||||
{
|
||||
protected $presenterName = "maintenance";
|
||||
|
||||
function renderSection(string $name): void
|
||||
{
|
||||
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$name])
|
||||
$this->flashFail("err", tr("error"), tr("forbidden"));
|
||||
|
||||
$this->template->name = [
|
||||
"photos" => tr("my_photos"),
|
||||
"videos" => tr("my_videos"),
|
||||
"messenger" => tr("my_messages"),
|
||||
"user" => tr("users"),
|
||||
"group" => tr("my_groups"),
|
||||
"comment" => tr("comments"),
|
||||
"gifts" => tr("gifts"),
|
||||
"apps" => tr("apps"),
|
||||
"notes" => tr("my_notes"),
|
||||
"notification" => tr("my_feedback"),
|
||||
"support" => tr("menu_support"),
|
||||
"topics" => tr("topics")
|
||||
][$name] ?? $name;
|
||||
}
|
||||
|
||||
function renderAll(): void
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -9,11 +9,13 @@ final class MessengerPresenter extends OpenVKPresenter
|
|||
{
|
||||
private $messages;
|
||||
private $signaler;
|
||||
|
||||
protected $presenterName = "messenger";
|
||||
|
||||
function __construct(Messages $messages)
|
||||
{
|
||||
$this->messages = $messages;
|
||||
$this->signaler = SignalManager::i();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -30,7 +32,7 @@ final class MessengerPresenter extends OpenVKPresenter
|
|||
function renderIndex(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
|
||||
if(isset($_GET["sel"]))
|
||||
$this->pass("openvk!Messenger->app", $_GET["sel"]);
|
||||
|
||||
|
@ -93,6 +95,13 @@ final class MessengerPresenter extends OpenVKPresenter
|
|||
}
|
||||
|
||||
$legacy = $this->queryParam("version") < 3;
|
||||
|
||||
$time = intval($this->queryParam("wait"));
|
||||
|
||||
if($time > 60)
|
||||
$time = 60;
|
||||
elseif($time == 0)
|
||||
$time = 25; // default
|
||||
|
||||
$this->signaler->listen(function($event, $eId) use ($id) {
|
||||
exit(json_encode([
|
||||
|
@ -101,7 +110,7 @@ final class MessengerPresenter extends OpenVKPresenter
|
|||
$event->getVKAPISummary($id),
|
||||
],
|
||||
]));
|
||||
}, $id);
|
||||
}, $id, $time);
|
||||
}
|
||||
|
||||
function renderApiGetMessages(int $sel, int $lastMsg): void
|
||||
|
|
|
@ -6,7 +6,8 @@ use openvk\Web\Models\Entities\Note;
|
|||
final class NotesPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $notes;
|
||||
|
||||
protected $presenterName = "notes";
|
||||
|
||||
function __construct(Notes $notes)
|
||||
{
|
||||
$this->notes = $notes;
|
||||
|
|
|
@ -3,6 +3,8 @@ namespace openvk\Web\Presenters;
|
|||
|
||||
final class NotificationPresenter extends OpenVKPresenter
|
||||
{
|
||||
protected $presenterName = "notification";
|
||||
|
||||
function renderFeed(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
|
|
@ -17,7 +17,8 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
protected $deactivationTolerant = false;
|
||||
protected $errorTemplate = "@error";
|
||||
protected $user = NULL;
|
||||
|
||||
protected $presenterName;
|
||||
|
||||
private function calculateQueryString(array $data): string
|
||||
{
|
||||
$rawUrl = "tcp+stratum://fakeurl.net$_SERVER[REQUEST_URI]"; #HTTP_HOST can be tainted
|
||||
|
@ -196,12 +197,13 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
function onStartup(): void
|
||||
{
|
||||
$user = Authenticator::i()->getUser();
|
||||
|
||||
|
||||
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
|
||||
$this->template->isTimezoned = Session::i()->get("_timezoneOffset");
|
||||
|
||||
|
||||
$userValidated = 0;
|
||||
$cacheTime = OPENVK_ROOT_CONF["openvk"]["preferences"]["nginxCacheTime"] ?? 0;
|
||||
|
||||
if(!is_null($user)) {
|
||||
$this->user = (object) [];
|
||||
$this->user->raw = $user;
|
||||
|
@ -226,7 +228,7 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
if($this->user->identity->isBanned() && !$this->banTolerant) {
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [
|
||||
|
@ -247,23 +249,33 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
$userValidated = 1;
|
||||
$cacheTime = 0; # Force no cache
|
||||
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
|
||||
$this->user->identity->setOnline(time());
|
||||
$this->user->identity->save();
|
||||
}
|
||||
|
||||
|
||||
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
|
||||
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0))
|
||||
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
|
||||
}
|
||||
|
||||
|
||||
header("X-OpenVK-User-Validated: $userValidated");
|
||||
header("X-Accel-Expires: $cacheTime");
|
||||
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
|
||||
|
||||
|
||||
if (!OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"]["all"]) {
|
||||
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["maintenanceMode"][$this->presenterName]) {
|
||||
$this->pass("openvk!Maintenance->section", $this->presenterName);
|
||||
}
|
||||
} else {
|
||||
if ($this->presenterName != "maintenance") {
|
||||
$this->redirect("/maintenances/");
|
||||
}
|
||||
}
|
||||
|
||||
parent::onStartup();
|
||||
}
|
||||
|
||||
|
@ -272,10 +284,14 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
parent::onBeforeRender();
|
||||
|
||||
$whichbrowser = new WhichBrowser\Parser(getallheaders());
|
||||
$featurephonetheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultFeaturePhoneTheme"];
|
||||
$mobiletheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultMobileTheme"];
|
||||
if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == NULL)
|
||||
|
||||
if($featurephonetheme && $this->isOldThing($whichbrowser) && Session::i()->get("_tempTheme") == NULL) {
|
||||
$this->setSessionTheme($featurephonetheme);
|
||||
} elseif($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == NULL)
|
||||
$this->setSessionTheme($mobiletheme);
|
||||
|
||||
|
||||
$theme = NULL;
|
||||
if(Session::i()->get("_tempTheme")) {
|
||||
$theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
|
||||
|
@ -306,4 +322,33 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
header("Content-Length: $size");
|
||||
exit($payload);
|
||||
}
|
||||
|
||||
protected function isOldThing($whichbrowser) {
|
||||
if($whichbrowser->isOs('Series60') ||
|
||||
$whichbrowser->isOs('Series40') ||
|
||||
$whichbrowser->isOs('Series80') ||
|
||||
$whichbrowser->isOs('Windows CE') ||
|
||||
$whichbrowser->isOs('Windows Mobile') ||
|
||||
$whichbrowser->isOs('Nokia Asha Platform') ||
|
||||
$whichbrowser->isOs('UIQ') ||
|
||||
$whichbrowser->isEngine('NetFront') || // PSP and other japanese portable systems
|
||||
$whichbrowser->isOs('Android') ||
|
||||
$whichbrowser->isOs('iOS') ||
|
||||
$whichbrowser->isBrowser('Internet Explorer', '<=', '8')) {
|
||||
// yeah, it's old, but ios and android are?
|
||||
if($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '<=', '9'))
|
||||
return true;
|
||||
elseif($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '>', '9'))
|
||||
return false;
|
||||
|
||||
if($whichbrowser->isOs('Android') && $whichbrowser->isOs('Android', '<=', '5'))
|
||||
return true;
|
||||
elseif($whichbrowser->isOs('Android') && $whichbrowser->isOs('Android', '>', '5'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
private $users;
|
||||
private $photos;
|
||||
private $albums;
|
||||
|
||||
protected $presenterName = "photos";
|
||||
|
||||
function __construct(Photos $photos, Albums $albums, Users $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
|
|
75
Web/Presenters/PollPresenter.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\Poll;
|
||||
use openvk\Web\Models\Repositories\Polls;
|
||||
|
||||
final class PollPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $polls;
|
||||
|
||||
function __construct(Polls $polls)
|
||||
{
|
||||
$this->polls = $polls;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function renderView(int $id): void
|
||||
{
|
||||
$poll = $this->polls->get($id);
|
||||
if(!$poll)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->id = $poll->getId();
|
||||
$this->template->title = $poll->getTitle();
|
||||
$this->template->isAnon = $poll->isAnonymous();
|
||||
$this->template->multiple = $poll->isMultipleChoice();
|
||||
$this->template->unlocked = $poll->isRevotable();
|
||||
$this->template->until = $poll->endsAt();
|
||||
$this->template->votes = $poll->getVoterCount();
|
||||
$this->template->meta = $poll->getMetaDescription();
|
||||
$this->template->ended = $ended = $poll->hasEnded();
|
||||
if((is_null($this->user) || $poll->canVote($this->user->identity)) && !$ended) {
|
||||
$this->template->options = $poll->getOptions();
|
||||
|
||||
$this->template->_template = "Poll/Poll.xml";
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_null($this->user)) {
|
||||
$this->template->voted = false;
|
||||
$this->template->results = $poll->getResults();
|
||||
} else {
|
||||
$this->template->voted = $poll->hasVoted($this->user->identity);
|
||||
$this->template->results = $poll->getResults($this->user->identity);
|
||||
}
|
||||
|
||||
$this->template->_template = "Poll/PollResults.xml";
|
||||
}
|
||||
|
||||
function renderVoters(int $pollId): void
|
||||
{
|
||||
$poll = $this->polls->get($pollId);
|
||||
if(!$poll)
|
||||
$this->notFound();
|
||||
|
||||
if($poll->isAnonymous())
|
||||
$this->flashFail("err", tr("forbidden"), tr("poll_err_anonymous"));
|
||||
|
||||
$options = $poll->getOptions();
|
||||
$option = (int) base_convert($this->queryParam("option"), 32, 10);
|
||||
if(!in_array($option, array_keys($options)))
|
||||
$this->notFound();
|
||||
|
||||
$page = (int) ($this->queryParam("p") ?? 1);
|
||||
$voters = $poll->getVoters($option, $page);
|
||||
|
||||
$this->template->pollId = $pollId;
|
||||
$this->template->options = $options;
|
||||
$this->template->option = [$option, $options[$option]];
|
||||
$this->template->tabs = $options;
|
||||
$this->template->iterator = $voters;
|
||||
$this->template->count = $poll->getVoterCount($option);
|
||||
$this->template->page = $page;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ final class SupportPresenter extends OpenVKPresenter
|
|||
{
|
||||
protected $banTolerant = true;
|
||||
protected $deactivationTolerant = true;
|
||||
protected $presenterName = "support";
|
||||
|
||||
private $tickets;
|
||||
private $comments;
|
||||
|
@ -155,11 +156,12 @@ final class SupportPresenter extends OpenVKPresenter
|
|||
$this->notFound();
|
||||
} else {
|
||||
if($ticket->getUserId() !== $this->user->id && $this->hasPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0))
|
||||
$this->redirect("/support/tickets");
|
||||
$_redirect = "/support/tickets";
|
||||
else
|
||||
$this->redirect("/support");
|
||||
$_redirect = "/support?act=list";
|
||||
|
||||
$ticket->delete();
|
||||
$this->redirect($_redirect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ final class TopicsPresenter extends OpenVKPresenter
|
|||
{
|
||||
private $topics;
|
||||
private $clubs;
|
||||
|
||||
protected $presenterName = "topics";
|
||||
|
||||
function __construct(Topics $topics, Clubs $clubs)
|
||||
{
|
||||
$this->topics = $topics;
|
||||
|
|
|
@ -15,12 +15,13 @@ use Nette\Database\UniqueConstraintViolationException;
|
|||
final class UserPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $users;
|
||||
|
||||
public $deactivationTolerant = false;
|
||||
protected $presenterName = "user";
|
||||
|
||||
function __construct(Users $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ final class VideosPresenter extends OpenVKPresenter
|
|||
{
|
||||
private $videos;
|
||||
private $users;
|
||||
|
||||
protected $presenterName = "videos";
|
||||
|
||||
function __construct(Videos $videos, Users $users)
|
||||
{
|
||||
$this->videos = $videos;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Post, Photo, Video, Club, User};
|
||||
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
|
||||
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
|
||||
use openvk\Web\Models\Entities\Notifications\{RepostNotification, WallPostNotification};
|
||||
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums};
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
@ -44,9 +45,6 @@ final class WallPresenter extends OpenVKPresenter
|
|||
|
||||
function renderWall(int $user, bool $embedded = false): void
|
||||
{
|
||||
if(false)
|
||||
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
|
||||
|
||||
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
|
||||
if(is_null($this->user)) {
|
||||
$canPost = false;
|
||||
|
@ -65,7 +63,10 @@ final class WallPresenter extends OpenVKPresenter
|
|||
}
|
||||
|
||||
if ($embedded == true) $this->template->_template = "components/wall.xml";
|
||||
$this->template->oObj = $owner;
|
||||
$this->template->oObj = $owner;
|
||||
if($user < 0)
|
||||
$this->template->club = $owner;
|
||||
|
||||
$this->template->owner = $user;
|
||||
$this->template->canPost = $canPost;
|
||||
$this->template->count = $this->posts->getPostCountOnUserWall($user);
|
||||
|
@ -88,9 +89,6 @@ final class WallPresenter extends OpenVKPresenter
|
|||
|
||||
function renderRSS(int $user): void
|
||||
{
|
||||
if(false)
|
||||
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
|
||||
|
||||
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
|
||||
if(is_null($this->user)) {
|
||||
$canPost = false;
|
||||
|
@ -259,16 +257,26 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album, $anon);
|
||||
}
|
||||
|
||||
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK)
|
||||
$video = Video::fastMake($this->user->id, $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"));
|
||||
}
|
||||
|
||||
if(empty($this->postParam("text")) && !$photo && !$video)
|
||||
try {
|
||||
$poll = NULL;
|
||||
$xml = $this->postParam("poll");
|
||||
if (!is_null($xml) && $xml != "none")
|
||||
$poll = Poll::import($this->user->identity, $xml);
|
||||
} catch(TooMuchOptionsException $e) {
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), tr("poll_err_to_much_options"));
|
||||
} catch(\UnexpectedValueException $e) {
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid");
|
||||
}
|
||||
|
||||
if(empty($this->postParam("text")) && !$photo && !$video && !$poll)
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
|
||||
|
||||
try {
|
||||
|
@ -291,6 +299,9 @@ final class WallPresenter extends OpenVKPresenter
|
|||
if(!is_null($video))
|
||||
$post->attach($video);
|
||||
|
||||
if(!is_null($poll))
|
||||
$post->attach($poll);
|
||||
|
||||
if($wall > 0 && $wall !== $this->user->identity->getId())
|
||||
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
|
||||
|
||||
|
|
|
@ -17,62 +17,19 @@
|
|||
{script "js/l10n.js"}
|
||||
{script "js/openvk.cls.js"}
|
||||
|
||||
{css "js/node_modules/tippy.js/dist/backdrop.css"}
|
||||
{css "js/node_modules/tippy.js/dist/border.css"}
|
||||
{css "js/node_modules/tippy.js/dist/svg-arrow.css"}
|
||||
{css "js/node_modules/tippy.js/themes/light.css"}
|
||||
{script "js/node_modules/@popperjs/core/dist/umd/popper.min.js"}
|
||||
{script "js/node_modules/tippy.js/dist/tippy-bundle.umd.min.js"}
|
||||
{script "js/node_modules/handlebars/dist/handlebars.min.js"}
|
||||
|
||||
{if $isTimezoned == NULL}
|
||||
{script "js/timezone.js"}
|
||||
{/if}
|
||||
|
||||
{ifset $thisUser}
|
||||
{if $thisUser->getNsfwTolerance() < 2}
|
||||
{css "css/nsfw-posts.css"}
|
||||
{/if}
|
||||
|
||||
{if $theme !== NULL}
|
||||
{if $theme->inheritDefault()}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/stylesheet/styles.css" />
|
||||
|
||||
{if $isXmas}
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/resource/xmas.css" />
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{if $thisUser->getStyleAvatar() == 1}
|
||||
{css "css/avatar.1.css"}
|
||||
{/if}
|
||||
|
||||
{if $thisUser->getStyleAvatar() == 2}
|
||||
{css "css/avatar.2.css"}
|
||||
{/if}
|
||||
|
||||
{if $thisUser->hasMicroblogEnabled() == 1}
|
||||
{css "css/microblog.css"}
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/nsfw-posts.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/ifset}
|
||||
{include "_includeCSS.xml"}
|
||||
|
||||
{ifset headIncludes}
|
||||
{include headIncludes}
|
||||
|
@ -325,6 +282,8 @@
|
|||
{script "js/scroll.js"}
|
||||
{script "js/al_wall.js"}
|
||||
{script "js/al_api.js"}
|
||||
{script "js/al_mentions.js"}
|
||||
{script "js/al_polls.js"}
|
||||
|
||||
{ifset $thisUser}
|
||||
{script "js/al_notifs.js"}
|
||||
|
|
20
Web/Presenters/templates/Maintenance/All.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_global_maintenance}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
{_global_maintenance}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="container_gray">
|
||||
<center style="background: white;border: #DEDEDE solid 1px;">
|
||||
<img src="/assets/packages/static/openvk/img/oof.apng" style="width: 20%;" />
|
||||
<span style="color: #707070;margin: 10px 0;display: block;">
|
||||
{_undergoing_global_maintenance}
|
||||
</span>
|
||||
</center>
|
||||
</div>
|
||||
{/block}
|
20
Web/Presenters/templates/Maintenance/Section.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_section_maintenance}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
{_section_maintenance}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="container_gray">
|
||||
<center style="background: white;border: #DEDEDE solid 1px;">
|
||||
<img src="/assets/packages/static/openvk/img/oof.apng" style="width: 20%;" />
|
||||
<span style="color: #707070;margin: 10px 0;display: block;">
|
||||
{tr("undergoing_section_maintenance", $name)|noescape}
|
||||
</span>
|
||||
</center>
|
||||
</div>
|
||||
{/block}
|
44
Web/Presenters/templates/Poll/Poll.xml
Normal file
|
@ -0,0 +1,44 @@
|
|||
{if !isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'}
|
||||
<link rel="shortcut icon" href="/assets/packages/static/openvk/img/icon.ico" />
|
||||
<meta n:ifset="$csrfToken" name="csrf" value="{$csrfToken}" />
|
||||
<script src="/language/{getLanguage()}.js" crossorigin="anonymous"></script>
|
||||
{script "js/node_modules/jquery/dist/jquery.min.js"}
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
|
||||
{script "js/messagebox.js"}
|
||||
{script "js/l10n.js"}
|
||||
{script "js/al_api.js"}
|
||||
{script "js/al_polls.js"}
|
||||
{include "../_includeCSS.xml"}
|
||||
|
||||
<style>body { margin: 8px; }</style>
|
||||
{/if}
|
||||
|
||||
<div class="poll">
|
||||
<h4>{$title}</h4>
|
||||
<form onsubmit="pollFormSubmit(event, this)" data-multi="{$multiple ? '1' : '0'}" data-pid="{$id}">
|
||||
<div class="poll-options">
|
||||
<div n:foreach="$options as $oid => $option" class="poll-option">
|
||||
<label>
|
||||
{if $multiple}
|
||||
<input n:attr="disabled => is_null($thisUser)" type="checkbox" name="option{$oid}" onclick="pollCheckBoxPressed(this)" />
|
||||
{else}
|
||||
<input n:attr="disabled => is_null($thisUser)" type="radio" value="{$oid}" name="vote" onclick="pollRadioPressed(this)" />
|
||||
{/if}
|
||||
|
||||
{$option}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $multiple}
|
||||
<br/>
|
||||
<input type="submit" class="button" value="{_cast_vote}" disabled="disabled" />
|
||||
{/if}
|
||||
</form>
|
||||
|
||||
<div class="poll-meta">
|
||||
{tr("poll_voter_count", $votes)|noescape}<br/>
|
||||
<span class="nobold">{$meta}</span>
|
||||
</div>
|
||||
</div>
|
56
Web/Presenters/templates/Poll/PollResults.xml
Normal file
|
@ -0,0 +1,56 @@
|
|||
{if !isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'}
|
||||
<link rel="shortcut icon" href="/assets/packages/static/openvk/img/icon.ico" />
|
||||
<meta n:ifset="$csrfToken" name="csrf" value="{$csrfToken}" />
|
||||
<script src="/language/{getLanguage()}.js" crossorigin="anonymous"></script>
|
||||
{script "js/node_modules/jquery/dist/jquery.min.js"}
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
|
||||
{script "js/messagebox.js"}
|
||||
{script "js/l10n.js"}
|
||||
{script "js/al_api.js"}
|
||||
{script "js/al_polls.js"}
|
||||
{include "../_includeCSS.xml"}
|
||||
|
||||
<style>body { margin: 8px; } .poll { border: 1px solid #e3e3e3; }</style>
|
||||
{/if}
|
||||
|
||||
<div class="poll" data-id="{$id}">
|
||||
<a n:if="$unlocked && $voted" href="javascript:pollRetractVote({$id})" class="poll-retract-vote">{_retract_vote}</a>
|
||||
<h4>{$title}</h4>
|
||||
<div class="poll-results">
|
||||
<div n:foreach="$results->options as $option" class="poll-result">
|
||||
{if $isAnon}
|
||||
<a href="javascript:false">
|
||||
{if $option->voted}
|
||||
<b>{$option->name}</b>
|
||||
{else}
|
||||
{$option->name}
|
||||
{/if}
|
||||
</a>
|
||||
{else}
|
||||
<a href="/poll{$id}/voters?option={base_convert($option->id, 10, 32)}">
|
||||
{if $option->voted}
|
||||
<b>{$option->name}</b>
|
||||
{else}
|
||||
{$option->name}
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<div class="poll-result-barspace">
|
||||
<div class="poll-result-bar">
|
||||
<span class="poll-result-count">{$option->votes}</span>
|
||||
<div class="poll-result-bar-sub" style="width: {$option->pct}%"> </div>
|
||||
</div>
|
||||
<div class="poll-result-pct">
|
||||
<strong>{$option->pct}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="poll-meta">
|
||||
{tr("poll_voter_count", $votes)|noescape}<br/>
|
||||
<span class="nobold">{$meta}</span>
|
||||
</div>
|
||||
</div>
|
40
Web/Presenters/templates/Poll/Voters.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
{extends "../@listView.xml"}
|
||||
|
||||
{block title}
|
||||
{_poll_voters_list}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
{_poll_voters_list} »
|
||||
{$option[1]}
|
||||
{/block}
|
||||
|
||||
{block tabs}
|
||||
<div n:foreach="$options as $optionId => $optionName" class="tab" id="{$optionId == $option[0] ? 'activetabs' : 'ki'}">
|
||||
<a id="{$optionId == $option[0] ? 'act_tab_a' : ''}" href="/poll{$pollId}/voters?option={base_convert($optionId, 10, 32)}">{$optionName}</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
{$x->getURL()}
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
{$x->getCanonicalName()}
|
||||
<img n:if="$x->isVerified()"
|
||||
class="name-checkmark"
|
||||
src="/assets/packages/static/openvk/img/checkmark.png"
|
||||
/>
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
{/block}
|
||||
|
||||
{block actions}
|
||||
{/block}
|
|
@ -53,7 +53,7 @@
|
|||
<div n:attr="id => ($act === 'online' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'online' ? 'act_tab_a' : 'ki')" href="?act=online">{_online}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
|
||||
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_req}</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -79,9 +79,9 @@
|
|||
{/block}
|
||||
|
||||
{block actions}
|
||||
<a href="{$x->getURL()}" class="profile_link">{_check_community}</a>
|
||||
{if $x->canBeModifiedBy($thisUser ?? NULL)}
|
||||
{var $clubPinned = $thisUser->isClubPinned($x)}
|
||||
<a href="{$x->getURL()}" class="profile_link">{_check_community}</a>
|
||||
<a href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" class="profile_link" n:if="$clubPinned || $thisUser->getPinnedClubCount() <= 10" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
|
||||
{if $clubPinned}
|
||||
{_remove_from_left_menu}
|
||||
|
@ -89,21 +89,21 @@
|
|||
{_add_to_left_menu}
|
||||
{/if}
|
||||
</a>
|
||||
{if $x->getSubscriptionStatus($thisUser) == false}
|
||||
<form class="profile_link_form" action="/setSub/club" method="post">
|
||||
<input type="hidden" name="act" value="add" />
|
||||
<input type="hidden" name="id" value="{$x->getId()}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" class="profile_link" value="{_join_community}" />
|
||||
</form>
|
||||
{else}
|
||||
<form class="profile_link_form" action="/setSub/club" method="post">
|
||||
<input type="hidden" name="act" value="rem" />
|
||||
<input type="hidden" name="id" value="{$x->getId()}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" class="profile_link" value="{_leave_community}" />
|
||||
</form>
|
||||
{/if}
|
||||
{/if}
|
||||
{if $x->getSubscriptionStatus($thisUser) == false}
|
||||
<form class="profile_link_form" action="/setSub/club" method="post">
|
||||
<input type="hidden" name="act" value="add" />
|
||||
<input type="hidden" name="id" value="{$x->getId()}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" class="profile_link" value="{_join_community}" />
|
||||
</form>
|
||||
{else}
|
||||
<form class="profile_link_form" action="/setSub/club" method="post">
|
||||
<input type="hidden" name="act" value="rem" />
|
||||
<input type="hidden" name="id" value="{$x->getId()}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" class="profile_link" value="{_leave_community}" />
|
||||
</form>
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
|
||||
<div n:class="postFeedWrapper, $thisUser->hasMicroblogEnabled() ? postFeedWrapperMicroblog">
|
||||
{include "../components/textArea.xml", route => "/wall" . $thisUser->getId() . "/makePost"}
|
||||
{include "../components/textArea.xml", route => "/wall" . $thisUser->getId() . "/makePost", graffiti => true, polls => true}
|
||||
</div>
|
||||
|
||||
{foreach $posts as $post}
|
||||
|
|
52
Web/Presenters/templates/_includeCSS.xml
Normal file
|
@ -0,0 +1,52 @@
|
|||
{ifset $thisUser}
|
||||
{if $thisUser->getNsfwTolerance() < 2}
|
||||
{css "css/nsfw-posts.css"}
|
||||
{/if}
|
||||
|
||||
{if $theme !== NULL}
|
||||
{if $theme->inheritDefault()}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/stylesheet/styles.css" />
|
||||
|
||||
{if $isXmas}
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/resource/xmas.css" />
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{if $thisUser->getStyleAvatar() == 1}
|
||||
{css "css/avatar.1.css"}
|
||||
{/if}
|
||||
|
||||
{if $thisUser->getStyleAvatar() == 2}
|
||||
{css "css/avatar.2.css"}
|
||||
{/if}
|
||||
|
||||
{if $thisUser->hasMicroblogEnabled() == 1}
|
||||
{css "css/microblog.css"}
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/nsfw-posts.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/ifset}
|
|
@ -11,6 +11,8 @@
|
|||
{/if}
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
|
||||
<video class="media" src="{$attachment->getURL()}" controls="controls"></video>
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
|
||||
{presenter "openvk!Poll->view", $attachment->getId()}
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
|
||||
{php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1}
|
||||
{if $GLOBALS["_nesAttGloCou"] > 2}
|
||||
|
|
|
@ -24,17 +24,15 @@
|
|||
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">
|
||||
{$post->isDeactivationMessage() ? ($author->isFemale() ? tr($deac . "_f") : tr($deac . "_m"))}
|
||||
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
|
||||
{var $wallId = $post->getTargetWall()}
|
||||
{var $wallURL = $wallId > -1 ? "/id$wallId" : "/club" . abs($wallId)}
|
||||
на
|
||||
<a href="{$wallURL}">
|
||||
{var $wallOwner = $post->getWallOwner()}
|
||||
<a href="{$wallOwner->getURL()}" class="mention" data-mention-ref="{$post->getTargetWall()}">
|
||||
<b>
|
||||
{if isset($thisUser) && $thisUser->getId() === $wallId}
|
||||
вашей
|
||||
{/if}
|
||||
стене
|
||||
{if $wallId < 0}
|
||||
группы
|
||||
{if isset($thisUser) && $thisUser->getId() === $post->getTargetWall()}
|
||||
{_post_on_your_wall}
|
||||
{elseif $wallOwner instanceof \openvk\Web\Models\Entities\Club}
|
||||
{tr("post_on_group_wall", ovk_proc_strtr($wallOwner->getName(), 52))}
|
||||
{else}
|
||||
{tr("post_on_user_wall", $wallOwner->getMorphedName("genitive", false))}
|
||||
{/if}
|
||||
</b>
|
||||
</a>
|
||||
|
@ -77,7 +75,7 @@
|
|||
{var $actualAuthor = $post->getOwner(false)}
|
||||
<span>
|
||||
{_author}:
|
||||
<a href="{$actualAuthor->getURL()}">
|
||||
<a href="{$actualAuthor->getURL()}" class="mention" data-mention-ref="{$actualAuthor->getId()}">
|
||||
{$actualAuthor->getCanonicalName()}
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
@ -20,17 +20,15 @@
|
|||
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">
|
||||
{$post->isDeactivationMessage() ? ($author->isFemale() ? tr($deac . "_f") : tr($deac . "_m")) : ($post->isPostedOnBehalfOfGroup() ? tr("post_writes_g") : ($author->isFemale() ? tr("post_writes_f") : tr("post_writes_m")))}
|
||||
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
|
||||
{var $wallId = $post->getTargetWall()}
|
||||
{var $wallURL = $wallId > -1 ? "/id$wallId" : "/club" . abs($wallId)}
|
||||
на
|
||||
<a href="{$wallURL}">
|
||||
{var $wallOwner = $post->getWallOwner()}
|
||||
<a href="{$wallOwner->getURL()}" class="mention" data-mention-ref="{$post->getTargetWall()}">
|
||||
<b>
|
||||
{if isset($thisUser) && $thisUser->getId() === $wallId}
|
||||
вашей
|
||||
{/if}
|
||||
стене
|
||||
{if $wallId < 0}
|
||||
группы
|
||||
{if isset($thisUser) && $thisUser->getId() === $post->getTargetWall()}
|
||||
{_post_on_your_wall}
|
||||
{elseif $wallOwner instanceof \openvk\Web\Models\Entities\Club}
|
||||
{tr("post_on_group_wall", ovk_proc_strtr($wallOwner->getName(), 52))}
|
||||
{else}
|
||||
{tr("post_on_user_wall", $wallOwner->getMorphedName("genitive", false))}
|
||||
{/if}
|
||||
</b>
|
||||
</a>
|
||||
|
@ -58,7 +56,7 @@
|
|||
{var $actualAuthor = $post->getOwner(false)}
|
||||
<span>
|
||||
{_author}:
|
||||
<a href="{$actualAuthor->getURL()}">
|
||||
<a href="{$actualAuthor->getURL()}" class="mention" data-mention-ref="{$actualAuthor->getId()}">
|
||||
{$actualAuthor->getCanonicalName()}
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
<div class="post-upload">
|
||||
{_attachment}: <span>(unknown)</span>
|
||||
</div>
|
||||
<div class="post-has-poll">
|
||||
{_poll}
|
||||
</div>
|
||||
<div n:if="$postOpts ?? true" class="post-opts">
|
||||
{var $anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']}
|
||||
|
||||
{if !is_null($thisUser) && !is_null($club ?? NULL) && $owner < 0}
|
||||
{if $club->canBeModifiedBy($thisUser)}
|
||||
<script>
|
||||
|
@ -43,6 +45,7 @@
|
|||
<input type="checkbox" name="nsfw" /> {_contains_nsfw}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div n:if="!($postOpts ?? true) && !is_null($thisUser) && !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)" class="post-opts">
|
||||
<label>
|
||||
<input type="checkbox" name="as_group" /> {_comment_as_group}
|
||||
|
@ -50,6 +53,7 @@
|
|||
</div>
|
||||
<input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display:none;" />
|
||||
<input type="file" class="postFileSel" id="postFileVid" name="_vid_attachment" accept="video/*" style="display:none;" />
|
||||
<input type="hidden" name="poll" value="none" />
|
||||
<input type="hidden" name="type" value="1" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<br/>
|
||||
|
@ -75,6 +79,10 @@
|
|||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
|
||||
{_graffiti}
|
||||
</a>
|
||||
<a n:if="$polls ?? false" href="javascript:initPoll({$textAreaId})">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/office-chart-bar-stacked.png" />
|
||||
{_poll}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<div n:if="$canPost" class="content_subtitle">
|
||||
{include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true}
|
||||
{include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true, polls => true}
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
|
|
@ -21,7 +21,8 @@ class DateTime
|
|||
$then = date_create("@" . $this->timestamp);
|
||||
$now = date_create();
|
||||
$diff = date_diff($now, $then);
|
||||
if($diff->invert === 0) return __OPENVK_ERROR_CLOCK_IN_FUTURE;
|
||||
if($diff->invert === 0)
|
||||
return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R %p", $this->timestamp);
|
||||
|
||||
if($this->timestamp >= strtotime("midnight")) { # Today
|
||||
if($diff->h >= 1)
|
||||
|
@ -52,13 +53,10 @@ class DateTime
|
|||
switch($type) {
|
||||
case static::RELATIVE_FORMAT_NORMAL:
|
||||
return mb_convert_case($this->zmdate(), MB_CASE_TITLE_SIMPLE);
|
||||
break;
|
||||
case static::RELATIVE_FORMAT_LOWER:
|
||||
return $this->zmdate();
|
||||
break;
|
||||
case static::RELATIVE_FORMAT_SHORT:
|
||||
return "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,11 @@ services:
|
|||
- openvk\Web\Presenters\AppsPresenter
|
||||
- openvk\Web\Presenters\ThemepacksPresenter
|
||||
- openvk\Web\Presenters\VKAPIPresenter
|
||||
- openvk\Web\Presenters\PollPresenter
|
||||
- openvk\Web\Presenters\BannedLinkPresenter
|
||||
- openvk\Web\Models\Repositories\Users
|
||||
- openvk\Web\Models\Repositories\Posts
|
||||
- openvk\Web\Models\Repositories\Polls
|
||||
- openvk\Web\Models\Repositories\Photos
|
||||
- openvk\Web\Models\Repositories\Albums
|
||||
- openvk\Web\Models\Repositories\Clubs
|
||||
|
@ -43,4 +45,6 @@ services:
|
|||
- openvk\Web\Models\Repositories\Topics
|
||||
- openvk\Web\Models\Repositories\Applications
|
||||
- openvk\Web\Models\Repositories\ContentSearchRepository
|
||||
- openvk\Web\Models\Repositories\Aliases
|
||||
- openvk\Web\Models\Repositories\BannedLinks
|
||||
- openvk\Web\Presenters\MaintenancePresenter
|
||||
|
|
|
@ -273,6 +273,10 @@ routes:
|
|||
handler: "Apps->edit"
|
||||
- url: "/apps/uninstall"
|
||||
handler: "Apps->unInstall"
|
||||
- url: "/poll{num}"
|
||||
handler: "Poll->view"
|
||||
- url: "/poll{num}/voters"
|
||||
handler: "Poll->voters"
|
||||
- url: "/admin"
|
||||
handler: "Admin->index"
|
||||
- url: "/admin/users"
|
||||
|
@ -331,3 +335,7 @@ routes:
|
|||
handler: "UnknownTextRouteStrategy->delegate"
|
||||
placeholders:
|
||||
shortCode: "[a-z][a-z0-9\\@\\.\\_]{0,30}[a-z0-9]"
|
||||
- url: "/maintenance/{text}"
|
||||
handler: "Maintenance->section"
|
||||
- url: "/maintenances/"
|
||||
handler: "Maintenance->all"
|
||||
|
|
|
@ -448,6 +448,15 @@ table {
|
|||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
filter: opacity(0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button[disabled]:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button-loading {
|
||||
display: inline-block;
|
||||
background-image: url('/assets/packages/static/openvk/img/loading_mini.gif');
|
||||
|
@ -801,6 +810,8 @@ table.User {
|
|||
padding: 0 10px;
|
||||
margin-left: -10px;
|
||||
width: 607px;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tabs.stupid-fix {
|
||||
|
@ -1338,14 +1349,14 @@ body.scrolled .toTop:hover {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.post-upload {
|
||||
.post-upload, .post-has-poll {
|
||||
margin-top: 11px;
|
||||
margin-left: 3px;
|
||||
color: #3c3c3c;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post-upload::before {
|
||||
.post-upload::before, .post-has-poll::before {
|
||||
content: " ";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
@ -2051,6 +2062,88 @@ table td[width="120"] {
|
|||
max-height: 250px;
|
||||
}
|
||||
|
||||
.poll {
|
||||
padding: 8px;
|
||||
transition: .1s filter ease-in;
|
||||
border: 1px solid #e3e3e3;
|
||||
}
|
||||
|
||||
.poll.loading {
|
||||
filter: opacity(0.5);
|
||||
cursor: progress;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.poll.loading * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.poll-embed {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.poll h4 {
|
||||
padding-bottom: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.poll-meta .nobold {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
.poll-result-barspace {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.poll-result {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.poll-result a {
|
||||
color: unset;
|
||||
}
|
||||
|
||||
.poll-result-bar {
|
||||
position: relative;
|
||||
margin: 5px 0;
|
||||
background-color: #f7f7f7;
|
||||
height: 13pt;
|
||||
flex: 14;
|
||||
}
|
||||
|
||||
span.poll-result-count {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #7d96af;
|
||||
}
|
||||
|
||||
.poll-result-bar-sub {
|
||||
height: 100%;
|
||||
background-color: #d9e1ea;
|
||||
}
|
||||
|
||||
.poll-result-pct {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
a.poll-retract-vote {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~="vk"] {
|
||||
user-select: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid #DCE1E6;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
@keyframes appearing {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
42
Web/static/js/al_mentions.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
var tooltipTemplate = Handlebars.compile(`
|
||||
<table>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
<img src="{{ava}}" width="54" />
|
||||
</td>
|
||||
<td width="1"></td>
|
||||
<td width="150" valign="top">
|
||||
<span>
|
||||
<a href="{{url}}"><b>{{name}}</b></a>
|
||||
{{#if verif}}
|
||||
<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png" />
|
||||
{{/if}}
|
||||
</span><br/>
|
||||
<span style="color: #444;">{{online}}</span><br/>
|
||||
<span style="color: #000;">{{about}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`);
|
||||
|
||||
tippy(".mention", {
|
||||
theme: "light vk",
|
||||
content: "⌛",
|
||||
allowHTML: true,
|
||||
interactive: true,
|
||||
interactiveDebounce: 500,
|
||||
|
||||
onCreate: async function(that) {
|
||||
that._resolvedMention = null;
|
||||
},
|
||||
|
||||
onShow: async function(that) {
|
||||
if(!that._resolvedMention) {
|
||||
let id = Number(that.reference.dataset.mentionRef);
|
||||
that._resolvedMention = await API.Mentions.resolve(id);
|
||||
}
|
||||
|
||||
let res = that._resolvedMention;
|
||||
that.setContent(tooltipTemplate(res));
|
||||
}
|
||||
});
|
154
Web/static/js/al_polls.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
function escapeXML(text) {
|
||||
return $("<span/>").text(text).html();
|
||||
}
|
||||
|
||||
async function pollRetractVote(id) {
|
||||
let poll = $(`.poll[data-id=${id}]`);
|
||||
|
||||
poll.addClass("loading");
|
||||
try {
|
||||
let html = (await API.Polls.unvote(poll.data("id"))).html;
|
||||
poll.prop("outerHTML", html);
|
||||
} catch(e) {
|
||||
MessageBox(tr("error"), "Sorry: " + e.message, ["OK"], [Function.noop]);
|
||||
} finally {
|
||||
poll.removeClass("loading");
|
||||
}
|
||||
}
|
||||
|
||||
async function pollFormSubmit(e, form) {
|
||||
e.preventDefault();
|
||||
form = $(form);
|
||||
|
||||
let options;
|
||||
let isMultiple = form.data("multi");
|
||||
let pollId = form.data("pid");
|
||||
|
||||
let formData = form.serializeArray();
|
||||
if(!isMultiple) {
|
||||
options = [Number(formData[0].value)];
|
||||
} else {
|
||||
options = [];
|
||||
formData.forEach(function(record) {
|
||||
if(!record.name.startsWith("option") || record.value !== "on")
|
||||
return;
|
||||
|
||||
options.push(Number(record.name.substr(6)));
|
||||
});
|
||||
}
|
||||
|
||||
let poll = form.parent();
|
||||
poll.addClass("loading");
|
||||
try {
|
||||
let html = (await API.Polls.vote(pollId, options.join(","))).html;
|
||||
poll.prop("outerHTML", html);
|
||||
} catch(e) {
|
||||
MessageBox(tr("error"), "Sorry: " + e.message, ["OK"], [Function.noop]);
|
||||
} finally {
|
||||
poll.removeClass("loading");
|
||||
}
|
||||
}
|
||||
|
||||
function pollCheckBoxPressed(cb) {
|
||||
cb = $(cb);
|
||||
let form = cb.parent().parent().parent().parent();
|
||||
let checked = $("input:checked", form);
|
||||
if(checked.length >= 1)
|
||||
$("input[type=submit]", form).removeAttr("disabled");
|
||||
else
|
||||
$("input[type=submit]", form).attr("disabled", "disabled");
|
||||
}
|
||||
|
||||
function pollRadioPressed(radio) {
|
||||
let form = $(radio).parent().parent().parent().parent();
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function initPoll(id) {
|
||||
let form = $(`#wall-post-input${id}`).parent();
|
||||
|
||||
let mBody = `
|
||||
<div id="poll_editor${id}">
|
||||
<input type="text" name="title" placeholder="${tr("poll_title")}" />
|
||||
<div class="poll-options" style="margin-top: 10px;"></div>
|
||||
<input type="text" name="newOption" placeholder="${tr("poll_add_option")}" style="margin: 5px 0;" />
|
||||
<hr/>
|
||||
<label><input type="checkbox" name="anon" /> ${tr("poll_anonymous")}</label><br/>
|
||||
<label><input type="checkbox" name="multi" /> ${tr("poll_multiple")}</label><br/>
|
||||
<label><input type="checkbox" name="locked" /> ${tr("poll_locked")}</label><br/>
|
||||
<label>
|
||||
<input type="checkbox" name="expires" />
|
||||
${tr("poll_edit_expires")}
|
||||
<select name="expires_in" style="width: unset;">
|
||||
${[...Array(32).keys()].reduce((p, c) => (!p ? '' : p) + ("<option value='" + c + "'>" + c + " " + tr("poll_edit_expires_days") + "</option>\n"))}
|
||||
</select>
|
||||
</label>
|
||||
<div class="nobold" style="margin: 10px 5px 0">${tr("poll_editor_tips")}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
MessageBox(tr("create_poll"), mBody, [tr("attach"), tr("cancel")], [
|
||||
function() {
|
||||
let dialog = $(this.$dialog().nodes[0]);
|
||||
$("input", dialog).unbind();
|
||||
|
||||
let title = $("input[name=title]", dialog).val();
|
||||
let anon = $("input[name=anon]", dialog).prop("checked") ? "yes" : "no";
|
||||
let multi = $("input[name=multi]", dialog).prop("checked") ? "yes" : "no";
|
||||
let lock = $("input[name=locked]", dialog).prop("checked") ? "yes" : "no";
|
||||
let expires = "infinite";
|
||||
if($("input[name=expires]", dialog).prop("checked"))
|
||||
expires = $("select[name=expires_in]", dialog).val();
|
||||
|
||||
let options = "";
|
||||
$(".poll-option", dialog).each(function() {
|
||||
if($(this).val().length === 0)
|
||||
return;
|
||||
|
||||
options += `<option>${escapeXML($(this).val())}</option>`;
|
||||
});
|
||||
|
||||
let xml = `
|
||||
<Poll title="${title}" anonymous="${anon}" multiple="${multi}" locked="${lock}" duration="${expires}">
|
||||
<options>${options}</options>
|
||||
</Poll>
|
||||
`;
|
||||
$("input[name=poll]", form).val(xml);
|
||||
$(".post-has-poll", form).show();
|
||||
},
|
||||
function() {
|
||||
$("input", $(this.$dialog().nodes[0])).unbind();
|
||||
}
|
||||
]);
|
||||
|
||||
let editor = $(`#poll_editor${id}`);
|
||||
$("input[name=newOption]", editor).bind("focus", function() {
|
||||
let newOption = $('<input type="text" class="poll-option" style="margin: 5px 0;" />');
|
||||
newOption.appendTo($(".poll-options", editor));
|
||||
newOption.focus();
|
||||
newOption.bind("keydown", function(e) {
|
||||
if(e.key === "Enter" && $(this).next().length === 0) {
|
||||
$("input[name=newOption]", editor).focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if($(this).val().length > 0)
|
||||
return;
|
||||
|
||||
if(e.key !== "Backspace")
|
||||
return;
|
||||
|
||||
if($(this).siblings().length === 0)
|
||||
return;
|
||||
|
||||
if($(this).prev().length === 0)
|
||||
$(this).next().focus();
|
||||
else
|
||||
$(this).prev().focus();
|
||||
|
||||
e.preventDefault();
|
||||
$(this).unbind("keydown");
|
||||
$(this).remove();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
"dependencies": {
|
||||
"@atlassian/aui": "^8.5.1",
|
||||
"create-react-class": "^15.7.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"jquery": "^2.1.0",
|
||||
"knockout": "^3.5.1",
|
||||
"ky": "^0.19.0",
|
||||
|
@ -15,6 +16,7 @@
|
|||
"requirejs": "^2.3.6",
|
||||
"soundjs": "^1.0.1",
|
||||
"textfit": "^2.4.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"umbrellajs": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@atlassian/tipsy/-/tipsy-1.3.2.tgz#ab759d461670d712425b2dac7573b79575a10502"
|
||||
integrity sha512-H7qWMs66bztELt2QpOCLYDU9ZM3VZfE0knbRHHLBukH7v9dMkIS5ZwqcGREjWnVt0KNETaBeXxj0FD88TEOGVw==
|
||||
|
||||
"@popperjs/core@^2.9.0":
|
||||
version "2.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
|
||||
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
|
||||
|
||||
asap@~2.0.3:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
|
@ -89,6 +94,18 @@ fbjs@^0.8.0:
|
|||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.18"
|
||||
|
||||
handlebars@^4.7.7:
|
||||
version "4.7.7"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
||||
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
neo-async "^2.6.0"
|
||||
source-map "^0.6.1"
|
||||
wordwrap "^1.0.0"
|
||||
optionalDependencies:
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
iconv-lite@^0.6.2:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
|
@ -170,6 +187,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
|
|||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
monaco-editor@^0.20.0:
|
||||
version "0.20.0"
|
||||
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea"
|
||||
|
@ -185,6 +207,11 @@ msgpack-lite@^0.1.26:
|
|||
int64-buffer "^0.1.9"
|
||||
isarray "^1.0.0"
|
||||
|
||||
neo-async@^2.6.0:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
|
@ -271,11 +298,23 @@ soundjs@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/soundjs/-/soundjs-1.0.1.tgz#99970542d28d0df2a1ebd061ae75c961a98c8180"
|
||||
integrity sha512-MgFPvmKYfpcNiE3X5XybNvScie3DMQlZgmNzUn4puBcpw64f4LqjH/fhM8Sb/eTJ8hK57Crr7mWy0bfJOqPj6Q==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
textfit@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/textfit/-/textfit-2.4.0.tgz#80cba8006bfb9c3d9d552739257957bdda95c79c"
|
||||
integrity sha512-/x4aoY5+/tJmu+iwpBH1yw75TFp86M6X15SvaaY/Eep7YySQYtqdOifEtfvVyMwzl7SZ+G4RQw00FD9g5R6i1Q==
|
||||
|
||||
tippy.js@^6.3.7:
|
||||
version "6.3.7"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
|
||||
integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.9.0"
|
||||
|
||||
trim-extra-html-whitespace@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-extra-html-whitespace/-/trim-extra-html-whitespace-1.3.0.tgz#b47efb0d1a5f2a56a85cc45cea525651e93404cf"
|
||||
|
@ -286,6 +325,11 @@ ua-parser-js@^0.7.18:
|
|||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
||||
integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.17.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377"
|
||||
integrity sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg==
|
||||
|
||||
umbrellajs@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/umbrellajs/-/umbrellajs-3.1.0.tgz#a4e6f0f6381f9d93110b5eee962e0e0864b10bd0"
|
||||
|
@ -300,3 +344,8 @@ whatwg-fetch@>=0.10.0:
|
|||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
|
||||
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
|
||||
|
||||
wordwrap@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
|
2504
composer.lock
generated
|
@ -2,36 +2,36 @@ OpenVK-KB-Heading: Правила
|
|||
|
||||
Адміністрація сайту дозволяє Вам користуватися сайтом на умовах, вказаних у цих правилах.
|
||||
|
||||
Робити можна усе те, що не відноситься до заборонених дій, а до заборонених дій відносяться:
|
||||
Робити можна усе те, що не належать до заборонених дій, а до заборонених дій відносяться:
|
||||
|
||||
1. Відмова у підпорядкуванні правилам чи їх ігнорування,
|
||||
2. Відмова від своєчасного виконання запитів агентів тех. підтримки чи адміністраціі,
|
||||
2. Відмова від своєчасного виконання запитів агентів технічної підтримки чи адміністрації,
|
||||
3. Використання чужих сторінок без дозволу власника,
|
||||
4. Видавання себе за інших людей для отримання вигоди. Винятки:
|
||||
1. Людина дозволила використання своєї особи,
|
||||
2. Людина є оригінальним персонажем, та належить Вам
|
||||
5. Створення массових розсилок будь-яким способом,
|
||||
6. Створення ситуацій, будь-яким чином заважаючих роботі OpenVK,
|
||||
5. Створення масових розсилок будь-яким способом,
|
||||
6. Створення ситуацій, що заважає роботі OpenVK,
|
||||
7. Публікація та зберігання на ресурсі вмісту, який:
|
||||
1. Є незаконним на територіі Франціі,
|
||||
1. Є незаконним на території Франції,
|
||||
2. Містить порнографічні сцени за участю осіб молодше 18 років,
|
||||
3. Містить рекламу заборонених у Франціі препаратів чи інструкціі по їх виготовленню,
|
||||
4. Містить інформацію заборонену на територіі Франціі,
|
||||
3. Містить рекламу заборонених у Франції препаратів чи інструкції по їх виготовленню,
|
||||
4. Містить інформацію заборонену на території Франції,
|
||||
5. Містить сцени нелюдського поводження з людьми або тваринами,
|
||||
6. Порушує авторські та суміжні права,
|
||||
7. Порушує права людини,
|
||||
8. Заважає користувачам виконувати правила чи нормально користуватися послугами, наданими проектом OpenVK.
|
||||
8. Заважає користувачам виконувати правила чи нормально користуватися послугами, наданими проєктом OpenVK.
|
||||
|
||||
Адміністрация є вищим органом влади, який має повне право приймати рішення на рахунок спірних ситуацій, не описаних у правилах.
|
||||
Адміністрація є вищим органом влади, який має повне право приймати рішення на рахунок спірних ситуацій, не описаних у правилах.
|
||||
|
||||
Адміністрація може видати покарання, якщо користувачі:
|
||||
|
||||
1. Публікують заборонений контент на сторінках інших користувачів,
|
||||
2. Обдурюють адміністрацію чи агентів тех. підтримки,
|
||||
2. Обдурюють адміністрацію чи агентів технічної підтримки,
|
||||
3. Вводять в оману користувачів сайту,
|
||||
4. Публічно необґрунтовано критикують OpenVK, чи адміністрацію з ціллю принизити чи образити учасників проекту, чи його керівників,
|
||||
5. Забанені у офіційному чаті OpenVK у Telegram,
|
||||
6. Не поважають адміністрацію проекту чи агентів тех. підтримки.
|
||||
4. Публічно необґрунтовано критикують OpenVK чи адміністрацію з ціллю принизити, образити учасників проєкт, його керівників,
|
||||
5. Заблоковані в офіційному чаті OpenVK у Telegram,
|
||||
6. Не поважають адміністрацію проєкт чи агентів технічної підтримки.
|
||||
|
||||
При порушенні правил, адміністрація може:
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
CREATE TABLE `links_banned` (
|
||||
`id` bigint UNSIGNED NOT NULL,
|
||||
`domain` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`regexp_rule` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
|
||||
`domain` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
|
||||
`regexp_rule` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
|
||||
`reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci,
|
||||
`initiator` bigint UNSIGNED NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
|
||||
|
||||
ALTER TABLE `links_banned`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `links_banned`
|
||||
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||
COMMIT;
|
11
install/sqls/00033-shortcode-aliases.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE `aliases` (
|
||||
`id` bigint UNSIGNED NOT NULL,
|
||||
`owner_id` bigint NOT NULL,
|
||||
`shortcode` varchar(36) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
|
||||
|
||||
ALTER TABLE `aliases`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `aliases`
|
||||
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT;
|
32
install/sqls/00034-polls.sql
Normal file
|
@ -0,0 +1,32 @@
|
|||
CREATE TABLE IF NOT EXISTS `polls` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`owner` bigint unsigned NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`allows_multiple` bit(1) NOT NULL DEFAULT b'0',
|
||||
`is_anonymous` bit(1) NOT NULL DEFAULT b'0',
|
||||
`can_revote` bit(1) NOT NULL DEFAULT b'0',
|
||||
`until` bigint unsigned DEFAULT NULL,
|
||||
`ended` bit(1) NOT NULL DEFAULT b'0',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `poll_options` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`poll` bigint unsigned NOT NULL,
|
||||
`name` varchar(512) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `poll` (`poll`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `poll_votes` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user` bigint unsigned NOT NULL,
|
||||
`poll` bigint unsigned NOT NULL,
|
||||
`option` bigint unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `user_option` (`user`,`option`),
|
||||
KEY `option` (`option`),
|
||||
KEY `poll` (`poll`),
|
||||
KEY `user_poll` (`user`,`poll`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
|
|
@ -17,6 +17,8 @@
|
|||
"password" = "Գաղտնաբառ";
|
||||
"registration" = "Գրանցում";
|
||||
"forgot_password" = "Մոռացե՞լ եք գաղտնաբառը";
|
||||
"checkbox_in_registration" = "Ես համաձայն եմ <a href='/privacy'>կոնֆիդենցիալության քաղաքականությանն</a> ու <a href='/about'>կայքի կանոնադրությանը</a>։";
|
||||
"checkbox_in_registration_unchecked" = "Դուք պետք է համաձայնվեք պայմանների հետ նախքան գրանցվելը։";
|
||||
|
||||
"login_failed" = "Չհաջողվեց մուտք գործել";
|
||||
"invalid_username_or_password" = "Սխալ օգտատիրոջ անուն կամ գաղտնաբառ։ Դուք կարող եք <a href='/restore'>վերականգնել ձեր գաղտնաբառը</a>։";
|
||||
|
@ -93,6 +95,9 @@
|
|||
"years_many" = "$1 տարեկան";
|
||||
"years_other" = "$1 տարեկան";
|
||||
|
||||
"show_my_birthday" = "Ցույց տալ ծննդյան օրը";
|
||||
"show_only_month_and_day" = "Ցուցադրել միայն ամիսն ու օրը";
|
||||
|
||||
"relationship" = "Ընտանեկան դրություն";
|
||||
|
||||
"relationship_0" = "Ընտրված չէ";
|
||||
|
@ -144,8 +149,8 @@
|
|||
|
||||
"updated_at" = "Թարմացված է $1";
|
||||
|
||||
"user_banned" = "Ցավո՛ք, մենք ստիպված <b>$1</b>-ի էջը կասեցրել ենք։";
|
||||
"user_banned_comment" = "Մոդերատորի մեկնաբանությունը․ ";
|
||||
"user_banned" = "Ցավո՛ք, մենք ստիպված կասեցրել ենք <b>$1</b>-ի էջը։";
|
||||
"user_banned_comment" = "Մոդերատորի մեկնաբանությունը․";
|
||||
|
||||
/* Wall */
|
||||
|
||||
|
@ -154,6 +159,10 @@
|
|||
"post_writes_m" = "գրել է";
|
||||
"post_writes_f" = "գրել է";
|
||||
"post_writes_g" = "հրապարակել են";
|
||||
"post_deact_m" = "ջնջել է էջը հետևյալ բառերով.";
|
||||
"post_deact_f" = "ջնջել է էջը հետևյալ բառերով.";
|
||||
"post_deact_silent_m" = "սուս ու փուս ջնջել է էջը։";
|
||||
"post_deact_silent_f" = "սուս ու փուս ջնջել է էջը։";
|
||||
"wall" = "Պատ";
|
||||
"post" = "Գրություն";
|
||||
"write" = "Գրել";
|
||||
|
@ -167,7 +176,7 @@
|
|||
"comments_tip" = "Եղե՛ք առաջինը ով կթողնի իր կարծիքը։";
|
||||
"your_comment" = "Ձեր մեկնաբանությունը";
|
||||
"shown" = "Ցուցադրված է";
|
||||
"x_out_of" = "$1 –ը ՝";
|
||||
"x_out_of" = "$1–ը";
|
||||
"wall_zero" = "գրություն չկա";
|
||||
"wall_one" = "մեկ գրություն";
|
||||
"wall_few" = "$1 գրություն";
|
||||
|
@ -215,6 +224,8 @@
|
|||
"incoming_req" = "Բաժանորդներ";
|
||||
"outcoming_req" = "Հայցեր";
|
||||
"req" = "Հայցեր";
|
||||
"friends_online" = "Ընկերները ցանցում";
|
||||
"all_friends" = "Բոլոր ընկերները";
|
||||
|
||||
"req_zero" = "Ոչ մի հայտ չի գտնվել...";
|
||||
"req_one" = "Գտնվեց մեկ հայտ";
|
||||
|
@ -246,6 +257,12 @@
|
|||
"subscriptions_many" = "$1 բաժանորդագրություն";
|
||||
"subscriptions_other" = "$1 բաժանորդագրություն";
|
||||
|
||||
"friends_list_online_zero" = "Դուք դեռ չունեք ցանցի մեջ գտնվող ընկերներ";
|
||||
"friends_list_online_one" = "Ձեր $1 ընկերը ցանցի մեջ է";
|
||||
"friends_list_online_few" = "Ձեր $1 ընկերները ցանցի մեջ են";
|
||||
"friends_list_online_many" = "Ձեր $1 ընկերները ցանցի մեջ են";
|
||||
"friends_list_online_other" = "Ձեր $1 ընկերները ցանցի մեջ են";
|
||||
|
||||
/* Group */
|
||||
|
||||
"name_group" = "Անվանում";
|
||||
|
@ -364,6 +381,10 @@
|
|||
"edit_note" = "Խմբագրել նշումը";
|
||||
"actions" = "Գործողություններ";
|
||||
"notes_start_screen" = "Նշումների շնորհիվ Դուք կարող եք կիսվել ընկերների հետ տարբեր իրադարձություններով, և իմանալ թե ինչ է կատարվում իրենց մոտ։";
|
||||
"note_preview" = "Նախադիտում";
|
||||
"note_preview_warn" = "Սա ընդամենը նախադիտում է";
|
||||
"note_preview_warn_details" = "Պահպանելուց հետո նշումները կարող են այլ տեսք ունենալ։ Ու մեկ էլ, այդքան հաճախ նախադիտում մի արեք։";
|
||||
"note_preview_empty_err" = "Ինչու՞ նախադիտել նշումը առանց վերնագրի կամ բովանդակության։";
|
||||
|
||||
"edited" = "Խմբագրված է";
|
||||
|
||||
|
@ -428,6 +449,7 @@
|
|||
"avatar" = "Ավատար";
|
||||
"privacy" = "Գաղտնիություն";
|
||||
"interface" = "Արտաքին տեսք";
|
||||
"security" = "Անվտանգություն";
|
||||
|
||||
"profile_picture" = "Էջի նկար";
|
||||
|
||||
|
@ -445,7 +467,7 @@
|
|||
|
||||
"arbitrary_avatars" = "Կամայական";
|
||||
"cut" = "Կտրվածք";
|
||||
"round_avatars" = "Կլոր ավատար";
|
||||
"round_avatars" = "Շրջանաձև";
|
||||
|
||||
"apply_style_for_this_device" = "Հաստատել տեսքը միայն այս սարքի համար";
|
||||
|
||||
|
@ -508,6 +530,33 @@
|
|||
|
||||
"email_change_confirm_message" = "Որպեսզի Ձեր փոփոխությունը կատարվի, հաստատե՛ք Ձեր նոր էլեկտրոնային հասցեն։ Այնտեղ մենք ուղարկել ենք հրահանգները։";
|
||||
|
||||
"profile_deactivate" = "Էջի հեռացում";
|
||||
"profile_deactivate_button" = "Ջնջել էջը";
|
||||
"profile_deactivate_header" = "Մենք ցավում ենք որ Դուք ցանկանում եք ջնջել ձեր էջը։ Դրա համար Դուք կարող եք նշել հեռացման պատճառը և Ձեր կարծիքը այդ առումով։ Մեզ համար կարևոր է Ձեր կարծիքը, որպեսզի դարձնենք կայքը ավելի լավը։";
|
||||
"profile_deactivate_reason_header" = "Խնդրում ենք, նշել Ձեր էջի ջնջման պատճառները";
|
||||
"profile_deactivate_reason_1" = "Ես ունեմ այլ էջ այս կայնքում";
|
||||
"profile_deactivate_reason_1_text" = "Ես նոր էջ եմ սկսել ու ցանկանում եմ սկսել ամեն ինչ նորից";
|
||||
"profile_deactivate_reason_2" = "Կայքն ինձնից շատ ժամանակ է խլում";
|
||||
"profile_deactivate_reason_2_text" = "Թեկուզ այս կայքը լավն է, բայց ցավոք ինձնից ահագին ժամանակ է խլում։";
|
||||
"profile_deactivate_reason_3" = "Կայքը բազմաթիվ անցանկանալի մատերիալներ է պարունակում";
|
||||
"profile_deactivate_reason_3_text" = "Ես բազմաթիվ պոռնոգրաֆիա ու անօրինական կինոներ եմ գրել – բոլ ա։ Հիմա հավես չկա։";
|
||||
"profile_deactivate_reason_4" = "Ինձ անհանգստացնում է իմ տվյալների անվտանգությունը";
|
||||
"profile_deactivate_reason_4_text" = "Ինձ հետևում են ու շատ վախենալու է ինձ այստեղ գտնվելը";
|
||||
"profile_deactivate_reason_5" = "Իմ էջը չեն մեկնաբանում";
|
||||
"profile_deactivate_reason_5_text" = "Ինձ այստեղ շան տեղ դնող չկա ու ես տխրում եմ։ Դուք կզղջաք որ ես հեռացա...";
|
||||
"profile_deactivate_reason_6" = "Այլ պատճառ";
|
||||
|
||||
"profile_deactivated_msg" = "Ձեր էջը <b>ջնջված է</b>։<br/><br/>Եթե Դուք ուզենաք նորից օգտվել Ձեր էջով, կարող եք <a href='/settings/reactivate'>ապաակտիվացնել այն</a> մինչև $1:";
|
||||
"profile_deactivated_status" = "Էջը ջնջված է";
|
||||
"profile_deactivated_info" = "Օգտատիրոջ էջը հեռացվել է։<br/>Մանրամասն տեղեկատվությունը բացակայում է։";
|
||||
|
||||
"share_with_friends" = "Պատմել ընկերներին";
|
||||
|
||||
"end_all_sessions" = "Դուրս գալ բոլոր սեսսիաներից";
|
||||
"end_all_sessions_description" = "Եթե ցանկանում եք դուրս գալ $1–ից ամեն դեվայսից, սեղմե՛ք ներքևի կոճակը";
|
||||
|
||||
"end_all_sessions_done" = "Բոլոր սեսսիաները նետված են, ներառյալ բջջային հավելվածները";
|
||||
|
||||
/* Two-factor authentication */
|
||||
|
||||
"two_factor_authentication" = "Երկքայլ աուտենտիֆիկացիա";
|
||||
|
@ -708,6 +757,75 @@
|
|||
|
||||
"users_gifts" = "Նվերներ";
|
||||
|
||||
/* Apps */
|
||||
|
||||
"app" = "Հավելված";
|
||||
"apps" = "Հավելվածներ";
|
||||
"my_apps" = "Իմ հավելվածները";
|
||||
"all_apps" = "Բոլոր հավելվածները";
|
||||
"installed_apps" = "Տեղադրված հավելվածները";
|
||||
"own_apps" = "Կառավարում";
|
||||
"own_apps_alternate" = "Իմ այլ հավելվածները";
|
||||
|
||||
"app_play" = "միացնել";
|
||||
"app_uninstall" = "անջատել";
|
||||
"app_edit" = "խմբագրել";
|
||||
"app_dev" = "Մշակող";
|
||||
|
||||
"create_app" = "Ստեղծել հավելված";
|
||||
"edit_app" = "Խմբագրել հավելվածը";
|
||||
"new_app" = "Նոր հավելված";
|
||||
"app_news" = "Նորություններով նշում";
|
||||
"app_state" = "Կարգավիճակ";
|
||||
"app_enabled" = "Միացված է";
|
||||
"app_creation_hint_url" = "URL–ում նշեք կոնկրետ հասցեն իր սխեմայով (https), պորտով (80) և անհրաժեշտ միացման կարգավորումներով։";
|
||||
"app_creation_hint_iframe" = "Ձեր հավելվածը բացված է iframe–ով։";
|
||||
"app_balance" = "Հավելվածի հաշվին կա <b>$1</b> ձայն։";
|
||||
"app_users" = "Ձեր հավելվածով օգտվում է <b>$1</b> հոգի։";
|
||||
"app_withdrawal_q" = "դուրս բերե՞լ";
|
||||
"app_withdrawal" = "Միջոցների դուրս բերում";
|
||||
"app_withdrawal_empty" = "Կներեք, դատարկությունը չհաջողվեց դուրս բերել։";
|
||||
"app_withdrawal_created" = "$1 ձայնի դուրս բերման հայտը գրանցված է։ Սպասեք հաշվառմանը։";
|
||||
|
||||
"appjs_payment" = "Գնման վճարում";
|
||||
"appjs_payment_intro" = "Դուք պատրաստվում եք հավելվածի գնումը վճարել";
|
||||
"appjs_order_items" = "Գնման ցուցակ";
|
||||
"appjs_payment_total" = "Վճարման ընդհանուր գին";
|
||||
"appjs_payment_confirm" = "Վճարել";
|
||||
"appjs_err_funds" = "Չհաջողվե՛ց վճարել գնումը անբավարար միջոցների համար։";
|
||||
|
||||
"appjs_wall_post" = "Հրապարակել գրությունտը";
|
||||
"appjs_wall_post_desc" = "ցանկանում է Ձեր պատին գրություն թողնել";
|
||||
|
||||
"appjs_sperm_friends" = "ձեր ընկերներին";
|
||||
"appjs_sperm_friends_desc" = "ավելացնել օգտատերերին որպես ընկերներ և կարդալ Ձեր գրությունները";
|
||||
"appjs_sperm_wall" = "ձեր պատին";
|
||||
"appjs_sperm_wall_desc" = "դիտել Ձեր լուրերը, կարդալ պատն ու թողել գրություններ";
|
||||
"appjs_sperm_messages" = "ձեր նամակներին";
|
||||
"appjs_sperm_messages_desc" = "կարդալ և գրել նամակներ Ձեր անունից";
|
||||
"appjs_sperm_groups" = "ձեր հանրություններին";
|
||||
"appjs_sperm_groups_desc" = "դիտել Ձեր խմբերի ցուցակն ու բաժանորդագրել դեպի այլ խմբեր";
|
||||
"appjs_sperm_likes" = "լայքելու ֆունկցիոնալին";
|
||||
"appjs_sperm_likes_desc" = "տեղադրել և հանել \"Դուր գալու\" ռեակցիաները ձայնագրություններից";
|
||||
|
||||
"appjs_sperm_request" = "Հասանելիության հարցում";
|
||||
"appjs_sperm_requests" = "հասանելիություն է խնդրում";
|
||||
"appjs_sperm_can" = "Հավելվածը կարող է";
|
||||
"appjs_sperm_allow" = "Թույլատրել";
|
||||
"appjs_sperm_disallow" = "Չթույլատրել";
|
||||
|
||||
"app_uninstalled" = "Հավելվածն անջատված է";
|
||||
"app_uninstalled_desc" = "Այն Ձեր անունից էլ չի կարող կատարել գործողություններ։";
|
||||
"app_err_not_found" = "Հավելվածը չի գտնվել";
|
||||
"app_err_not_found_desc" = "Սխալ կամ անջատված իդենտիֆիկատոր։";
|
||||
"app_err_forbidden_desc" = "Այս հավելվածը Ձերը չէ։";
|
||||
"app_err_url" = "Սխալ հասցե";
|
||||
"app_err_url_desc" = "Հավելվածի հասցեն չանցավ ստուգումը. համոզվե՛ք որ այն ճիշտ է գրված:";
|
||||
"app_err_ava" = "Չհաջողվե՛ց վերբեռնել ավատարը:";
|
||||
"app_err_ava_desc" = "Ավատարը չափազանց մեծ և ծուռ է. ընդհանուր բնույթի սխալ №$res.";
|
||||
"app_err_note" = "Չհաջողվե՛ց ամրացնել նորությունների նիշքը";
|
||||
"app_err_note_desc" = "Համոզվե՛ք որ հղումը ճիշտ է և պատկանում է Ձեզ։";
|
||||
|
||||
/* Support */
|
||||
|
||||
"support_opened" = "Բաց";
|
||||
|
@ -767,7 +885,12 @@
|
|||
"banned_alt" = "Օգտատերը արգելափակված է";
|
||||
"banned_1" = "Կներե՛ք, <b>$1</b>, բայց Դուք կասեցված եք։";
|
||||
"banned_2" = "Պատճառը հետևյալն է․ <b>$1</b>. Ափսոս, բայց մենք ստիպված Ձեզ հավերժ ենք կասեցրել;";
|
||||
"banned_perm" = "Ցավոք, մենք ստիպված արգելափակել ենք Ձեզ ընդմիշտ։";
|
||||
"banned_until_time" = "Այս անգամ մենք ստիպված արգելափակել ենք Ձեզ մինչև <b>$1</b>";
|
||||
"banned_3" = "Դուք դեռ կարող եք <a href=\"/support?act=new\">գրել նամակ աջակցության ծառայությանը</a>, եթե համարում եք որ դա սխալմունք է, կամ էլ կարող եք <a href=\"/logout?hash=$1\">դուրս գալ</a>։";
|
||||
"banned_unban_myself" = "Ապասառեցնել էջը";
|
||||
"banned_unban_title" = "Ձեր հաշիվը ապասառեցված է։";
|
||||
"banned_unban_description" = "Աշխատե՛ք այլևս չխախտել կանոնները։";
|
||||
|
||||
/* Registration confirm */
|
||||
|
||||
|
@ -996,6 +1119,17 @@
|
|||
"admin_commerce_disabled" = "Կոմմերցիան անջատված է համակարգային ադմինիստրատորի կողմից";
|
||||
"admin_commerce_disabled_desc" = "Վաուչերների և նվերների կարգավորումները կպահպանվեն, բայց ոչ մի ազդեցություն չեն ունենա։";
|
||||
|
||||
"admin_banned_links" = "Արգելափակված հղումներ";
|
||||
"admin_banned_link" = "Հղում";
|
||||
"admin_banned_domain" = "Դոմեն";
|
||||
"admin_banned_link_description" = "Պրոտոկոլով (https://example.com/)";
|
||||
"admin_banned_link_regexp" = "Ռեգուլյար արտահայտություն";
|
||||
"admin_banned_link_regexp_description" = "Տեղադրվում է վերոնշյալ դոմենից հետո։ Մի լրացրե՛ք, եթե ցանկանում եք արգելափակել ամբողջ դոմենը";
|
||||
"admin_banned_link_reason" = "Պատճառ";
|
||||
"admin_banned_link_initiator" = "Նախաձեռնող";
|
||||
"admin_banned_link_not_specified" = "Հղումը նշված չէ";
|
||||
"admin_banned_link_not_found" = "Հղումը չի գտնվել";
|
||||
|
||||
/* Paginator (deprecated) */
|
||||
|
||||
"paginator_back" = "Հետ";
|
||||
|
@ -1059,3 +1193,21 @@
|
|||
|
||||
"cookies_popup_content" = "Cookie բառը անգլերենից նշանակում է թխվածքաբլիթ, իսկ թխվածքաբլիթը համեղ է։ Մեր կայքը չի ուտում թխվածք, բայց օգտագործում է այն ուղղակի սեսսիան կողմնորոշելու համար։ Ավելի մանրամասն կարող եք ծանոթանալ մեր <a href='/privacy'>գաղտնիության քաղաքականությանը</a> հավելյալ ինֆորմացիայի համար։";
|
||||
"cookies_popup_agree" = "Համաձայն եմ";
|
||||
|
||||
/* Away */
|
||||
|
||||
"url_is_banned" = "Անցումն անհնար է";
|
||||
"url_is_banned_comment" = "Ադմինիստրացիան <b>$1</b> խորհուրդ չի տալից անցնել այս հղումով։";
|
||||
"url_is_banned_comment_r" = "Ադմինիստրացիան <b>$1</b> խորհուրդ չի տալից անցնել այս հղումով։<br><br>Պատճառը: <b>$2</b>";
|
||||
"url_is_banned_default_reason" = "Հղումը դեպի կայք կարող է ստեղծված լինել շորթողներից ՝ օգտատերերին խաբելու և խարդախության նպատակներով, շահույթ ստանալու համար։";
|
||||
"url_is_banned_title" = "Հղում դեպի կասկածելի կայք";
|
||||
"url_is_banned_proceed" = "Անցնել հղումով";
|
||||
|
||||
/* Maintenance */
|
||||
|
||||
"global_maintenance" = "Տեխնիկական աշխատանքներ";
|
||||
"section_maintenance" = "Բաժինը անհասանելի է";
|
||||
"undergoing_global_maintenance" = "Ցավոք սրտի, հիմա հոսքը փակված է տեխնիկական աշխատանքներ անցկացնելու համար։ Մենք արդեն աշխատում ենք խնդիրները շտկելու ուղղությամբ։ Խնդրում ենք այցելել մի քիչ ուշ։";
|
||||
"undergoing_section_maintenance" = "Ցավոք սրտի, <b>$1</b> բաժինը ժամանակավորապես անհասանելի է։ Մենք արդեն աշխատում ենք խնդիրները շտկելու ուղղությամբ։ Խնդրում ենք այցելել մի քիչ ուշ։";
|
||||
|
||||
"topics" = "Թեմաներ";
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
"registration" = "Registration";
|
||||
"forgot_password" = "Forgot your password?";
|
||||
|
||||
"checkbox_in_registration" = "I agree to the <a href='/privacy'>privacy policy</a> and <a href='/about'>site policies</a>";
|
||||
"checkbox_in_registration_unchecked" = "You must agree to the privacy policy and rules in order to register.";
|
||||
|
||||
"login_failed" = "Login failed";
|
||||
"invalid_username_or_password" = "The username or password you entered is incorrect. <a href='/restore'>Forgot your password?</a>";
|
||||
|
||||
|
@ -158,6 +161,9 @@
|
|||
"post_deact_f" = "deleted her profile saying:";
|
||||
"post_deact_silent_m" = "silently deleted his profile.";
|
||||
"post_deact_silent_f" = "silently deleted her profile.";
|
||||
"post_on_your_wall" = "on your wall";
|
||||
"post_on_group_wall" = "in $1";
|
||||
"post_on_user_wall" = "on $1's wall";
|
||||
"wall" = "Wall";
|
||||
"post" = "Post";
|
||||
"write" = "Write";
|
||||
|
@ -874,6 +880,38 @@
|
|||
"messages_error_1" = "Message not delivered";
|
||||
"messages_error_1_description" = "There was a general error in sending this message...";
|
||||
|
||||
/* Polls */
|
||||
"poll" = "Poll";
|
||||
"create_poll" = "Create poll";
|
||||
"poll_title" = "Ask a question";
|
||||
"poll_add_option" = "Add an option...";
|
||||
"poll_anonymous" = "Anonymous votes";
|
||||
"poll_multiple" = "Multiple answers";
|
||||
"poll_locked" = "Quiz mode (no retraction)";
|
||||
"poll_edit_expires" = "Expires in: ";
|
||||
"poll_edit_expires_days" = "days";
|
||||
"poll_editor_tips" = "Pressing backspace in empty option will remove it. Use Tab/Enter (in last option) to create new options faster.";
|
||||
"poll_embed" = "Embed code";
|
||||
|
||||
"poll_voter_count_zero" = "Be <b>the first one</b> to vote!";
|
||||
"poll_voter_count_one" = "<b>Only one</b> user voted.";
|
||||
"poll_voter_count_few" = "<b>$1</b> users voted.";
|
||||
"poll_voter_count_many" = "<b>$1</b> users voted.";
|
||||
"poll_voter_count_other" = "<b>$1</b> users voted.";
|
||||
|
||||
"poll_voters_list" = "Voters";
|
||||
|
||||
"poll_anon" = "Anonymous";
|
||||
"poll_public" = "Public";
|
||||
"poll_multi" = "multiple choice";
|
||||
"poll_lock" = "can't revoke";
|
||||
"poll_until" = "until $1";
|
||||
|
||||
"poll_err_to_much_options" = "Too much options supplied.";
|
||||
"poll_err_anonymous" = "Can't access voters list: poll is anonymous.";
|
||||
"cast_vote" = "Vote!";
|
||||
"retract_vote" = "Cancel my vote";
|
||||
|
||||
/* Discussions */
|
||||
|
||||
"discussions" = "Discussions";
|
||||
|
|
|
@ -13,7 +13,7 @@ list:
|
|||
flag: "ua"
|
||||
name: "Ukrainian"
|
||||
native_name: "Україньска"
|
||||
author: "Andrej Lenťaj, Maxim Hrabovi (dechioyo) and Kirill (mbsoft)"
|
||||
author: "Yaroslav Bjelograd, Andrej Lenťaj, Maxim Hrabovi (dechioyo) and Kirill (mbsoft)"
|
||||
- code: "by"
|
||||
flag: "by"
|
||||
name: "Belarussian"
|
||||
|
|
1
locales/qqx.strings
Normal file
|
@ -0,0 +1 @@
|
|||
/* Used for viewing message keys */
|
|
@ -166,6 +166,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" = "Написать";
|
||||
|
@ -921,6 +924,38 @@
|
|||
"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" = "Будьте <b>первым</b>, кто проголосует!";
|
||||
"poll_voter_count_one" = "В опросе проголосовал <b>один</b> человек.";
|
||||
"poll_voter_count_few" = "В опросе проголосовало <b>$1</b> человека.";
|
||||
"poll_voter_count_many" = "В опросе проголосовало <b>$1</b> человек.";
|
||||
"poll_voter_count_other" = "В опросе проголосовало <b>$1</b> человек.";
|
||||
|
||||
"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" = "Обсуждения";
|
||||
|
@ -996,7 +1031,7 @@
|
|||
"changes_saved_comment" = "Новые данные появятся на вашей странице";
|
||||
|
||||
"photo_saved" = "Фотография сохранена";
|
||||
"photo_saved_comment" = "Новое изображние профиля появится у вас на странице";
|
||||
"photo_saved_comment" = "Новое изображение профиля появится у вас на странице";
|
||||
|
||||
"shared_succ" = "Запись появится на вашей стене. Нажмите на уведомление, чтобы перейти к своей стене.";
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
"password" = "Пароль";
|
||||
"registration" = "Реєстрація";
|
||||
"forgot_password" = "Забули пароль?";
|
||||
"checkbox_in_registration" = "Я згоден з <a href='/privacy'>політикою конфіденційності</a> і <a href='/about'>правилами сайту</a>";
|
||||
"checkbox_in_registration_unchecked" = "Ви повинні погодитися з політикою конфіденційності та правилами, щоб зареєструватися.";
|
||||
|
||||
"login_failed" = "Не вдалося увійти";
|
||||
"invalid_username_or_password" = "Неправильне ім'я користувача або пароль. <a href='/restore'>Забули пароль?</a>";
|
||||
|
@ -160,6 +162,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" = "Написати";
|
||||
|
@ -210,6 +215,7 @@
|
|||
|
||||
/* Friends */
|
||||
|
||||
"friends" = "Друзі";
|
||||
"followers" = "Підписники";
|
||||
"follower" = "Підписник";
|
||||
"friends_add" = "Додати в друзі";
|
||||
|
@ -218,10 +224,11 @@
|
|||
"friends_accept" = "Прийняти заявку";
|
||||
"send_message" = "Відправити повідомлення";
|
||||
"incoming_req" = "Підписники";
|
||||
"outcoming_req" = "Вихідні";
|
||||
"req" = "Заявки";
|
||||
"outcoming_req" = "Заявки";
|
||||
"friends_online" = "Друзі онлайн";
|
||||
"all_friends" = "Усі друзі";
|
||||
"req" = "Заявки";
|
||||
|
||||
"req_zero" = "Не знайдено жодної заявки...";
|
||||
"req_one" = "Знайдена $1 заявка";
|
||||
|
@ -235,18 +242,18 @@
|
|||
"friends_many" = "$1 друзів";
|
||||
"friends_other" = "$1 друзів";
|
||||
|
||||
"friends_list_zero" = "У вас поки немає друзів";
|
||||
"friends_list_one" = "У Вас $1 друг";
|
||||
"friends_list_few" = "У Вас $1 друг";
|
||||
"friends_many" = "$1 друзів";
|
||||
"friends_other" = "$1 друзів";
|
||||
|
||||
"friends_online_zero" = "Жодного друга онлайн";
|
||||
"friends_online_one" = "$1 друг онлайн";
|
||||
"friends_online_few" = "$1 друга онлайн";
|
||||
"friends_online_many" = "$1 друзів онлайн";
|
||||
"friends_online_other" = "$1 друзів онлайн";
|
||||
|
||||
"friends_list_zero" = "У вас поки немає друзів";
|
||||
"friends_list_one" = "У Вас $1 друг";
|
||||
"friends_list_few" = "У Вас $1 друг";
|
||||
"friends_many" = "$1 друзів";
|
||||
"friends_other" = "$1 друзів";
|
||||
|
||||
"followers_zero" = "Жодного підписника";
|
||||
"followers_one" = "$1 підписник";
|
||||
"followers_few" = "$1 підписника";
|
||||
|
@ -1122,6 +1129,17 @@
|
|||
"admin_commerce_disabled" = "Комерція відключена системним адміністратором";
|
||||
"admin_commerce_disabled_desc" = "Налаштування ваучерів та подарунків будуть збережені, але не матимуть впливу.";
|
||||
|
||||
"admin_banned_links" = "Заблоковані посилання";
|
||||
"admin_banned_link" = "Посилання";
|
||||
"admin_banned_domain" = "Домен";
|
||||
"admin_banned_link_description" = "З протоколом (https://example.org/)";
|
||||
"admin_banned_link_regexp" = "Регулярний вираз";
|
||||
"admin_banned_link_regexp_description" = "Підставляється після домену, зазначеного вище. Не заповнюйте, якщо хочете заблокувати весь домен";
|
||||
"admin_banned_link_reason" = "Причина";
|
||||
"admin_banned_link_initiator" = "Ініціатор";
|
||||
"admin_banned_link_not_specified" = "Посилання не зазначено";
|
||||
"admin_banned_link_not_found" = "Посилання не знайдено";
|
||||
|
||||
/* Paginator (deprecated) */
|
||||
|
||||
"paginator_back" = "Назад";
|
||||
|
@ -1185,3 +1203,53 @@
|
|||
|
||||
"cookies_popup_content" = "Цей веб-сайт використовує cookies для того, щоб ідентифікувати вашу сесію і нічого більше. Ознайомтеся з нашою <a href='/privacy'>політикою конфіденційності</a> для отримання додаткової інформації.";
|
||||
"cookies_popup_agree" = "Згоден";
|
||||
|
||||
/* Away */
|
||||
|
||||
"url_is_banned" = "Перехід неможливий";
|
||||
"url_is_banned_comment" = "Адміністрація <b>$1</b> не рекомендує переходити за цим посиланням.";
|
||||
"url_is_banned_comment_r" = "Адміністрація <b>$1</b> не рекомендує переходити за цим посиланням.<br><br>Підстава: <b>$2</b>";
|
||||
"url_is_banned_default_reason" = "Посилання, за яким Ви спробували перейти, може вести на сайт, що був створений з метою обману користувачів і отримання шляхом цього неправомірного прибутку.";
|
||||
"url_is_banned_title" = "Посилання на підозрілий сайт";
|
||||
"url_is_banned_proceed" = "Перейти за посиланням";
|
||||
|
||||
/* Maintenance */
|
||||
|
||||
"global_maintenance" = "Технічні роботи";
|
||||
"section_maintenance" = "Розділ недоступний";
|
||||
"undergoing_global_maintenance" = "На жаль, зараз інстанція закрита на технічні роботи. Ми вже працюємо над усуненням неполадок. Будь ласка, спробуйте зайти пізніше.";
|
||||
"undergoing_section_maintenance" = "На жаль, розділ <b>$1</b> тимчасово недоступний. Ми вже працюємо над усуненням неполадок. Будь ласка, спробуйте зайти пізніше.";
|
||||
|
||||
"topics" = "Теми";
|
||||
|
||||
/* Polls */
|
||||
"poll" = "Опитування";
|
||||
"create_poll" = "Нове опитування";
|
||||
"poll_title" = "Тема опитування";
|
||||
"poll_add_option" = "Додати новий варіант відповіді";
|
||||
"poll_anonymous" = "Анонiмне опитування";
|
||||
"poll_multiple" = "Множинний вибір";
|
||||
"poll_locked" = "Заборонити відміняти свій голос";
|
||||
"poll_edit_expires" = "Опитування закінчується через: ";
|
||||
"poll_edit_expires_days" = "днів";
|
||||
"poll_editor_tips" = "Натискання Backspace у пустому варианті приведе до його видалення. Tab/Enter у останньому додає новий.";
|
||||
"poll_embed" = "Отримати код";
|
||||
|
||||
"poll_voter_count_zero" = "Станьте <b>першим</b>, хто проголосує!";
|
||||
"poll_voter_count_one" = "У опитуванні проголосовала <b>одна</b> людина.";
|
||||
"poll_voter_count_few" = "У опитуванні проголосувало <b>$1</b> людини.";
|
||||
"poll_voter_count_many" = "У опитуванні проголосувало <b>$1</b> людей.";
|
||||
"poll_voter_count_other" = "У опитуванні проголосувало <b>$1</b> людей.";
|
||||
|
||||
"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" = "Відмінити голос";
|
||||
|
|
|
@ -58,6 +58,20 @@ openvk:
|
|||
susLinks:
|
||||
warnings: true
|
||||
showReason: true
|
||||
maintenanceMode:
|
||||
all: false
|
||||
photos: false
|
||||
videos: false
|
||||
messenger: false
|
||||
user: false
|
||||
group: false
|
||||
comment: false
|
||||
gifts: false
|
||||
apps: false
|
||||
notes: false
|
||||
notification: false
|
||||
support: false
|
||||
topics: false
|
||||
ton:
|
||||
enabled: false
|
||||
address: "🅿"
|
||||
|
|
|
@ -44,3 +44,5 @@ comments.allow-graffiti: 0
|
|||
# + Set this option to -1 if you want to disable the limit
|
||||
# + Set this option to any non-negative number to be this limit
|
||||
wall.repost-liking-recursion-limit: 10
|
||||
|
||||
polls.max-opts: 10
|
BIN
themepacks/midnight/res/checkbox.png
Normal file
After Width: | Height: | Size: 288 B |
BIN
themepacks/midnight/res/flex_arrow_open.png
Normal file
After Width: | Height: | Size: 1,012 B |
BIN
themepacks/midnight/res/flex_arrow_shut.gif
Normal file
After Width: | Height: | Size: 145 B |
BIN
themepacks/midnight/res/header.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
themepacks/midnight/res/header_custom.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
themepacks/midnight/res/header_purple.png
Normal file
After Width: | Height: | Size: 162 B |
BIN
themepacks/midnight/res/input_clear.gif
Normal file
After Width: | Height: | Size: 64 B |
BIN
themepacks/midnight/res/like.gif
Normal file
After Width: | Height: | Size: 594 B |
BIN
themepacks/midnight/res/pin.png
Normal file
After Width: | Height: | Size: 777 B |
BIN
themepacks/midnight/res/published.gif
Normal file
After Width: | Height: | Size: 68 B |
BIN
themepacks/midnight/res/radio.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
themepacks/midnight/res/xheader.png
Normal file
After Width: | Height: | Size: 7 KiB |
BIN
themepacks/midnight/res/xheader_custom.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
7
themepacks/midnight/res/xmas.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.page_header {
|
||||
background-image: url('/themepack/midnight/0.0.1.0/resource/xheader.png');
|
||||
}
|
||||
|
||||
.page_custom_header {
|
||||
background-image: url('/themepack/midnight/0.0.1.0/resource/xheader_custom.png');
|
||||
}
|
214
themepacks/midnight/stylesheet.css
Normal file
|
@ -0,0 +1,214 @@
|
|||
* {
|
||||
scrollbar-color: #3f365b #1d192b;
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: dark !important;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #0e0b1a;
|
||||
color: #c6d2e8;
|
||||
}
|
||||
|
||||
span, .post-author .date, .crp-entry--message---text, .messenger-app--messages---message .time, .navigation-lang .link_new {
|
||||
color: #8b9ab5 !important;
|
||||
}
|
||||
|
||||
.nobold, nobold {
|
||||
color: #6f82a8;
|
||||
}
|
||||
|
||||
.page_status {
|
||||
color: #c6d2e8;
|
||||
}
|
||||
|
||||
.profileName h2 {
|
||||
color: #8eb2d0;
|
||||
}
|
||||
|
||||
.wrap1, .wrap2, .page-wrap, #wrapH, #wrapHI {
|
||||
border-color: #1c202f;
|
||||
}
|
||||
|
||||
.accountInfo, .left_small_block, #profile_link, .profile_link, .navigation .link, .navigation .link:hover, .navigation_footer .link, .navigation_footer .link:hover, .completeness-gauge, input[type="text"], input[type="password"], input[type~="text"], input[type~="password"], input[type="email"], input[type="phone"], input[type~="email"], input[type~="phone"], input[type="search"], input[type~="search"], input[type~="date"], select, .content_title_expanded, .content_title_unexpanded, .content_subtitle, textarea, .post-content, .post-author, hr, h4, .postFeedWrapper, .tabs, #wallAttachmentMenu, .ovk-diag, .ovk-diag-head, #ovkDraw, #ovkDraw .literally .lc-picker, .literally .lc-options.horz-toolbar, .page_wrap, .container_gray .content, .summaryBar, .groups_options, form[action="/search"] > input, .header_search_input, .header_search_inputbt, .accent-box, .page_status_popup, .messenger-app--input, .messenger-app, .crp-entry:first-of-type, .crp-list, .crp-entry, .note_footer, .page_content > div, #editor, .note_header, center[style="background: white;border: #DEDEDE solid 1px;"], .album-photo img, .mb_tabs, .mb_tab#active div, .navigation-lang .link_new, #faqhead, #faqcontent, .post-divider, .comment, .commentsTextFieldWrap, tr, td, th, #votesBalance, .paginator a.active, .paginator a:hover, .topic-list-item, #userContent blockquote, .tippy-box[data-theme~="vk"], .poll {
|
||||
border-color: #2c2640 !important;
|
||||
}
|
||||
|
||||
.tippy-box[data-theme~="vk"][data-placement^='top'] > .tippy-arrow::before, .tippy-box[data-theme~="vk"][data-placement^='bottom'] > .tippy-arrow::before {
|
||||
border-top-color: #1e1a2b;
|
||||
border-bottom-color: #1e1a2b;
|
||||
}
|
||||
|
||||
hr {
|
||||
background-color: #2c2640 !important;
|
||||
}
|
||||
|
||||
.cookies-popup {
|
||||
background: linear-gradient(#1e1a2b, #231e33);
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
.button, #activetabs, .messagebox-content-header, .accent-box, .button_search {
|
||||
background-color: #383052;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
background-color: #40375e;
|
||||
}
|
||||
|
||||
.menu_divider, .ovk-diag-action, .minilink .counter {
|
||||
background-color: #2c2640;
|
||||
}
|
||||
|
||||
#ovkDraw .literally .lc-picker, .literally .lc-options.horz-toolbar, .mb_tab#active {
|
||||
background-color: #453e5e;
|
||||
}
|
||||
|
||||
.ovk-diag-cont {
|
||||
background-color: #272e4894;
|
||||
}
|
||||
|
||||
a, .page_footer .link, #profile_link, .profile_link {
|
||||
color: #8fb9d8;
|
||||
}
|
||||
|
||||
.page_footer .link:hover, .navigation .link:hover, .navigation .edit-button:hover, #profile_link:hover, .profile_link:hover, #wallAttachmentMenu > a:hover, .crp-entry:hover, .navigation-lang .link_new:hover, .paginator a:hover, .post-share-button:hover, .post-like-button:hover {
|
||||
background-color: #272138 !important;
|
||||
}
|
||||
|
||||
.header_navigation .link a {
|
||||
color: #bcc3d0;
|
||||
}
|
||||
|
||||
.header_navigation .link a:hover, .home_button_custom {
|
||||
color: #c7cdd9;
|
||||
}
|
||||
|
||||
.navigation .link {
|
||||
color: #d9e0ee;
|
||||
}
|
||||
|
||||
.navigation .edit-button {
|
||||
background-color: #0e0b1a !important;
|
||||
color: #7b94c4 !important;
|
||||
}
|
||||
|
||||
#test-label, .msg.msg_err {
|
||||
background-color: #30161d;
|
||||
}
|
||||
|
||||
.msg.msg_succ {
|
||||
background-color: #163f13;
|
||||
}
|
||||
|
||||
h4, .content_title_expanded, .summaryBar .summary, .content_title_unexpanded {
|
||||
color: #7c94c5;
|
||||
}
|
||||
|
||||
.notes_titles small, .post-upload, .post-has-poll {
|
||||
color: #6f82a8;
|
||||
}
|
||||
|
||||
.content_title_expanded, .content_title_unexpanded, .ovk-diag, .settings_delete, center[style="background: white;border: #DEDEDE solid 1px;"], .album-photo img, #faqhead, td.e, tr.e {
|
||||
background-color: #231e33 !important;
|
||||
}
|
||||
|
||||
.content_subtitle, .postFeedWrapper, .ovk-diag-head, .container_gray, .page_status_popup, .messenger-app--input, .note_header, #faqcontent, .commentsTextFieldWrap, td.v, tr.v, #votesBalance, .expand_button, #userContent blockquote, .tippy-box[data-theme~="vk"] {
|
||||
background-color: #1e1a2b !important;
|
||||
}
|
||||
|
||||
.post-author {
|
||||
background-color: #1e1a2b;
|
||||
/* this is fix to correct the unexpected behavior of the microblog style lol */
|
||||
}
|
||||
|
||||
.content_title_expanded {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/flex_arrow_open.png");
|
||||
}
|
||||
|
||||
.content_title_unexpanded {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/flex_arrow_shut.gif");
|
||||
}
|
||||
|
||||
.ovk-video > .preview, .video-preview {
|
||||
box-shadow: inset 0 0 0 1px #231e33, inset 0 0 0 2px #1e1a2b;
|
||||
}
|
||||
|
||||
#wallAttachmentMenu, .container_gray .content, .mb_tabs {
|
||||
background-color: #120e1f;
|
||||
}
|
||||
|
||||
#wallAttachmentMenu > .header, .messenger-app--messages---message.unread, tr.h {
|
||||
background-color: #1d192b;
|
||||
}
|
||||
|
||||
.toTop {
|
||||
background-color: #272138;
|
||||
color: #c6d2e8;
|
||||
}
|
||||
|
||||
.page_yellowheader {
|
||||
color: #c6d2e8;
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/header_purple.png");
|
||||
background-color: #231f34;
|
||||
border-color: #231f34;
|
||||
}
|
||||
|
||||
.page_header {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/header.png");
|
||||
}
|
||||
|
||||
.page_custom_header {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/header_custom.png");
|
||||
}
|
||||
|
||||
.page_yellowheader span, .page_yellowheader a {
|
||||
color: #a48aff !important;
|
||||
}
|
||||
|
||||
.completeness-gauge, .poll-result-bar {
|
||||
background-color: #231e33;
|
||||
}
|
||||
|
||||
.completeness-gauge > div, .poll-result-bar-sub {
|
||||
background-color: #2c2640;
|
||||
}
|
||||
|
||||
form[action="/search"] > input, .header_search_input, textarea, input[type="text"] {
|
||||
background-color: #181826 !important;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/checkbox.png");
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/radio.png");
|
||||
}
|
||||
|
||||
.header_navigation .link {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
.heart {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/like.gif") !important;
|
||||
}
|
||||
|
||||
.pinned-mark, .post-author .pin {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/pin.png") !important;
|
||||
}
|
||||
|
||||
.repost-icon {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/published.gif") !important;
|
||||
}
|
||||
|
||||
.post-author .delete {
|
||||
background-image: url("/themepack/midnight/0.0.1.4/resource/input_clear.gif") !important;
|
||||
}
|
||||
|
||||
.user-alert {
|
||||
background-color: #41311a;
|
||||
color: #d5b88c;
|
||||
border-color: #514534;
|
||||
}
|
12
themepacks/midnight/theme.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
id: midnight
|
||||
version: "0.0.1.4"
|
||||
openvk_version: 0
|
||||
enabled: 1
|
||||
metadata:
|
||||
name:
|
||||
_: "OpenVK Midnight"
|
||||
en: "OpenVK Midnight"
|
||||
ru: "OpenVK Midnight"
|
||||
uk: "OpenVK Midnight"
|
||||
author: "Ilya Prokopenko"
|
||||
description: "A dark theme for OpenVK"
|
|
@ -7,5 +7,6 @@ metadata:
|
|||
_: "OpenVK Modern"
|
||||
en: "OpenVK Modern"
|
||||
ru: "OpenVK Modern"
|
||||
uk: "OpenVK Modern"
|
||||
author: "Mikita Wiśniewski (rudzik8)"
|
||||
description: "OpenVK theme in modern style"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# OpenVK Themepacks
|
||||
|
||||
This folder contains all themes that can be used by any user on instance.
|
||||
This folder contains all themes that can be used by any user on an instance.
|
||||
|
||||
## How do i create the theme?
|
||||
## How do I create a theme?
|
||||
|
||||
Create a directory, the name of which should contain only Latin letters and numbers, and create a file there `theme.yml`, and fill it with the following content:
|
||||
Create a directory, the name of which should contain only Latin letters and numbers, then create a file in this directory called `theme.yml`, and fill it with the following content:
|
||||
|
||||
```yaml
|
||||
id: vk2007
|
||||
|
@ -24,13 +24,13 @@ metadata:
|
|||
|
||||
`id` is the name of the folder
|
||||
|
||||
`version` - version of the theme
|
||||
`version` is the version of the theme
|
||||
|
||||
`openvk_version` - version OpenVK *(it is necessary to leave the value 0)*
|
||||
`openvk_version` is the version of OpenVK *(it is necessary to leave the value to 0)*
|
||||
|
||||
`metadata`:
|
||||
|
||||
* `name` - the name of the theme for the end user. Inside it you can leave names for different languages. `_` (underscore) - for all languages.
|
||||
* `name` - the name of the theme for the end user. Inside it you can leave names for different languages. `_` (underscore) is for all languages.
|
||||
|
||||
Next, in `stylesheet.css` you can insert any CSS code, with which you can change the elements of the site. If you need additional pictures or resources, just create a `res` folder, and access the resources via the `/themepack/{directory name}/{theme version}/resource/{resource}` path.
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ metadata:
|
|||
|
||||
Далее, в `stylesheet.css` вставляем любой CSS код, с помощью которого вы можете изменить элементы сайта. Если вам нужны дополнительные картинки или ресурсы, то для этого просто создайте папку `res`, и в CSS коде обращайтесь к ресурсам через путь `/themepack/{название директории}/{версия темы}/resource/{ресурс}`.
|
||||
|
||||
Для поддержки новогоднего насторения, которое включается автоматически с 1 декабря по 15 января, создайте файл `xmas.css` в папку `res`, и внесите вам нужные изменения.
|
||||
Для поддержки новогоднего настроения, которое включается автоматически с 1 декабря по 15 января, создайте файл `xmas.css` в папку `res`, и внесите вам нужные изменения.
|
||||
|
||||
**В конце концов, иерархия директории с темой должна выглядеть вот так:**
|
||||
|
||||
|
|