Videos: add window player (#951)

* De#910fy

* Fiksez

* newlines

---------

Co-authored-by: Dmitry Tretyakov <76806170+tretdm@users.noreply.github.com>
This commit is contained in:
lalka2018 2023-11-15 11:41:18 +03:00 committed by GitHub
parent 9fbf7f5bf5
commit 0f0d3ee950
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1208 additions and 125 deletions

156
ServiceAPI/Video.php Normal file
View file

@ -0,0 +1,156 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\{User, Post};
use openvk\Web\Models\Repositories\{Videos, Comments, Clubs};
use Chandler\MVC\Routing\Router;
class Video implements Handler
{
protected $user;
protected $videos;
protected $comments;
protected $groups;
function __construct(?User $user)
{
$this->user = $user;
$this->videos = new Videos;
$this->comments = new Comments;
$this->groups = new Clubs;
}
function getVideo(int $id, callable $resolve, callable $reject)
{
$video = $this->videos->get($id);
if(!$video || $video->isDeleted()) {
$reject(2, "Video does not exists");
}
if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user)) {
$reject(4, "Access to video denied");
}
if(!$video->getOwner()->getPrivacyPermission('videos.read', $this->user)) {
$reject(8, "Access to video denied: this user chose to hide his videos");
}
$prevVideo = NULL;
$nextVideo = NULL;
$lastVideo = $this->videos->getLastVideo($video->getOwner());
if($video->getVirtualId() - 1 != 0) {
for($i = $video->getVirtualId(); $i != 0; $i--) {
$maybeVideo = (new Videos)->getByOwnerAndVID($video->getOwner()->getId(), $i);
if(!is_null($maybeVideo) && !$maybeVideo->isDeleted() && $maybeVideo->getId() != $video->getId()) {
if(method_exists($maybeVideo, "canBeViewedBy") && !$maybeVideo->canBeViewedBy($this->user)) {
continue;
}
$prevVideo = $maybeVideo;
break;
}
}
}
if(is_null($lastVideo) || $lastVideo->getId() == $video->getId()) {
$nextVideo = NULL;
} else {
for($i = $video->getVirtualId(); $i <= $lastVideo->getVirtualId(); $i++) {
$maybeVideo = (new Videos)->getByOwnerAndVID($video->getOwner()->getId(), $i);
if(!is_null($maybeVideo) && !$maybeVideo->isDeleted() && $maybeVideo->getId() != $video->getId()) {
if(method_exists($maybeVideo, "canBeViewedBy") && !$maybeVideo->canBeViewedBy($this->user)) {
continue;
}
$nextVideo = $maybeVideo;
break;
}
}
}
$res = [
"id" => $video->getId(),
"title" => $video->getName(),
"owner" => $video->getOwner()->getId(),
"commentsCount" => $video->getCommentsCount(),
"description" => $video->getDescription(),
"type" => $video->getType(),
"name" => $video->getOwner()->getCanonicalName(),
"pretty_id" => $video->getPrettyId(),
"virtual_id" => $video->getVirtualId(),
"published" => (string)$video->getPublicationTime(),
"likes" => $video->getLikesCount(),
"has_like" => $video->hasLikeFrom($this->user),
"author" => $video->getOwner()->getCanonicalName(),
"canBeEdited" => $video->getOwner()->getId() == $this->user->getId(),
"isProcessing" => $video->getType() == 0 && $video->getURL() == "/assets/packages/static/openvk/video/rendering.mp4",
"prevVideo" => !is_null($prevVideo) ? $prevVideo->getId() : null,
"nextVideo" => !is_null($nextVideo) ? $nextVideo->getId() : null,
];
if($video->getType() == 1) {
$res["embed"] = $video->getVideoDriver()->getEmbed();
} else {
$res["url"] = $video->getURL();
}
$resolve($res);
}
function shareVideo(int $owner, int $vid, int $type, string $message, int $club, bool $signed, bool $asGroup, callable $resolve, callable $reject)
{
$video = $this->videos->getByOwnerAndVID($owner, $vid);
if(!$video || $video->isDeleted()) {
$reject(16, "Video does not exists");
}
if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user)) {
$reject(32, "Access to video denied");
}
if(!$video->getOwner()->getPrivacyPermission('videos.read', $this->user)) {
$reject(8, "Access to video denied: this user chose to hide his videos");
}
$flags = 0;
$nPost = new Post;
$nPost->setOwner($this->user->getId());
if($type == 0) {
$nPost->setWall($this->user->getId());
} else {
$club = $this->groups->get($club);
if(!$club || $club->isDeleted() || !$club->canBeModifiedBy($this->user)) {
$reject(64, "Can't do repost to this club");
}
if($asGroup)
$flags |= 0b10000000;
if($signed)
$flags |= 0b01000000;
$nPost->setWall($club->getId() * -1);
}
$nPost->setContent($message);
$nPost->setFlags($flags);
$nPost->save();
$nPost->attach($video);
$res = [
"id" => $nPost->getId(),
"pretty_id" => $nPost->getPrettyId(),
];
$resolve($res);
}
}

View file

@ -1,7 +1,12 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\{Posts as PostsRepo, Comments as CommentsRepo, Photos as PhotosRepo, Videos as VideosRepo}; use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
final class Likes extends VKAPIRequestHandler final class Likes extends VKAPIRequestHandler
{ {
@ -10,20 +15,44 @@ final class Likes extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$postable = NULL;
switch($type) { switch($type) {
case "post": case "post":
$post = (new PostsRepo)->getPostById($owner_id, $item_id); $post = (new PostsRepo)->getPostById($owner_id, $item_id);
if(is_null($post)) $postable = $post;
$this->fail(100, "One of the parameters specified was missing or invalid: object not found"); break;
case "comment":
$post->setLike(true, $this->getUser()); $comment = (new CommentsRepo)->get($item_id);
$postable = $comment;
return (object) [ break;
"likes" => $post->getLikesCount() case "video":
]; $video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $video;
break;
case "photo":
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $photo;
break;
case "note":
$note = (new NotesRepo)->getNoteById($owner_id, $item_id);
$postable = $note;
break;
default: default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
} }
if(is_null($postable) || $postable->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
if(method_exists($postable, "canBeViewedBy") && !$postable->canBeViewedBy($this->getUser() ?? NULL)) {
$this->fail(2, "Access to postable denied");
}
$postable->setLike(true, $this->getUser());
return (object) [
"likes" => $postable->getLikesCount()
];
} }
function delete(string $type, int $owner_id, int $item_id): object function delete(string $type, int $owner_id, int $item_id): object
@ -31,42 +60,98 @@ final class Likes extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$postable = NULL;
switch($type) { switch($type) {
case "post": case "post":
$post = (new PostsRepo)->getPostById($owner_id, $item_id); $post = (new PostsRepo)->getPostById($owner_id, $item_id);
if (is_null($post)) $postable = $post;
$this->fail(100, "One of the parameters specified was missing or invalid: object not found"); break;
case "comment":
$post->setLike(false, $this->getUser()); $comment = (new CommentsRepo)->get($item_id);
return (object) [ $postable = $comment;
"likes" => $post->getLikesCount() break;
]; case "video":
$video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $video;
break;
case "photo":
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $photo;
break;
case "note":
$note = (new NotesRepo)->getNoteById($owner_id, $item_id);
$postable = $note;
break;
default: default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
} }
if(is_null($postable) || $postable->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
if(method_exists($postable, "canBeViewedBy") && !$postable->canBeViewedBy($this->getUser() ?? NULL)) {
$this->fail(2, "Access to postable denied");
}
if(!is_null($postable)) {
$postable->setLike(false, $this->getUser());
return (object) [
"likes" => $postable->getLikesCount()
];
}
} }
function isLiked(int $user_id, string $type, int $owner_id, int $item_id): object function isLiked(int $user_id, string $type, int $owner_id, int $item_id): object
{ {
$this->requireUser(); $this->requireUser();
switch($type) {
case "post":
$user = (new UsersRepo)->get($user_id); $user = (new UsersRepo)->get($user_id);
if (is_null($user))
if(is_null($user) || $user->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: user not found"); $this->fail(100, "One of the parameters specified was missing or invalid: user not found");
$post = (new PostsRepo)->getPostById($owner_id, $item_id); if(method_exists($user, "canBeViewedBy") && !$user->canBeViewedBy($this->getUser())) {
if (is_null($post)) $this->fail(1984, "Access denied: you can't see this user");
$this->fail(100, "One of the parameters specified was missing or invalid: object not found"); }
return (object) [ $postable = NULL;
"liked" => (int) $post->hasLikeFrom($user), switch($type) {
"copied" => 0 # TODO: handle this case "post":
]; $post = (new PostsRepo)->getPostById($owner_id, $item_id);
$postable = $post;
break;
case "comment":
$comment = (new CommentsRepo)->get($item_id);
$postable = $comment;
break;
case "video":
$video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $video;
break;
case "photo":
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id);
$postable = $photo;
break;
case "note":
$note = (new NotesRepo)->getNoteById($owner_id, $item_id);
$postable = $note;
break;
default: default:
$this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type");
} }
if(is_null($postable) || $postable->isDeleted())
$this->fail(100, "One of the parameters specified was missing or invalid: object not found");
if(!$postable->canBeViewedBy($this->getUser())) {
$this->fail(665, "Access to postable denied");
}
return (object) [
"liked" => (int) $postable->hasLikeFrom($user),
"copied" => 0
];
} }
function getList(string $type, int $owner_id, int $item_id, bool $extended = false, int $offset = 0, int $count = 10, bool $skip_own = false) function getList(string $type, int $owner_id, int $item_id, bool $extended = false, int $offset = 0, int $count = 10, bool $skip_own = false)

View file

@ -26,7 +26,7 @@ final class Video extends VKAPIRequestHandler
$video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1])); $video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1]));
if($video) { if($video) {
$items[] = $video->getApiStructure(); $items[] = $video->getApiStructure($this->getUser());
} }
} }
@ -40,12 +40,16 @@ final class Video extends VKAPIRequestHandler
else else
$this->fail(1, "Not implemented"); $this->fail(1, "Not implemented");
if(!$user->getPrivacyPermission('videos.read', $this->getUser())) {
$this->fail(20, "Access denied: this user chose to hide his videos");
}
$videos = (new VideosRepo)->getByUser($user, $offset + 1, $count); $videos = (new VideosRepo)->getByUser($user, $offset + 1, $count);
$videosCount = (new VideosRepo)->getUserVideosCount($user); $videosCount = (new VideosRepo)->getUserVideosCount($user);
$items = []; $items = [];
foreach ($videos as $video) { foreach ($videos as $video) {
$items[] = $video->getApiStructure(); $items[] = $video->getApiStructure($this->getUser());
} }
return (object) [ return (object) [

View file

@ -57,7 +57,7 @@ final class Wall extends VKAPIRequestHandler
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) { } else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
$attachments[] = $this->getApiPoll($attachment, $this->getUser()); $attachments[] = $this->getApiPoll($attachment, $this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure(); $attachments[] = $attachment->getApiStructure($this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct(); $attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) {
@ -237,7 +237,7 @@ final class Wall extends VKAPIRequestHandler
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) { } else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
$attachments[] = $this->getApiPoll($attachment, $user); $attachments[] = $this->getApiPoll($attachment, $user);
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure(); $attachments[] = $attachment->getApiStructure($this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct(); $attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) { } else if ($attachment instanceof \openvk\Web\Models\Entities\Audio) {

View file

@ -75,7 +75,11 @@ class Comment extends Post
if($attachment->isDeleted()) if($attachment->isDeleted())
continue; continue;
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$res->attachments[] = $attachment->toVkApiStruct(); $res->attachments[] = $attachment->toVkApiStruct();
} else if($attachment instanceof \openvk\Web\Models\Entities\Video) {
$res->attachments[] = $attachment->toVkApiStruct($this->getUser());
}
} }
if($need_likes) { if($need_likes) {

View file

@ -131,11 +131,16 @@ abstract class Postable extends Attachable
"target" => $this->getRecord()->id, "target" => $this->getRecord()->id,
]; ];
if($liked) if($liked) {
if(!$this->hasLikeFrom($user)) {
DB::i()->getContext()->table("likes")->insert($searchData); DB::i()->getContext()->table("likes")->insert($searchData);
else }
} else {
if($this->hasLikeFrom($user)) {
DB::i()->getContext()->table("likes")->where($searchData)->delete(); DB::i()->getContext()->table("likes")->where($searchData)->delete();
} }
}
}
function hasLikeFrom(User $user): bool function hasLikeFrom(User $user): bool
{ {

View file

@ -115,15 +115,15 @@ class Video extends Media
return $this->getRecord()->owner; return $this->getRecord()->owner;
} }
function getApiStructure(): object function getApiStructure(?User $user = NULL): object
{ {
$fromYoutube = $this->getType() == Video::TYPE_EMBED; $fromYoutube = $this->getType() == Video::TYPE_EMBED;
return (object)[ $res = (object)[
"type" => "video", "type" => "video",
"video" => [ "video" => [
"can_comment" => 1, "can_comment" => 1,
"can_like" => 0, // we don't h-have wikes in videos "can_like" => 1, // we don't h-have wikes in videos
"can_repost" => 0, "can_repost" => 1,
"can_subscribe" => 1, "can_subscribe" => 1,
"can_add_to_faves" => 0, "can_add_to_faves" => 0,
"can_add" => 0, "can_add" => 0,
@ -155,21 +155,26 @@ class Video extends Media
"repeat" => 0, "repeat" => 0,
"type" => "video", "type" => "video",
"views" => 0, "views" => 0,
"likes" => [
"count" => 0,
"user_likes" => 0
],
"reposts" => [ "reposts" => [
"count" => 0, "count" => 0,
"user_reposted" => 0 "user_reposted" => 0
] ]
] ]
]; ];
if(!is_null($user)) {
$res->video["likes"] = [
"count" => $this->getLikesCount(),
"user_likes" => $this->hasLikeFrom($user)
];
} }
function toVkApiStruct(): object return $res;
}
function toVkApiStruct(?User $user): object
{ {
return $this->getApiStructure(); return $this->getApiStructure($user);
} }
function setLink(string $link): string function setLink(string $link): string

View file

@ -77,4 +77,11 @@ class Videos
return new Util\EntityStream("Video", $result->order("$sort")); return new Util\EntityStream("Video", $result->order("$sort"));
} }
function getLastVideo(User $user)
{
$video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->order("id DESC")->fetch();
return new Video($video);
}
} }

View file

@ -74,18 +74,18 @@ final class VideosPresenter extends OpenVKPresenter
else if(!empty($this->postParam("link"))) else if(!empty($this->postParam("link")))
$video->setLink($this->postParam("link")); $video->setLink($this->postParam("link"));
else else
$this->flashFail("err", tr("no_video"), tr("no_video_desc")); $this->flashFail("err", tr("no_video_error"), tr("no_video_description"));
} catch(\DomainException $ex) { } catch(\DomainException $ex) {
$this->flashFail("err", tr("error_occured"), tr("error_video_damaged_file")); $this->flashFail("err", tr("error_video"), tr("file_corrupted"));
} catch(ISE $ex) { } catch(ISE $ex) {
$this->flashFail("err", tr("error_occured"), tr("error_video_incorrect_link")); $this->flashFail("err", tr("error_video"), tr("link_incorrect"));
} }
$video->save(); $video->save();
$this->redirect("/video" . $video->getPrettyId()); $this->redirect("/video" . $video->getPrettyId());
} else { } else {
$this->flashFail("err", tr("error_occured"), tr("error_video_no_title")); $this->flashFail("err", tr("error_video"), tr("no_name_error"));
} }
} }
} }
@ -99,14 +99,14 @@ final class VideosPresenter extends OpenVKPresenter
if(!$video) if(!$video)
$this->notFound(); $this->notFound();
if(is_null($this->user) || $this->user->id !== $owner) if(is_null($this->user) || $this->user->id !== $owner)
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied")); $this->flashFail("err", tr("access_denied_error"), tr("access_denied_error_description"));
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name")); $video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name"));
$video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); $video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$video->save(); $video->save();
$this->flash("succ", tr("changes_saved"), tr("new_data_video")); $this->flash("succ", tr("changes_saved"), tr("changes_saved_video_comment"));
$this->redirect("/video" . $video->getPrettyId()); $this->redirect("/video" . $video->getPrettyId());
} }
@ -128,9 +128,29 @@ final class VideosPresenter extends OpenVKPresenter
$video->deleteVideo($owner, $vid); $video->deleteVideo($owner, $vid);
} }
} else { } else {
$this->flashFail("err", tr("error_deleting_video"), tr("login_please")); $this->flashFail("err", tr("cant_delete_video"), tr("cant_delete_video_comment"));
} }
$this->redirect("/videos" . $owner); $this->redirect("/videos" . $owner);
} }
function renderLike(int $owner, int $video_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->assertNoCSRF();
$video = $this->videos->getByOwnerAndVID($owner, $video_id);
if(!$video || $video->isDeleted() || $video->getOwner()->isDeleted()) $this->notFound();
if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user->identity)) {
$this->flashFail("err", tr("error"), tr("forbidden"));
}
if(!is_null($this->user)) {
$video->toggleLike($this->user->identity);
}
$this->redirect("$_SERVER[HTTP_REFERER]");
}
} }

View file

@ -13,6 +13,7 @@
<script src="/language/{php echo getLanguage()}.js" crossorigin="anonymous"></script> <script src="/language/{php echo getLanguage()}.js" crossorigin="anonymous"></script>
{script "js/node_modules/jquery/dist/jquery.min.js"} {script "js/node_modules/jquery/dist/jquery.min.js"}
{script "js/node_modules/jquery-ui/dist/jquery-ui.min.js"}
{script "js/node_modules/umbrellajs/umbrella.min.js"} {script "js/node_modules/umbrellajs/umbrella.min.js"}
{script "js/l10n.js"} {script "js/l10n.js"}
{script "js/openvk.cls.js"} {script "js/openvk.cls.js"}
@ -370,6 +371,10 @@
</p> </p>
</div> </div>
<div id="ajloader" class="loader">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</div>
{include "components/cookies.xml"} {include "components/cookies.xml"}
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"} {script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}

View file

@ -327,13 +327,13 @@
</div> </div>
<div style="padding: 5px;"> <div style="padding: 5px;">
<div class="ovk-video" n:foreach="$videos as $video"> <div class="ovk-video" n:foreach="$videos as $video">
<a href="/video{$video->getPrettyId()}" class="preview" align="center"> <a href="/video{$video->getPrettyId()}" class="preview" align="center" id="videoOpen" data-id="{$video->getId()}">
<img <img
src="{$video->getThumbnailURL()}" src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" /> style="max-width: 170px; max-height: 127px; margin: auto;" />
</a> </a>
<div> <div>
<b><a href="/video{$video->getPrettyId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br> <b><a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
<span style="font-size: 10px;">{$video->getPublicationTime()} | {_comments} ({$video->getCommentsCount()})</span> <span style="font-size: 10px;">{$video->getPublicationTime()} | {_comments} ({$video->getCommentsCount()})</span>
</div> </div>
</div> </div>

View file

@ -33,7 +33,7 @@
{/block} {/block}
{block preview} {block preview}
<div class="video-preview"> <div class="video-preview" id="videoOpen" data-id="{$x->getId()}">
<img src="{$x->getThumbnailURL()}" <img src="{$x->getThumbnailURL()}"
alt="{$x->getName()}" alt="{$x->getName()}"
style="max-width: 170px; max-height: 127px; margin: auto;" /> style="max-width: 170px; max-height: 127px; margin: auto;" />
@ -41,7 +41,7 @@
{/block} {/block}
{block name} {block name}
{$x->getName()} <span id="videoOpen" data-id="{$x->getId()}" style="color:unset;">{$x->getName()}</span>
{/block} {/block}
{block description} {block description}
@ -51,7 +51,7 @@
<span style="color: grey;">{_video_uploaded} {$x->getPublicationTime()}</span><br/> <span style="color: grey;">{_video_uploaded} {$x->getPublicationTime()}</span><br/>
<span style="color: grey;">{_video_updated} {$x->getEditTime() ?? $x->getPublicationTime()}</span> <span style="color: grey;">{_video_updated} {$x->getEditTime() ?? $x->getPublicationTime()}</span>
<p> <p>
<a href="/video{$x->getPrettyId()}">{_view_video}</a> <a href="/video{$x->getPrettyId()}" id="videoOpen" data-id="{$x->getId()}">{_view_video}</a>
{if $x->getCommentsCount() > 0}| <a href="/video{$x->getPrettyId()}#comments">{_comments} ({$x->getCommentsCount()})</a>{/if} {if $x->getCommentsCount() > 0}| <a href="/video{$x->getPrettyId()}#comments">{_comments} ({$x->getCommentsCount()})</a>{/if}
</p> </p>
{/block} {/block}

View file

@ -29,7 +29,7 @@
<hr/> <hr/>
<div style="width: 100%; min-height: 100px;"> <div style="width: 100%; min-height: 100px;">
<div style="float: left; min-height: 100px; width: 68%; margin-right: 2%;"> <div style="float: left; min-height: 100px; width: 68%; margin-right: 2%;" id="comments">
{include "../components/comments.xml", {include "../components/comments.xml",
comments => $comments, comments => $comments,
count => $cCount, count => $cCount,
@ -50,6 +50,7 @@
{$video->getPublicationTime()} {$video->getPublicationTime()}
</div> </div>
<br/> <br/>
<div>
<div n:if="isset($thisUser) && $thisUser->getId() === $user->getId()"> <div n:if="isset($thisUser) && $thisUser->getId() === $user->getId()">
<h4>{_actions}</h4> <h4>{_actions}</h4>
<a href="/video{$video->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;"> <a href="/video{$video->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">
@ -59,6 +60,10 @@
{_delete} {_delete}
</a> </a>
</div> </div>
<a href="/video{$video->getPrettyId()}" class="profile_link" id="videoOpen" data-id="{$video->getId()}" style="display:block;width:96%;">
{_watch_in_window}
</a>
</div>
{if isset($thisUser)} {if isset($thisUser)}
{if $thisUser->getId() != $video->getOwner()->getId()} {if $thisUser->getId() != $video->getOwner()->getId()}

View file

@ -10,6 +10,7 @@
</a> </a>
{/if} {/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video} {elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
{if !$attachment->isDeleted()}
{if $attachment->getType() === 0} {if $attachment->getType() === 0}
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}"> <div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
<video class="media" src="{$attachment->getURL()}"></video> <video class="media" src="{$attachment->getURL()}"></video>
@ -25,8 +26,12 @@
<div class="video-wowzer"> <div class="video-wowzer">
<img src="/assets/packages/static/openvk/img/videoico.png" /> <img src="/assets/packages/static/openvk/img/videoico.png" />
<a href="/video{$attachment->getPrettyId()}">{$attachment->getName()}</a> <a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getId()}">{$attachment->getName()}</a>
</div> </div>
{else}
<span style="color:gray;">{_video_is_deleted}</span>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll} {elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
{presenter "openvk!Poll->view", $attachment->getId()} {presenter "openvk!Poll->view", $attachment->getId()}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Note} {elseif $attachment instanceof \openvk\Web\Models\Entities\Note}

View file

@ -1,14 +1,12 @@
{block content} {block content}
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td valign="top"> <td valign="top">
<div class="video-preview"> <div class="video-preview">
<a href="/video{$video->getPrettyId()}"> <a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">
<div class="video-preview">
<img src="{$video->getThumbnailURL()}" <img src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" > style="max-width: 170px; max-height: 127px; margin: auto;" >
</div>
</a> </a>
</div> </div>
</td> </td>
@ -17,7 +15,7 @@
{include infotable, x => $dat} {include infotable, x => $dat}
{else} {else}
<a href="/video{$video->getPrettyId()}"> <a href="/video{$video->getPrettyId()}">
<b> <b id="videoOpen" data-id="{$video->getId()}">
{$video->getName()} {$video->getName()}
</b> </b>
</a> </a>
@ -28,12 +26,12 @@
<span style="color: grey;">{_video_uploaded} {$video->getPublicationTime()}</span><br/> <span style="color: grey;">{_video_uploaded} {$video->getPublicationTime()}</span><br/>
<p> <p>
<a href="/video{$video->getPrettyId()}">{_view_video}</a> <a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">{_view_video}</a>
{if $video->getCommentsCount() > 0}| <a href="/video{$video->getPrettyId()}#comments">{_comments} ({$video->getCommentsCount()})</a>{/if} {if $video->getCommentsCount() > 0}| <a href="/video{$video->getPrettyId()}#comments">{_comments} ({$video->getCommentsCount()})</a>{/if}
</p> </p>
{/ifset} {/ifset}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
{/block} {/block}

View file

@ -187,6 +187,8 @@ routes:
handler: "Videos->edit" handler: "Videos->edit"
- url: "/video{num}_{num}/remove" - url: "/video{num}_{num}/remove"
handler: "Videos->remove" handler: "Videos->remove"
- url: "/video{num}_{num}/like"
handler: "Videos->like"
- url: "/player/upload" - url: "/player/upload"
handler: "Audio->upload" handler: "Audio->upload"
- url: "/audios{num}" - url: "/audios{num}"

View file

@ -100,7 +100,7 @@ button.bsdn_playButton {
cursor: pointer; cursor: pointer;
} }
.bsdn_fullScreenButton { .bsdn_fullScreenButton, .bsdn_repeatButton {
cursor: pointer; cursor: pointer;
} }
@ -110,6 +110,12 @@ button.bsdn_playButton {
object-position: -64px 0; object-position: -64px 0;
} }
.bsdn_repeatButton.pressed > img {
background: url("/assets/packages/static/openvk/img/bsdn/repeat_hover.gif");
object-fit: none;
object-position: -64px 0;
}
.bsdn_teaserWrap { .bsdn_teaserWrap {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -215,6 +221,6 @@ time.bsdn_timeFull {
margin: 10px 0; margin: 10px 0;
} }
.bsdn_fullScreenButton > img, .bsdn_soundIcon { .bsdn_fullScreenButton > img, .bsdn_repeatButton > img, .bsdn_soundIcon {
vertical-align: middle; vertical-align: middle;
} }

View file

@ -58,3 +58,249 @@ body.dimmed > .dimmer {
.ovk-diag-action > .button { .ovk-diag-action > .button {
margin-left: 10px; margin-left: 10px;
} }
/* fullscreen player */
.ovk-fullscreen-player {
top: 9%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, 0%);
z-index: 6667;
position: absolute;
width: 823px;
min-height: 400px;
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, 0.2);
}
.top-part span {
color: #515151;
font-size: 13px;
transition: color 200ms ease-in-out;
}
.top-part .clickable:hover {
color: #ffffff;
}
.ovk-fullscreen-player .bsdn_teaserTitleBox span {
color: unset;
font-size: unset;
}
.ovk-fullscreen-player .bsdn-player {
max-width: 80%;
max-height: 350px;
}
.inner-player {
background: #000000;
min-height: 438px;
max-height: 439px;
position: relative;
padding-top: 11px;
}
.top-part-name {
font-size: 15px;
font-weight: bolder;
margin-left: 20px;
margin-top: 5px;
}
.top-part-buttons {
float: right;
margin-right: 20px;
}
.top-part-buttons span {
cursor: pointer;
user-select: none;
}
.fplayer {
text-align: center;
margin-top: 20px;
}
.top-part-bottom-buttons {
position: absolute;
margin-left: 20px;
bottom: 0;
margin-bottom: 20px;
}
.top-part-bottom-buttons span {
user-select: none;
}
.top-part .clickable {
cursor: pointer;
}
.bottom-part {
display: none;
background: white;
padding-bottom: 20px;
padding-top: 30px;
}
.left_block {
padding-left: 20px;
/*padding-top: 20px;*/
width: 75%;
float: left;
background: white;
padding-right: 6px;
max-height: 400px;
overflow-y: scroll;
}
/* Работает только в хроме, потому что в фурифоксе до сих пор нет кастомных скроллбаров лул */
.left_block::-webkit-scrollbar {
width: 0;
}
.right_block {
padding-left: 10px;
/*padding-top: 20px;*/
width: 20%;
border-left: 1px solid gray;
float: right;
}
.bottom-part span {
font-size: 13px;
}
.bottom-part .gray {
color: gray;
}
.ovk-fullscreen-dimmer {
/* спижжено у пулла с несколькими картинками там где просмотрщик фоток */
position: fixed;
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
overflow: auto;
padding-bottom: 20px;
z-index: 300;
}
.v_author {
margin-top: 20px;
}
.miniplayer {
position: absolute;
top:0;
background: rgba(54, 54, 54, 0.9);
border-radius: 3px;
min-width: 299px;
min-height: 192px;
padding-top: 3px;
z-index: 9999;
}
.miniplayer .bsdn-player {
max-height: 150px;
}
.miniplayer .fplayer {
max-width: 286px;
margin-left: 6px;
margin-top: 10px;
}
.miniplayer-actions {
float: right;
margin-right: 8px;
margin-top: 4px;
}
.miniplayer-name {
color: #8a8a8a;
font-size: 14px;
margin-left: 7px;
margin-top: -6px;
font-weight: bolder;
user-select: none;
}
.ui-draggable {
position:fixed !important;
}
.miniplayer-actions img {
max-width: 11px;
cursor: pointer;
transition: opacity 200ms ease-in-out;
opacity: 70%;
}
.miniplayer .fplayer iframe {
max-width: 260px;
max-height: 160px;
}
.miniplayer-actions img:hover {
opacity: 100%;
}
#vidComments {
margin-top: 10px;
}
.showMoreComments {
background: #eaeaea;
cursor: pointer;
text-align: center;
padding: 10px;
user-select: none;
margin-top: 10px;
}
.loader {
display: none;
position: fixed;
top: -10%;
background: rgba(26, 26, 26, 0.9);;
padding-top: 12px;
width: 91px;
height: 25px;
text-align: center;
border-radius: 1px;
margin: auto;
left: 0;
right: 0;
bottom: 0;
z-index: 5555;
}
.right-arrow, .left-arrow {
position: absolute;
cursor: pointer;
transition: all 200ms ease-in-out;
margin-left: -50px;
background: none;
height: 449px;
width: 57px;
user-select: none;
}
.right-arrow img, .left-arrow img {
user-select: none;
opacity: 5%;
transition: all 200ms ease-in-out;
}
.right-arrow:hover, .left-arrow:hover {
background: rgba(0, 0, 0, 0.5);
}
.right-arrow img:hover, .left-arrow img:hover {
opacity: 50%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
Web/static/img/left_arr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -22,6 +22,31 @@ function trim(string) {
return newStr; return newStr;
} }
function trimNum(string, num) {
var newStr = string.substring(0, num);
if(newStr.length !== string.length)
newStr += "…";
return newStr;
}
function handleUpload(id) {
console.warn("блять...");
u("#post-buttons" + id + " .postFileSel").not("#" + this.id).each(input => input.value = null);
var indicator = u("#post-buttons" + id + " .post-upload");
var file = this.files[0];
if(typeof file === "undefined") {
indicator.attr("style", "display: none;");
} else {
u("span", indicator.nodes[0]).text(trim(file.name) + " (" + humanFileSize(file.size, false) + ")");
indicator.attr("style", "display: block;");
}
document.querySelector("#post-buttons" + id + " #wallAttachmentMenu").classList.add("hidden");
}
function initGraffiti(id) { function initGraffiti(id) {
let canvas = null; let canvas = null;
let msgbox = MessageBox(tr("draw_graffiti"), "<div id='ovkDraw'></div>", [tr("save"), tr("cancel")], [function() { let msgbox = MessageBox(tr("draw_graffiti"), "<div id='ovkDraw'></div>", [tr("save"), tr("cancel")], [function() {
@ -52,6 +77,7 @@ function initGraffiti(id) {
}); });
} }
$(document).on("click", ".post-like-button", function(e) {
function fastUploadImage(textareaId, file) { function fastUploadImage(textareaId, file) {
// uploading images // uploading images
@ -493,6 +519,395 @@ async function showArticle(note_id) {
u("body").addClass("article"); u("body").addClass("article");
} }
// Оконный плеер
$(document).on("click", "#videoOpen", async (e) => {
e.preventDefault()
document.getElementById("ajloader").style.display = "block"
if(document.querySelector(".ovk-fullscreen-dimmer") != null) {
u(".ovk-fullscreen-dimmer").remove()
}
let target = e.currentTarget
let videoId = target.dataset.id
let videoObj = null;
try {
videoObj = await API.Video.getVideo(Number(videoId))
} catch(e) {
console.error(e)
document.getElementById("ajloader").style.display = "none"
MessageBox(tr("error"), tr("video_access_denied"), [tr("cancel")], [
function() {
Function.noop
}]);
return 0;
}
document.querySelector("html").style.overflowY = "hidden"
let player = null;
if(target.dataset.dontload == null) {
document.querySelectorAll("video").forEach(vid => vid.pause())
if(videoObj.type == 0) {
if(videoObj.isProcessing) {
player = `
<span class="gray">${tr("video_processing")}</span>
`
} else {
player = `
<div class="bsdn media" data-name="${videoObj.title}" data-author="${videoObj.name}">
<video class="media" src="${videoObj.url}"></video>
</div>`
}
} else {
player = videoObj.embed
}
} else {
player = ``
}
let dialog = u(
`
<div class="ovk-fullscreen-dimmer">
<div class="ovk-fullscreen-player">
${videoObj.prevVideo != null ?
`<div class="right-arrow" id="videoOpen" data-id="${videoObj.prevVideo}">
<img src="/assets/packages/static/openvk/img/right_arr.png" draggable="false">
</div>` : ""}
${videoObj.nextVideo != null ? `
<div class="left-arrow" id="videoOpen" data-id="${videoObj.nextVideo}" style="margin-left: 820px;">
<img src="/assets/packages/static/openvk/img/left_arr.png" draggable="false">
</div>` : ""}
<div class="inner-player">
<div class="top-part">
<span class="top-part-name">${escapeHtml(videoObj.title)}</span>
<div class="top-part-buttons">
<span class="clickable" id="minimizePlayer" data-name="${videoObj.title}" data-id="${videoObj.id}">${tr("hide_player")}</span>
<span>|</span>
<span class="clickable" id="closeFplayer">${tr("close_player")}</span>
</div>
<div class="top-part-player-subdiv">
${target.dataset.dontload == null ?`
<div class="fplayer">
${player}
</div>` : ""}
</div>
<div class="top-part-bottom-buttons">
<span class="clickable" id="showComments" data-id="${videoObj.id}" data-owner="${videoObj.owner}" data-pid="${videoObj.pretty_id}">${tr("show_comments")}</span>
<span>|</span>
<span class="clickable" id="gotopage" data-id="/video${videoObj.pretty_id}">${tr("to_page")}</span>
${ videoObj.type == 0 && videoObj.isProcessing == false ? `<span>|</span>
<a class="clickable" href="${videoObj.url}" download><span class="clickable">${tr("download_video")}</span></a>` : ""}
</div>
</div>
</div>
<div class="bottom-part">
<div class="left_block">
<div class="description" style="margin-bottom: 5px;">
<span>${videoObj.description != null ? escapeHtml(videoObj.description) : "(" + tr("no_description") + ")"}</span>
</div>
<div class="bottom-part-info" style="display: flex;">
<span class="gray">${tr("added")} ${videoObj.published}&nbsp;</span><span>|</span>
<div class="like_wrap" style="float:unset;">
<a href="/video${videoObj.pretty_id}/like?hash=${encodeURIComponent(u("meta[name=csrf]").attr("value"))}" class="post-like-button" data-liked="${videoObj.has_like ? 1 : 0}" data-likes="${videoObj.likes}">
<div class="heart" id="${videoObj.has_like ? "liked" : ""}"></div>
<span class="likeCnt" style="margin-top: -2px;">${videoObj.likes > 0 ? videoObj.likes : ""}</span>
</a>
</div>
</div>
<div id="vidComments"></div>
</div>
<div class="right_block">
<div class="views">
<!--prosmoters are not implemented((-->
<span class="gray">${tr("x_views", 0)}</span>
</div>
<div class="v_author">
<span class="gray">${tr("video_author")}:</span><br>
<a href="/id${videoObj.owner}"><span style="color:unset;">${videoObj.author}</span></a>
</div>
<div class="actions" style="margin-top: 10px;margin-left: -3px;">
${videoObj.canBeEdited ? `
<a href="/video${videoObj.pretty_id}/edit" class="profile_link" style="display:block;width:96%;font-size: 13px;">
${tr("edit")}
</a>
<a href="/video${videoObj.pretty_id}/remove" class="profile_link" style="display:block;width:96%;font-size: 13px;">
${tr("delete")}
</a>`
: ""}
<a id="shareVideo" class="profile_link" id="shareVideo" data-owner="${videoObj.owner}" data-vid="${videoObj.virtual_id}" style="display:block;width:96%;font-size: 13px;">
${tr("share")}
</a>
</div>
</div>
</div>
</div>
</div>`);
u("body").addClass("dimmed").append(dialog);
if(target.dataset.dontload != null) {
let oldPlayer = document.querySelector(".miniplayer-video .fplayer")
let newPlayer = document.querySelector(".top-part-player-subdiv")
document.querySelector(".top-part-player-subdiv")
newPlayer.append(oldPlayer)
}
if(videoObj.type == 0 && videoObj.isProcessing == false) {
bsdnInitElement(document.querySelector(".fplayer .bsdn"))
}
document.getElementById("ajloader").style.display = "none"
u(".miniplayer").remove()
})
$(document).on("click", "#closeFplayer", async (e) => {
u(".ovk-fullscreen-dimmer").remove();
document.querySelector("html").style.overflowY = "scroll"
u("body").removeClass("dimmed")
})
$(document).on("click", "#minimizePlayer", async (e) => {
let targ = e.currentTarget
let player = document.querySelector(".fplayer")
let dialog = u(`
<div class="miniplayer">
<span class="miniplayer-name">${escapeHtml(trimNum(targ.dataset.name, 26))}</span>
<div class="miniplayer-actions">
<img src="/assets/packages/static/openvk/img/miniplayer_open.png" id="videoOpen" data-dontload="true" data-id="${targ.dataset.id}">
<img src="/assets/packages/static/openvk/img/miniplayer_close.png" id="closeMiniplayer">
</div>
<div class="miniplayer-video">
</div>
</div>
`);
u("body").append(dialog);
$('.miniplayer').draggable({cursor: "grabbing", containment: "body", cancel: ".miniplayer-video"});
let newPlayer = document.querySelector(".miniplayer-video")
newPlayer.append(player)
document.querySelector(".miniplayer").style.top = window.scrollY;
document.querySelector("#closeFplayer").click()
})
$(document).on("click", "#closeMiniplayer", async (e) => {
u(".miniplayer").remove()
})
$(document).on("mouseup", "#gotopage", async (e) => {
if(e.originalEvent.which === 1) {
location.href = e.currentTarget.dataset.id
} else if (e.originalEvent.which === 2) {
window.open(e.currentTarget.dataset.id, '_blank')
}
})
$(document).keydown(function(e) {
if(document.querySelector(".top-part-player-subdiv .bsdn") != null && document.activeElement.tagName == "BODY") {
let video = document.querySelector(".top-part-player-subdiv video")
switch(e.keyCode) {
// Пробел вроде
case 32:
document.querySelector(".top-part-player-subdiv .bsdn_teaserButton").click()
break
// Стрелка вниз, уменьшение громкости
case 40:
oldVolume = video.volume
if(oldVolume - 0.1 > 0) {
video.volume = oldVolume - 0.1
} else {
video.volume = 0
}
break;
// Стрелка вверх, повышение громкости
case 38:
oldVolume = video.volume
if(oldVolume + 0.1 < 1) {
video.volume = oldVolume + 0.1
} else {
video.volume = 1
}
break
// стрелка влево, отступ на 2 секунды назад
case 37:
oldTime = video.currentTime
video.currentTime = oldTime - 2
break
// стрелка вправо, отступ на 2 секунды вперёд
case 39:
oldTime = document.querySelector(".top-part-player-subdiv video").currentTime
document.querySelector(".top-part-player-subdiv video").currentTime = oldTime + 2
break
}
}
});
$(document).keyup(function(e) {
if(document.querySelector(".top-part-player-subdiv .bsdn") != null && document.activeElement.tagName == "BODY") {
let video = document.querySelector(".top-part-player-subdiv video")
switch(e.keyCode) {
// Escape, закрытие плеера
case 27:
document.querySelector("#closeFplayer").click()
break
// Блять, я перепутал лево и право, пиздец я долбаёб конечно
// Ну короче стрелка влево
case 65:
if(document.querySelector(".right-arrow") != null) {
document.querySelector(".right-arrow").click()
} else {
console.info("No left arrow bro")
}
break
// Фуллскрин
case 70:
document.querySelector(".top-part-player-subdiv .bsdn_fullScreenButton").click()
break
// стрелка вправо
case 68:
if(document.querySelector(".left-arrow") != null) {
document.querySelector(".left-arrow").click()
} else {
console.info("No right arrow bro")
}
break;
// S: Показать инфо о видео (не комментарии)
case 83:
document.querySelector(".top-part-player-subdiv #showComments").click()
break
// Мут (M)
case 77:
document.querySelector(".top-part-player-subdiv .bsdn_soundIcon").click()
break;
// Escape, выход из плеера
case 192:
document.querySelector(".top-part-buttons #minimizePlayer").click()
break
// Бля не помню сори
case 75:
document.querySelector(".top-part-player-subdiv .bsdn_playButton").click()
break
// Home, переход в начало видосика
case 36:
video.currentTime = 0
break
// End, переход в конец видосика
case 35:
video.currentTime = video.duration
break;
}
}
});
$(document).on("click", "#showComments", async (e) => {
if(document.querySelector(".bottom-part").style.display == "none" || document.querySelector(".bottom-part").style.display == "") {
if(document.getElementById("vidComments").innerHTML == "") {
let xhr = new XMLHttpRequest
xhr.open("GET", "/video"+e.currentTarget.dataset.pid)
xhr.onloadstart = () => {
document.getElementById("vidComments").innerHTML = `<img src="/assets/packages/static/openvk/img/loading_mini.gif">`
}
xhr.timeout = 10000;
xhr.onload = () => {
let parser = new DOMParser();
let body = parser.parseFromString(xhr.responseText, "text/html");
let comms = body.getElementById("comments")
let commsHTML = comms.innerHTML.replace("expand_wall_textarea(11)", "expand_wall_textarea(999)")
.replace("wall-post-input11", "wall-post-input999")
.replace("post-buttons11", "post-buttons999")
.replace("toggleMenu(11)", "toggleMenu(999)")
.replace("toggleMenu(11)", "toggleMenu(999)")
.replace(/ons11/g, "ons999")
document.getElementById("vidComments").innerHTML = commsHTML
}
xhr.onerror = () => {
document.getElementById("vidComments").innerHTML = `<span>${tr("comments_load_timeout")}</span>`
}
xhr.ontimeout = () => {
document.getElementById("vidComments").innerHTML = `<span>${tr("comments_load_timeout")}</span>`
};
xhr.send()
}
document.querySelector(".bottom-part").style.display = "flex"
e.currentTarget.innerHTML = tr("close_comments")
} else {
document.querySelector(".bottom-part").style.display = "none"
e.currentTarget.innerHTML = tr("show_comments")
}
})
$(document).on("click", "#shareVideo", async (e) => {
let owner_id = e.currentTarget.dataset.owner
let virtual_id = e.currentTarget.dataset.vid
let body = `
<b>${tr('auditory')}:</b> <br/>
<input type="radio" name="type" onchange="signs.setAttribute('hidden', 'hidden');document.getElementById('groupId').setAttribute('hidden', 'hidden')" value="0" checked>${tr("in_wall")}<br/>
<input type="radio" name="type" onchange="signs.removeAttribute('hidden');document.getElementById('groupId').removeAttribute('hidden')" value="1" id="group">${tr("in_group")}<br/>
<select style="width:50%;" id="groupId" name="groupId" hidden>
</select><br/>
<b>${tr('your_comment')}:</b>
<textarea id='uRepostMsgInput'></textarea>
<div id="signs" hidden>
<label><input onchange="signed.checked ? signed.checked = false : null" type="checkbox" id="asgroup" name="asGroup">${tr('post_as_group')}</label><br>
<label><input onchange="asgroup.checked = true" type="checkbox" id="signed" name="signed">${tr('add_signature')}</label>
</div>
`
MessageBox(tr("share_video"), body, [tr("share"), tr("cancel")], [
(async function() {
let type = $('input[name=type]:checked').val()
let club = document.getElementById("groupId").value
let asGroup = document.getElementById("asgroup").checked
let signed = document.getElementById("signed").checked
let repost = null;
try {
repost = await API.Video.shareVideo(Number(owner_id), Number(virtual_id), Number(type), uRepostMsgInput.value, Number(club), signed, asGroup)
NewNotification(tr('information_-1'), tr('shared_succ_video'), null, () => {window.location.href = "/wall" + repost.pretty_id});
} catch(e) {
console.log("tudu")
}
}), (function() {
Function.noop
})], false);
try {
clubs = await API.Groups.getWriteableClubs();
for(const el of clubs) {
document.getElementById("groupId").insertAdjacentHTML("beforeend", `<option value="${el.id}">${escapeHtml(el.name)}</option>`)
}
} catch(rejection) {
console.error(rejection)
document.getElementById("group").setAttribute("disabled", "disabled")
}
$(document).on("click", "#videoAttachment", async (e) => { $(document).on("click", "#videoAttachment", async (e) => {
e.preventDefault() e.preventDefault()

View file

@ -1,6 +1,6 @@
Function.noop = () => {}; Function.noop = () => {};
function MessageBox(title, body, buttons, callbacks) { function MessageBox(title, body, buttons, callbacks, removeDimmedOnExit = true) {
if(u(".ovk-diag-cont").length > 0) return false; if(u(".ovk-diag-cont").length > 0) return false;
document.querySelector("html").style.overflowY = "hidden" document.querySelector("html").style.overflowY = "hidden"
@ -20,9 +20,9 @@ function MessageBox(title, body, buttons, callbacks) {
button.on("click", function(e) { button.on("click", function(e) {
let __closeDialog = () => { let __closeDialog = () => {
if(document.querySelector(".ovk-photo-view-dimmer") == null) {
if(removeDimmedOnExit) {
u("body").removeClass("dimmed"); u("body").removeClass("dimmed");
document.querySelector("html").style.overflowY = "scroll"
} }
u(".ovk-diag-cont").remove(); u(".ovk-diag-cont").remove();

View file

@ -68,6 +68,12 @@ function _bsdnTpl(name, author) {
</div> </div>
</div> </div>
<div>
<div class="bsdn_repeatButton">
<img src="/assets/packages/static/openvk/img/bsdn/repeat.gif" />
</div>
</div>
<div> <div>
<div class="bsdn_fullScreenButton"> <div class="bsdn_fullScreenButton">
<img src="/assets/packages/static/openvk/img/bsdn/fullscreen.gif" /> <img src="/assets/packages/static/openvk/img/bsdn/fullscreen.gif" />
@ -252,6 +258,26 @@ function _bsdnEventListenerFactory(el, v) {
] ]
}, },
".bsdn_repeatButton": {
click: [
() => {
if(!v.loop) {
v.loop = true
el.querySelector(".bsdn_repeatButton").classList.add("pressed")
if(v.currentTime == v.duration) {
v.currentTime = 0
v.play()
}
} else {
v.loop = false
el.querySelector(".bsdn_repeatButton").classList.remove("pressed")
}
}
]
},
".bsdn_fullScreenButton": { ".bsdn_fullScreenButton": {
click: [ click: [
() => { () => {

View file

@ -1972,6 +1972,50 @@
"mobile_user_info_hide" = "Hide"; "mobile_user_info_hide" = "Hide";
"mobile_user_info_show_details" = "Show details"; "mobile_user_info_show_details" = "Show details";
/* Fullscreen player */
"hide_player" = "Minimize";
"close_player" = "Close";
"show_comments" = "Show info";
"close_comments" = "Hide info";
"to_page" = "Go to page";
"download_video" = "Download";
"added" = "Added";
"x_views" = "$1 views";
"video_author" = "Video's author";
"video_delete" = "Delete";
"no_description" = "no description";
"show_more_comments" = "Show more comments";
"video_processing" = "Video is succefully uploaded and now is processed.";
"video_access_denied" = "Access to video denied";
"open_page_to_read_comms" = "To read the comments, open <a href=\"$1\">page</a>.";
"no_video_error" = "No videofile";
"no_video_description" = "Select file or specify link.";
"error_video" = "An error has occurred";
"file_corrupted" = "File is corrupted or does not have video.";
"link_incorrect" = "Maybe, the link is wrong.";
"no_name_error" = "Video can't be published without name";
"access_denied_error" = "Access denied";
"access_denied_error_description" = "You are not allowed to edit this resource";
"changes_saved_video_comment" = "Updated data will appear on the video page";
"cant_delete_video" = "Failed to delete video";
"cant_delete_video_comment" = "You are not logged in.";
"change_video" = "Change video";
"video_is_deleted" = "Video was deleted.";
"share_video" = "Share video";
"shared_succ_video" = "Video will appear at your wall. Click on this notification to move to post.";
"watch_in_window" = "Watch in window";
"comments_load_timeout" = "The instance may have fallen";
"my" = "My"; "my" = "My";
"enter_a_name_or_artist" = "Enter a name or artist..."; "enter_a_name_or_artist" = "Enter a name or artist...";

View file

@ -1858,6 +1858,50 @@
"mobile_user_info_hide" = "Скрыть"; "mobile_user_info_hide" = "Скрыть";
"mobile_user_info_show_details" = "Показать подробнее"; "mobile_user_info_show_details" = "Показать подробнее";
/* Fullscreen player */
"hide_player" = "Скрыть";
"close_player" = "Закрыть";
"show_comments" = "Показать информацию";
"close_comments" = "Скрыть информацию";
"to_page" = "Перейти на страницу";
"download_video" = "Скачать";
"added" = "Добавлено";
"x_views" = "$1 просмотров";
"video_author" = "Автор видео";
"video_delete" = "Удалить";
"no_description" = "описания нет";
"show_more_comments" = "Показать больше комментариев";
"video_processing" = "Видео успешно загружено и на данный момент обрабатывается.";
"video_access_denied" = "Доступ к видео запрещён";
"open_page_to_read_comms" = "Для чтения комментариев откройте <a href=\"$1\">страницу</a>.";
"no_video_error" = "Нету видеозаписи";
"no_video_description" = "Выберите файл или укажите ссылку.";
"error_video" = "Произошла ошибка";
"file_corrupted" = "Файл повреждён или не содержит видео.";
"link_incorrect" = "Возможно, ссылка некорректна.";
"no_name_error" = "Видео не может быть опубликовано без названия";
"access_denied_error" = "Ошибка доступа";
"access_denied_error_description" = "Вы не имеете права редактировать этот ресурс";
"changes_saved_video_comment" = "Обновлённые данные появятся на странице с видео";
"cant_delete_video" = "Не удалось удалить видео";
"cant_delete_video_comment" = "Вы не вошли в аккаунт.";
"change_video" = "Изменить видеозапись";
"video_is_deleted" = "Видео удалено.";
"share_video" = "Поделиться видеороликом";
"shared_succ_video" = "Видео появится на вашей стене. Нажмите на уведомление, чтобы перейти к записи.";
"watch_in_window" = "Смотреть в окне";
"comments_load_timeout" = "Возможно, инстанция упала.";
"my" = "Мои"; "my" = "Мои";
"enter_a_name_or_artist" = "Введите название или автора..."; "enter_a_name_or_artist" = "Введите название или автора...";
@ -1897,3 +1941,4 @@
"roll_back" = "откатить"; "roll_back" = "откатить";
"roll_backed" = "откачено"; "roll_backed" = "откачено";