Merge branch 'master' into infinityscroll2

This commit is contained in:
mrilyew 2024-11-17 13:18:40 +03:00 committed by GitHub
commit e3b5d60f89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 5045 additions and 3952 deletions

View file

@ -1,14 +1,6 @@
name: Build images
on:
push:
# Publish `master` as Docker `latest` image.
branches:
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
on: [push, pull_request]
env:
BASE_IMAGE_NAME: openvk
@ -17,24 +9,21 @@ env:
DB_VERSION: "10.9"
jobs:
build:
runs-on: ubuntu-latest
buildbase:
name: Build base images
strategy:
matrix:
arch: ['x86_64']
platform: [amd64, arm64]
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v3
with:
lfs: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Change repository string to lowercase
id: repositorystring
@ -42,29 +31,114 @@ jobs:
with:
string: ${{ github.repository }}
- name: Base image meta
id: basemeta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/${{env.BASE_IMAGE_NAME}}
labels: |
org.opencontainers.image.documentation=https://github.com/OpenVK/openvk/blob/master/install/automated/docker/Readme.md
tags: |
type=sha
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Log into registry
if: github.event_name != 'pull_request'
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build base image
run: |
IMAGE_ID=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$BASE_IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
[ "$VERSION" == "master" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
uses: docker/build-push-action@v6
with:
platforms: linux/${{matrix.platform}}
file: install/automated/docker/openvk.Dockerfile
tags: ${{ steps.basemeta.outputs.tags }}
labels: ${{ steps.basemeta.outputs.labels }}
push: ${{ github.event_name != 'pull_request' }}
build-args: |
GITREPO=${{ steps.repositorystring.outputs.lowercase }}
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_ID:$VERSION . --push -f install/automated/docker/openvk.Dockerfile --build-arg GITREPO=${{ steps.repositorystring.outputs.lowercase }}
builddb:
name: Build DB images
strategy:
matrix:
platform: [amd64, arm64]
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Change repository string to lowercase
id: repositorystring
uses: Entepotenz/change-string-case-action-min-dependencies@v1.1.0
with:
string: ${{ github.repository }}
- name: MariaDB primary meta
id: db-primarymeta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/${{env.DB_IMAGE_NAME}}
labels: |
org.opencontainers.image.title=OpenVK MariaDB (Primary)
org.opencontainers.image.description=OpenVK's image for MariaDB for primary database.
org.opencontainers.image.documentation=https://github.com/OpenVK/openvk/blob/master/install/automated/docker/Readme.md
tags: |
type=sha,prefix=${{env.DB_VERSION}}-primary-sha-
type=ref,event=branch,prefix=${{env.DB_VERSION}}-primary-
type=ref,event=pr,prefix=${{env.DB_VERSION}}-primary-pr-
type=ref,event=tag,prefix=${{env.DB_VERSION}}-primary-
type=raw,value=${{env.DB_VERSION}}-primary,enable={{is_default_branch}}
- name: MariaDB event meta
id: db-eventmeta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/${{env.DB_IMAGE_NAME}}
labels: |
org.opencontainers.image.title=OpenVK MariaDB (EventDB)
org.opencontainers.image.description=OpenVK's image for MariaDB for event database.
org.opencontainers.image.documentation=https://github.com/OpenVK/openvk/blob/master/install/automated/docker/Readme.md
tags: |
type=sha,prefix=${{env.DB_VERSION}}-eventdb-sha-
type=ref,event=branch,prefix=${{env.DB_VERSION}}-eventdb-
type=ref,event=pr,prefix=${{env.DB_VERSION}}-eventdb-pr-
type=ref,event=tag,prefix=${{env.DB_VERSION}}-eventdb-
type=raw,value=${{env.DB_VERSION}}-eventdb,enable={{is_default_branch}}
- name: Log into registry
if: github.event_name != 'pull_request'
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build MariaDB primary image
run: |
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$DB_IMAGE_NAME:$DB_VERSION-primary
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-primary.Dockerfile --build-arg VERSION=$DB_VERSION
uses: docker/build-push-action@v6
with:
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/${{matrix.platform}}
file: install/automated/docker/mariadb-primary.Dockerfile
tags: ${{ steps.db-primarymeta.outputs.tags }}
labels: ${{ steps.db-primarymeta.outputs.labels }}
build-args: |
VERSION=${{env.DB_VERSION}}
- name: Build MariaDB event image
run: |
IMAGE_NAME=ghcr.io/${{ steps.repositorystring.outputs.lowercase }}/$EVENT_IMAGE_NAME:$DB_VERSION-eventdb
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . --push -f install/automated/docker/mariadb-eventdb.Dockerfile --build-arg VERSION=$DB_VERSION
uses: docker/build-push-action@v6
with:
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/${{matrix.platform}}
file: install/automated/docker/mariadb-eventdb.Dockerfile
tags: ${{ steps.db-eventmeta.outputs.tags }}
labels: ${{ steps.db-eventmeta.outputs.labels }}
build-args: |
VERSION=${{env.DB_VERSION}}

View file

@ -4,7 +4,7 @@ _[Русский](README_RU.md)_
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VKontakte. Code provided here is not stable yet.
VKontakte belongs to Pavel Durov and VK Group.
VKontakte belongs to VK (formerly Mail.ru Group).
To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://ovk.to/support?act=new) (you will need an OpenVK account for this).
@ -36,7 +36,7 @@ Here is our minimum hardware recommendation:
### Installation procedure
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
1. Install PHP 7.4, web-server, Composer, Node.js, NPM and [Chandler](https://github.com/openvk/chandler)
* PHP 8 is still being tested; the functionality of the engine on this version of PHP is not yet guaranteed.
@ -65,7 +65,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
7. Copy `openvk-example.yml` to `openvk.yml` and change options to your liking
8. Run `composer install` in OpenVK directory
9. Run `composer install` in commitcaptcha directory
10. Move to `Web/static/js` and execute `yarn install`
10. Move to `Web/static/js` and execute `npm install`
11. Set `openvk` as your root app in `chandler.yml`
Once you are done, you can login as a system administrator on the network itself (no registration required):

View file

@ -4,7 +4,7 @@ _[English](README.md)_
**OpenVK** — это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент, представленный здесь исходный код проекта пока не является стабильным.
ВКонтакте принадлежит Павлу Дурову и VK Group.
ВКонтакте принадлежит VK (в прошлом Mail.ru Group).
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://ovk.to/support?act=new) (для этого вам понадобится учетная запись OpenVK).
@ -28,7 +28,7 @@ _[English](README.md)_
### Процедура установки
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, Yarn и [Chandler](https://github.com/openvk/chandler)
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, NPM и [Chandler](https://github.com/openvk/chandler)
* PHP 8 пока ещё тестируется, работоспособность движка на этой версии PHP пока не гарантируется.
@ -57,7 +57,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
7. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры под свои нужды
8. Запустите `composer install` в директории OpenVK
9. Запустите `composer install` в директории commitcaptcha
10. Перейдите в `Web/static/js` и выполните `yarn install`
10. Перейдите в `Web/static/js` и выполните `npm install`
11. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется):

View file

@ -1,92 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Photos as PhotosRepo, Albums, Clubs};
class Photos implements Handler
{
protected $user;
protected $photos;
function __construct(?User $user)
{
$this->user = $user;
$this->photos = new PhotosRepo;
}
function getPhotos(int $page = 1, int $album = 0, callable $resolve, callable $reject)
{
if($album == 0) {
$photos = $this->photos->getEveryUserPhoto($this->user, $page, 24);
$count = $this->photos->getUserPhotosCount($this->user);
} else {
$album = (new Albums)->get($album);
if(!$album || $album->isDeleted())
$reject(55, "Invalid .");
if($album->getOwner() instanceof User) {
if($album->getOwner()->getId() != $this->user->getId())
$reject(555, "Access to album denied");
} else {
if(!$album->getOwner()->canBeModifiedBy($this->user))
$reject(555, "Access to album denied");
}
$photos = $album->getPhotos($page, 24);
$count = $album->size();
}
$arr = [
"count" => $count,
"items" => [],
];
foreach($photos as $photo) {
$res = json_decode(json_encode($photo->toVkApiStruct()), true);
$arr["items"][] = $res;
}
$resolve($arr);
}
function getAlbums(int $club, callable $resolve, callable $reject)
{
$albumsRepo = (new Albums);
$count = $albumsRepo->getUserAlbumsCount($this->user);
$albums = $albumsRepo->getUserAlbums($this->user, 1, $count);
$arr = [
"count" => $count,
"items" => [],
];
foreach($albums as $album) {
$res = ["id" => $album->getId(), "name" => $album->getName()];
$arr["items"][] = $res;
}
if($club > 0) {
$cluber = (new Clubs)->get($club);
if(!$cluber || !$cluber->canBeModifiedBy($this->user))
$reject(1337, "Invalid (club), or you can't modify him");
$clubCount = (new Albums)->getClubAlbumsCount($cluber);
$clubAlbums = (new Albums)->getClubAlbums($cluber, 1, $clubCount);
foreach($clubAlbums as $albumr) {
$res = ["id" => $albumr->getId(), "name" => $albumr->getName()];
$arr["items"][] = $res;
}
$arr["count"] = $arr["count"] + $clubCount;
}
$resolve($arr);
}
}

View file

@ -1,156 +0,0 @@
<?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

@ -80,67 +80,4 @@ class Wall implements Handler
$resolve($post->getId());
}
function getMyNotes(callable $resolve, callable $reject)
{
$count = $this->notes->getUserNotesCount($this->user);
$myNotes = $this->notes->getUserNotes($this->user, 1, $count);
$arr = [
"count" => $count,
"closed" => $this->user->getPrivacySetting("notes.read"),
"items" => [],
];
foreach($myNotes as $note) {
$arr["items"][] = [
"id" => $note->getId(),
"name" => ovk_proc_strtr($note->getName(), 30),
#"preview" => $note->getPreview()
];
}
$resolve($arr);
}
function getVideos(int $page = 1, callable $resolve, callable $reject)
{
$videos = $this->videos->getByUser($this->user, $page, 8);
$count = $this->videos->getUserVideosCount($this->user);
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct($this->user)), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
function searchVideos(int $page = 1, string $query, callable $resolve, callable $reject)
{
$dbc = $this->videos->find($query);
$videos = $dbc->page($page, 8);
$count = $dbc->size();
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct($this->user)), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
}

View file

@ -11,6 +11,12 @@ final class Groups extends VKAPIRequestHandler
{
$this->requireUser();
# InfoApp fix
if($filter == "admin" && ($user_id != 0 && $user_id != $this->getUser()->getId())) {
$this->fail(15, 'Access denied: filter admin is available only for current user');
}
$clbs = [];
if($user_id == 0) {
foreach($this->getUser()->getClubs($offset, $filter == "admin", $count, true) as $club)
$clbs[] = $club;

View file

@ -32,6 +32,7 @@ final class Newsfeed extends VKAPIRequestHandler
->select("id")
->where("wall IN (?)", $ids)
->where("deleted", 0)
->where("suggested", 0)
->where("id < (?)", empty($start_from) ? PHP_INT_MAX : $start_from)
->where("? <= created", empty($start_time) ? 0 : $start_time)
->where("? >= created", empty($end_time) ? PHP_INT_MAX : $end_time)
@ -56,6 +57,15 @@ final class Newsfeed extends VKAPIRequestHandler
if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0";
if($return_banned == 0) {
$ignored_sources_ids = $this->getUser()->getIgnoredSources(0, OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50, true);
if(sizeof($ignored_sources_ids) > 0) {
$imploded_ids = implode("', '", $ignored_sources_ids);
$queryBase .= " AND `posts`.`wall` NOT IN ('$imploded_ids')";
}
}
$start_from = empty($start_from) ? PHP_INT_MAX : $start_from;
$start_time = empty($start_time) ? 0 : $start_time;
@ -74,4 +84,152 @@ final class Newsfeed extends VKAPIRequestHandler
return $response;
}
function getByType(string $feed_type = 'top', string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $return_banned = 0)
{
$this->requireUser();
switch($feed_type) {
case 'top':
return $this->getGlobal($fields, $start_from, $start_time, $end_time, $offset, $count, $extended, $return_banned);
break;
default:
return $this->get($fields, $start_from, $start_time, $end_time, $offset, $count, $extended);
break;
}
}
function getBanned(int $extended = 0, string $fields = "", string $name_case = "nom", int $merge = 0): object
{
$this->requireUser();
$offset = 0;
$count = OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50;
$banned = $this->getUser()->getIgnoredSources($offset, $count, ($extended != 1));
$return_object = (object) [
'groups' => [],
'members' => [],
];
if($extended == 0) {
foreach($banned as $ban) {
if($ban > 0)
$return_object->members[] = $ban;
else
$return_object->groups[] = $ban;
}
} else {
if($merge == 1) {
$return_object = (object) [
'count' => sizeof($banned),
'items' => [],
];
foreach($banned as $ban) {
$return_object->items[] = $ban->toVkApiStruct($this->getUser(), $fields);
}
} else {
$return_object = (object) [
'groups' => [],
'profiles' => [],
];
foreach($banned as $ban) {
if($ban->getRealId() > 0)
$return_object->profiles[] = $ban->toVkApiStruct($this->getUser(), $fields);
else
$return_object->groups[] = $ban->toVkApiStruct($this->getUser(), $fields);
}
}
}
return $return_object;
}
function addBan(string $user_ids = "", string $group_ids = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
# Formatting input ids
if(!empty($user_ids)) {
$user_ids = array_map(function($el) {
return (int)$el;
}, explode(',', $user_ids));
$user_ids = array_unique($user_ids);
} else
$user_ids = [];
if(!empty($group_ids)) {
$group_ids = array_map(function($el) {
return abs((int)$el) * -1;
}, explode(',', $group_ids));
$group_ids = array_unique($group_ids);
} else
$group_ids = [];
$ids = array_merge($user_ids, $group_ids);
if(sizeof($ids) < 1)
return 0;
if(sizeof($ids) > 10)
$this->fail(-10, "Limit of 'ids' is 10");
$config_limit = OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50;
$user_ignores = $this->getUser()->getIgnoredSourcesCount();
if(($user_ignores + sizeof($ids)) > $config_limit) {
$this->fail(-50, "Ignoring limit exceeded");
}
$entities = get_entities($ids);
$successes = 0;
foreach($entities as $entity) {
if(!$entity || $entity->getRealId() === $this->getUser()->getRealId() || $entity->isHideFromGlobalFeedEnabled() || $entity->isIgnoredBy($this->getUser())) continue;
$entity->addIgnore($this->getUser());
$successes += 1;
}
return 1;
}
function deleteBan(string $user_ids = "", string $group_ids = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if(!empty($user_ids)) {
$user_ids = array_map(function($el) {
return (int)$el;
}, explode(',', $user_ids));
$user_ids = array_unique($user_ids);
} else
$user_ids = [];
if(!empty($group_ids)) {
$group_ids = array_map(function($el) {
return abs((int)$el) * -1;
}, explode(',', $group_ids));
$group_ids = array_unique($group_ids);
} else
$group_ids = [];
$ids = array_merge($user_ids, $group_ids);
if(sizeof($ids) < 1)
return 0;
if(sizeof($ids) > 10)
$this->fail(-10, "Limit of ids is 10");
$entities = get_entities($ids);
$successes = 0;
foreach($entities as $entity) {
if(!$entity || $entity->getRealId() === $this->getUser()->getRealId() || !$entity->isIgnoredBy($this->getUser())) continue;
$entity->removeIgnore($this->getUser());
$successes += 1;
}
return 1;
}
}

View file

@ -427,7 +427,7 @@ final class Photos extends VKAPIRequestHandler
$this->fail(15, "Access denied");
$photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset);
$res["count"] = sizeof($photos);
$res["count"] = $album->size();
foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue;
@ -638,15 +638,15 @@ final class Photos extends VKAPIRequestHandler
$this->fail(4, "This method doesn't works with clubs");
$user = (new UsersRepo)->get($owner_id);
if(!$user)
$this->fail(4, "Invalid user");
if(!$user->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his albums.");
$photos = array_slice(iterator_to_array((new PhotosRepo)->getEveryUserPhoto($user, 1, $count + $offset)), $offset);
$photos = (new PhotosRepo)->getEveryUserPhoto($user, $offset, $count);
$res = [
"count" => (new PhotosRepo)->getUserPhotosCount($user),
"items" => [],
];

View file

@ -11,24 +11,55 @@ use openvk\Web\Models\Repositories\Comments as CommentsRepo;
final class Video extends VKAPIRequestHandler
{
function get(int $owner_id, string $videos = "", int $offset = 0, int $count = 30, int $extended = 0): object
function get(int $owner_id = 0, string $videos = "", string $fields = "", int $offset = 0, int $count = 30, int $extended = 0): object
{
$this->requireUser();
if(!empty($videos)) {
$vids = explode(',', $videos);
foreach($vids as $vid)
{
$profiles = [];
$groups = [];
foreach($vids as $vid) {
$id = explode("_", $vid);
$items = [];
$video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1]));
if($video) {
$items[] = $video->getApiStructure($this->getUser());
if($video && !$video->isDeleted()) {
$out_video = $video->getApiStructure($this->getUser())->video;
$items[] = $out_video;
if($out_video['owner_id']) {
if($out_video['owner_id'] > 0)
$profiles[] = $out_video['owner_id'];
else
$groups[] = abs($out_video['owner_id']);
}
}
}
if($extended == 1) {
$profiles = array_unique($profiles);
$groups = array_unique($groups);
$profilesFormatted = [];
$groupsFormatted = [];
foreach($profiles as $prof) {
$profile = (new UsersRepo)->get($prof);
$profilesFormatted[] = $profile->toVkApiStruct($this->getUser(), $fields);
}
foreach($groups as $gr) {
$group = (new ClubsRepo)->get($gr);
$groupsFormatted[] = $group->toVkApiStruct($this->getUser(), $fields);
}
return (object) [
"count" => sizeof($items),
"items" => $items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
}
return (object) [
"count" => count($items),
@ -46,12 +77,46 @@ final class Video extends VKAPIRequestHandler
if(!$user->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his videos.");
$videos = (new VideosRepo)->getByUser($user, $offset + 1, $count);
$videos = (new VideosRepo)->getByUserLimit($user, $offset, $count);
$videosCount = (new VideosRepo)->getUserVideosCount($user);
$items = [];
foreach ($videos as $video) {
$items[] = $video->getApiStructure($this->getUser());
$profiles = [];
$groups = [];
foreach($videos as $video) {
$video = $video->getApiStructure($this->getUser())->video;
$items[] = $video;
if($video['owner_id']) {
if($video['owner_id'] > 0)
$profiles[] = $video['owner_id'];
else
$groups[] = abs($video['owner_id']);
}
}
if($extended == 1) {
$profiles = array_unique($profiles);
$groups = array_unique($groups);
$profilesFormatted = [];
$groupsFormatted = [];
foreach($profiles as $prof) {
$profile = (new UsersRepo)->get($prof);
$profilesFormatted[] = $profile->toVkApiStruct($this->getUser(), $fields);
}
foreach($groups as $gr) {
$group = (new ClubsRepo)->get($gr);
$groupsFormatted[] = $group->toVkApiStruct($this->getUser(), $fields);
}
return (object) [
"count" => $videosCount,
"items" => $items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
}
return (object) [

View file

@ -284,12 +284,16 @@ final class Wall extends VKAPIRequestHandler
foreach($psts as $pst) {
$id = explode("_", $pst);
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]));
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]), true);
if($post && !$post->isDeleted()) {
if(!$post->canBeViewedBy($this->getUser()))
continue;
if($post->getSuggestionType() != 0 && !$post->canBeEditedBy($this->getUser())) {
continue;
}
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments = [];
$repost = []; // чел высрал семь сигарет 😳 помянем 🕯
@ -473,7 +477,7 @@ final class Wall extends VKAPIRequestHandler
];
}
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object
function post(string $owner_id, string $message = "", string $copyright = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object
{
$this->requireUser();
$this->willExecuteWriteAction();
@ -551,7 +555,17 @@ final class Wall extends VKAPIRequestHandler
if($signed == 1)
$flags |= 0b01000000;
if(empty($message) && empty($attachments))
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'poll', 'audio']);
$final_attachments = [];
$should_be_suggested = $owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2;
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if((empty($message) && (empty($attachments) || sizeof($final_attachments) < 1)))
$this->fail(100, "Required parameter 'message' missing.");
try {
@ -569,7 +583,7 @@ final class Wall extends VKAPIRequestHandler
} catch(\Throwable) {}
}
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2)
if($should_be_suggested)
$post->setSuggested(1);
$post->save();
@ -577,134 +591,70 @@ final class Wall extends VKAPIRequestHandler
$this->fail(100, "One of the parameters specified was missing or invalid");
}
# TODO use parseAttachments
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
# Аттачи такого вида: [тип][id владельца]_[id вложения]
# Пример: photo1_1
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Too many attachments");
preg_match_all("/poll/m", $attachments, $matches, PREG_SET_ORDER, 0);
if(sizeof($matches) > 1)
$this->fail(85, "Too many polls");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
elseif(str_contains($attac, "note"))
$attachmentType = "note";
elseif(str_contains($attac, "poll"))
$attachmentType = "poll";
elseif(str_contains($attac, "audio"))
$attachmentType = "audio";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Invalid photo");
if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(43, "Access to photo denied");
$post->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(43, "Access to video denied");
$post->attach($attacc);
} elseif($attachmentType == "note") {
$attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Note does not exist");
if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(11, "Access to note denied");
$post->attach($attacc);
} elseif($attachmentType == "poll") {
$attacc = (new PollsRepo)->get($attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Poll does not exist");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this poll");
$post->attach($attacc);
} elseif($attachmentType == "audio") {
$attacc = (new AudiosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Audio does not exist");
$post->attach($attacc);
}
}
foreach($final_attachments as $attachment) {
$post->attach($attachment);
}
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2) {
$suggsCount = (new PostsRepo)->getSuggestedPostsCount($wallOwner->getId());
if($suggsCount % 10 == 0) {
$managers = $wallOwner->getManagers();
$owner = $wallOwner->getOwner();
(new NewSuggestedPostsNotification($owner, $wallOwner))->emit();
foreach($managers as $manager) {
(new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit();
}
}
return (object)["post_id" => "on_view"];
}
return (object)["post_id" => $post->getVirtualId()];
}
function repost(string $object, string $message = "", int $group_id = 0) {
function repost(string $object, string $message = "", string $attachments = "", int $group_id = 0, int $as_group = 0, int $signed = 0) {
$this->requireUser();
$this->willExecuteWriteAction();
$postArray;
if(preg_match('/wall((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0)
if(preg_match('/(wall|video|photo)((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0)
$this->fail(100, "One of the parameters specified was missing or invalid: object is incorrect");
$post = (new PostsRepo)->getPostById((int) $postArray[1], (int) $postArray[2]);
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
if(!$post->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
$repost_entity = NULL;
$repost_type = $postArray[1];
switch($repost_type) {
default:
case 'wall':
$repost_entity = (new PostsRepo)->getPostById((int) $postArray[2], (int) $postArray[3]);
break;
case 'photo':
$repost_entity = (new PhotosRepo)->getByOwnerAndVID((int) $postArray[2], (int) $postArray[3]);
break;
case 'video':
$repost_entity = (new VideosRepo)->getByOwnerAndVID((int) $postArray[2], (int) $postArray[3]);
break;
}
if(!$repost_entity || $repost_entity->isDeleted() || !$repost_entity->canBeViewedBy($this->getUser())) $this->fail(100, "One of the parameters specified was missing or invalid");
$nPost = new Post;
$nPost->setOwner($this->user->getId());
if($group_id > 0) {
$club = (new ClubsRepo)->get($group_id);
$club = (new ClubsRepo)->get($group_id);
if(!$club)
$this->fail(42, "Invalid group");
if(!$club->canBeModifiedBy($this->user))
$this->fail(16, "Access to group denied");
$nPost->setWall($group_id * -1);
$nPost->setWall($club->getRealId());
$flags = 0;
if($as_group === 1 || $signed === 1)
$flags |= 0b10000000;
if($signed === 1)
$flags |= 0b01000000;
$nPost->setFlags($flags);
} else {
$nPost->setWall($this->user->getId());
}
@ -712,16 +662,27 @@ final class Wall extends VKAPIRequestHandler
$nPost->setContent($message);
$nPost->setApi_Source_Name($this->getPlatform());
$nPost->save();
$nPost->attach($post);
if($post->getOwner(false)->getId() !== $this->user->getId() && !($post->getOwner() instanceof Club))
(new RepostNotification($post->getOwner(false), $post, $this->user))->emit();
$nPost->attach($repost_entity);
foreach($parsed_attachments as $attachment) {
$nPost->attach($attachment);
}
if($repost_type == 'wall' && $repost_entity->getOwner(false)->getId() !== $this->user->getId() && !($repost_entity->getOwner() instanceof Club))
(new RepostNotification($repost_entity->getOwner(false), $repost_entity, $this->user))->emit();
$repost_count = 1;
if($repost_type == 'wall') {
$repost_count = $repost_entity->getRepostCount();
}
return (object) [
"success" => 1, // 👍
"post_id" => $nPost->getVirtualId(),
"reposts_count" => $post->getRepostCount(),
"likes_count" => $post->getLikesCount()
"pretty_id" => $nPost->getPrettyId(),
"reposts_count" => $repost_count,
"likes_count" => $repost_entity->getLikesCount()
];
}
@ -767,7 +728,7 @@ final class Wall extends VKAPIRequestHandler
"date" => $comment->getPublicationTime()->timestamp(),
"text" => $comment->getText(false),
"post_id" => $post->getVirtualId(),
"owner_id" => $post->isPostedOnBehalfOfGroup() ? $post->getOwner()->getId() * -1 : $post->getOwner()->getId(),
"owner_id" => method_exists($post, 'isPostedOnBehalfOfGroup') && $post->isPostedOnBehalfOfGroup() ? $post->getOwner()->getId() * -1 : $post->getOwner()->getId(),
"parents_stack" => [],
"attachments" => $attachments,
"thread" => [
@ -833,6 +794,13 @@ final class Wall extends VKAPIRequestHandler
foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
} elseif($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure();
} elseif($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = [
'type' => 'note',
'note' => $attachment->toVkApiStruct()
];
} elseif($attachment instanceof \openvk\Web\Models\Entities\Audio) {
$attachments[] = [
"type" => "audio",
@ -847,7 +815,7 @@ final class Wall extends VKAPIRequestHandler
"date" => $comment->getPublicationTime()->timestamp(),
"text" => $comment->getText(false),
"post_id" => $comment->getTarget()->getVirtualId(),
"owner_id" => $comment->getTarget()->isPostedOnBehalfOfGroup() ? $comment->getTarget()->getOwner()->getId() * -1 : $comment->getTarget()->getOwner()->getId(),
"owner_id" => method_exists($comment->getTarget(), 'isPostedOnBehalfOfGroup') && $comment->getTarget()->isPostedOnBehalfOfGroup() ? $comment->getTarget()->getOwner()->getId() * -1 : $comment->getTarget()->getOwner()->getId(),
"parents_stack" => [],
"attachments" => $attachments,
"likes" => [
@ -899,7 +867,16 @@ final class Wall extends VKAPIRequestHandler
if($post->getTargetWall() < 0)
$club = (new ClubsRepo)->get(abs($post->getTargetWall()));
if(empty($message) && empty($attachments)) {
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if((empty($message) && (empty($attachments) || sizeof($final_attachments) < 1))) {
$this->fail(100, "Required parameter 'message' missing.");
}
@ -920,55 +897,8 @@ final class Wall extends VKAPIRequestHandler
$this->fail(1, "ошибка про то что коммент большой слишком");
}
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
elseif(str_contains($attac, "audio"))
$attachmentType = "audio";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(11, "Access to photo denied");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(11, "Access to video denied");
$comment->attach($attacc);
} elseif($attachmentType == "audio") {
$attacc = (new AudiosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Audio does not exist");
$comment->attach($attacc);
}
}
foreach($final_attachments as $attachment) {
$comment->attach($attachment);
}
if($post->getOwner()->getId() !== $this->user->getId())
@ -1019,11 +949,24 @@ final class Wall extends VKAPIRequestHandler
}
}
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "", string $copyright = NULL) {
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "", string $copyright = NULL, int $explicit = -1, int $from_group = 0, int $signed = 0) {
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio', 'poll']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if(empty($message) && sizeof($final_attachments) < 1) {
$this->fail(-66, "Post will be empty, don't saving.");
}
$post = (new PostsRepo)->getPostById($owner_id, $post_id, true);
if(!$post || $post->isDeleted())
$this->fail(102, "Invalid post");
@ -1031,36 +974,43 @@ final class Wall extends VKAPIRequestHandler
if(!$post->canBeEditedBy($this->getUser()))
$this->fail(7, "Access to editing denied");
if(!empty($message))
if(!empty($message) || (empty($message) && sizeof($final_attachments) > 0))
$post->setContent($message);
$post->setEdited(time());
if(!is_null($copyright) && !empty($copyright)) {
try {
$post->setSource($copyright);
} catch(\Throwable) {}
if($copyright == 'remove') {
$post->resetSource();
} else {
try {
$post->setSource($copyright);
} catch(\Throwable) {}
}
}
if($explicit != -1) {
$post->setNsfw($explicit == 1);
}
$wallOwner = ($owner_id > 0 ? (new UsersRepo)->get($owner_id) : (new ClubsRepo)->get($owner_id * -1));
$flags = 0;
if($from_group == 1 && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->getUser()))
$flags |= 0b10000000;
/*if($signed == 1)
$flags |= 0b01000000;*/
$post->setFlags($flags);
$post->save(true);
# todo добавить такое в веб версию
if(!empty($attachments)) {
$attachs = parseAttachments($attachments);
$newAttachmentsCount = sizeof($attachs);
if($attachments == 'remove' || sizeof($final_attachments) > 0) {
foreach($post->getChildren() as $att) {
if(!($att instanceof Post)) {
$post->detach($att);
}
}
$postsAttachments = iterator_to_array($post->getChildren());
if(sizeof($postsAttachments) >= 10)
$this->fail(15, "Post have too many attachments");
if(($newAttachmentsCount + sizeof($postsAttachments)) > 10)
$this->fail(158, "Post will have too many attachments");
foreach($attachs as $attach) {
if($attach && !$attach->isDeleted())
$post->attach($attach);
else
$this->fail(52, "One of the attachments is invalid");
foreach($final_attachments as $attachment) {
$post->attach($attachment);
}
}
@ -1072,8 +1022,16 @@ final class Wall extends VKAPIRequestHandler
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if(empty($message) && empty($attachments))
if(empty($message) && sizeof($final_attachments) < 1)
$this->fail(100, "Required parameter 'message' missing.");
if(!$comment || $comment->isDeleted())
@ -1082,29 +1040,16 @@ final class Wall extends VKAPIRequestHandler
if(!$comment->canBeEditedBy($this->getUser()))
$this->fail(15, "Access to editing comment denied");
if(!empty($message))
if(!empty($message) || (empty($message) && sizeof($final_attachments) > 0))
$comment->setContent($message);
$comment->setEdited(time());
$comment->save(true);
if(!empty($attachments)) {
$attachs = parseAttachments($attachments);
$newAttachmentsCount = sizeof($attachs);
$postsAttachments = iterator_to_array($comment->getChildren());
if(sizeof($postsAttachments) >= 10)
$this->fail(15, "Post have too many attachments");
if(($newAttachmentsCount + sizeof($postsAttachments)) > 10)
$this->fail(158, "Post will have too many attachments");
foreach($attachs as $attach) {
if($attach && !$attach->isDeleted())
$comment->attach($attach);
else
$this->fail(52, "One of the attachments is invalid");
if(sizeof($final_attachments) > 0) {
$comment->unwire();
foreach($final_attachments as $attachment) {
$comment->attach($attachment);
}
}

View file

@ -87,6 +87,7 @@ class Album extends MediaCollection
$res = (object) [];
$res->id = $this->getPrettyId();
$res->vid = $this->getId();
$res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0;
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();

View file

@ -42,10 +42,11 @@ class Club extends RowModel
return iterator_to_array($avPhotos)[0] ?? NULL;
}
function getAvatarUrl(string $size = "miniscule"): string
function getAvatarUrl(string $size = "miniscule", $avPhoto = NULL): string
{
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
$avPhoto = $this->getAvatarPhoto();
if(!$avPhoto)
$avPhoto = $this->getAvatarPhoto();
return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size);
}
@ -443,30 +444,57 @@ class Club extends RowModel
$res->id = $this->getId();
$res->name = $this->getName();
$res->screen_name = $this->getShortCode();
$res->is_closed = 0;
$res->screen_name = $this->getShortCode() ?? "club".$this->getId();
$res->is_closed = false;
$res->type = 'group';
$res->is_member = $user ? (int)$this->getSubscriptionStatus($user) : 0;
$res->deactivated = NULL;
$res->is_admin = $user && $this->canBeModifiedBy($user);
$res->can_access_closed = true;
if($user && $this->canBeModifiedBy($user)) {
$res->admin_level = 3;
if(!is_array($fields))
$fields = explode(',', $fields);
$avatar_photo = $this->getAvatarPhoto();
foreach($fields as $field) {
switch($field) {
case 'verified':
$res->verified = (int)$this->isVerified();
break;
case 'site':
$res->site = $this->getWebsite();
break;
case 'description':
$res->description = $this->getDescription();
break;
case 'background':
$res->background = $this->getBackDropPictureURLs();
break;
case 'photo_50':
$res->photo_50 = $this->getAvatarUrl('miniscule', $avatar_photo);
break;
case 'photo_100':
$res->photo_100 = $this->getAvatarUrl('tiny', $avatar_photo);
break;
case 'photo_200':
$res->photo_200 = $this->getAvatarUrl('normal', $avatar_photo);
break;
case 'photo_max':
$res->photo_max = $this->getAvatarUrl('original', $avatar_photo);
break;
case 'members_count':
$res->members_count = $this->getFollowersCount();
break;
case 'real_id':
$res->real_id = $this->getRealId();
break;
}
}
$res->is_member = $user && $this->getSubscriptionStatus($user) ? 1 : 0;
$res->type = "group";
$res->photo_50 = $this->getAvatarUrl("miniscule");
$res->photo_100 = $this->getAvatarUrl("tiny");
$res->photo_200 = $this->getAvatarUrl("normal");
$res->can_create_topic = $user && $this->canBeModifiedBy($user) ? 1 : ($this->isEveryoneCanCreateTopics() ? 1 : 0);
$res->can_post = $user && $this->canBeModifiedBy($user) ? 1 : ($this->canPost() ? 1 : 0);
return $res;
}
use Traits\TBackDrops;
use Traits\TSubscribable;
use Traits\TAudioStatuses;
use Traits\TIgnorable;
}

View file

@ -46,8 +46,11 @@ class Comment extends Post
return parent::getOwner($honourFlags, $real);
}
function canBeDeletedBy(User $user): bool
function canBeDeletedBy(User $user = NULL): bool
{
if(!$user)
return false;
return $this->getOwner()->getId() == $user->getId() ||
$this->getTarget()->getOwner()->getId() == $user->getId() ||
$this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) ||

View file

@ -308,7 +308,7 @@ class Photo extends Media
$res->date = $res->created = $this->getPublicationTime()->timestamp();
if($photo_sizes) {
$res->sizes = $this->getVkApiSizes();
$res->sizes = array_values($this->getVkApiSizes());
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");

View file

@ -103,6 +103,11 @@ class Post extends Postable
$this->stateChanges("source", $source);
}
function resetSource()
{
$this->stateChanges("source", NULL);
}
function getVkApiCopyright(): object
{
return (object)[
@ -260,16 +265,22 @@ class Post extends Postable
$this->save();
}
function canBePinnedBy(User $user): bool
function canBePinnedBy(User $user = NULL): bool
{
if(!$user)
return false;
if($this->getTargetWall() < 0)
return (new Clubs)->get(abs($this->getTargetWall()))->canBeModifiedBy($user);
return $this->getTargetWall() === $user->getId();
}
function canBeDeletedBy(User $user): bool
function canBeDeletedBy(User $user = NULL): bool
{
if(!$user)
return false;
if($this->getTargetWall() < 0 && !$this->getWallOwner()->canBeModifiedBy($user) && $this->getWallOwner()->getWallType() != 1 && $this->getSuggestionType() == 0)
return false;

View file

@ -134,8 +134,13 @@ class Report extends RowModel
function getContentName(): string
{
if (method_exists($this->getContentObject(), "getCanonicalName"))
return $this->getContentObject()->getCanonicalName();
$content_object = $this->getContentObject();
if(!$content_object) {
return 'unknown';
}
if (method_exists($content_object, "getCanonicalName"))
return $content_object->getCanonicalName();
return $this->getContentType() . " #" . $this->getContentId();
}

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Entities\{Attachable, Photo};
use openvk\Web\Models\Entities\{Attachable, Photo, Video};
use openvk\Web\Util\Makima\Makima;
use Chandler\Database\DatabaseConnection;
@ -36,10 +36,10 @@ trait TAttachmentHost
if($h < 0)
$h = $w;
$children = $this->getChildren();
$children = iterator_to_array($this->getChildren());
$skipped = $photos = $result = [];
foreach($children as $child) {
if($child instanceof Photo) {
if($child instanceof Photo || $child instanceof Video && $child->getDimensions()) {
$photos[] = $child;
continue;
}

View file

@ -0,0 +1,55 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\User;
trait TIgnorable
{
function isIgnoredBy(User $user = NULL): bool
{
if(!$user)
return false;
$ctx = DatabaseConnection::i()->getContext();
$data = [
"owner" => $user->getId(),
"source" => $this->getRealId(),
];
$sub = $ctx->table("ignored_sources")->where($data);
return $sub->count() > 0;
}
function addIgnore(User $for_user): bool
{
DatabaseConnection::i()->getContext()->table("ignored_sources")->insert([
"owner" => $for_user->getId(),
"source" => $this->getRealId(),
]);
return true;
}
function removeIgnore(User $for_user): bool
{
DatabaseConnection::i()->getContext()->table("ignored_sources")->where([
"owner" => $for_user->getId(),
"source" => $this->getRealId(),
])->delete();
return true;
}
function toggleIgnore(User $for_user): bool
{
if($this->isIgnoredBy($for_user)) {
$this->removeIgnore($for_user);
return false;
} else {
$this->addIgnore($for_user);
return true;
}
}
}

View file

@ -112,7 +112,7 @@ class User extends RowModel
return "/id" . $this->getId();
}
function getAvatarUrl(string $size = "miniscule"): string
function getAvatarUrl(string $size = "miniscule", $avPhoto = NULL): string
{
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
@ -121,7 +121,9 @@ class User extends RowModel
else if($this->isBanned())
return "$serverUrl/assets/packages/static/openvk/img/banned.jpg";
$avPhoto = $this->getAvatarPhoto();
if(!$avPhoto)
$avPhoto = $this->getAvatarPhoto();
if(is_null($avPhoto))
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
else
@ -1344,11 +1346,6 @@ class User extends RowModel
$res->first_name = $this->getFirstName();
$res->last_name = $this->getLastName();
$res->deactivated = $this->isDeactivated();
$res->photo_50 = $this->getAvatarURL();
$res->photo_100 = $this->getAvatarURL("tiny");
$res->photo_200 = $this->getAvatarURL("normal");
$res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL;
$res->is_closed = $this->isClosed();
if(!is_null($user))
@ -1357,17 +1354,60 @@ class User extends RowModel
if(!is_array($fields))
$fields = explode(',', $fields);
$avatar_photo = $this->getAvatarPhoto();
foreach($fields as $field) {
switch($field) {
case 'is_dead':
$res->is_dead = $user->isDead();
$res->is_dead = $this->isDead();
break;
case 'verified':
$res->verified = (int)$this->isVerified();
break;
case 'sex':
$res->sex = $this->isFemale() ? 1 : ($this->isNeutral() ? 0 : 2);
break;
case 'photo_50':
$res->photo_50 = $this->getAvatarUrl('miniscule', $avatar_photo);
break;
case 'photo_100':
$res->photo_100 = $this->getAvatarUrl('tiny', $avatar_photo);
break;
case 'photo_200':
$res->photo_200 = $this->getAvatarUrl('normal', $avatar_photo);
break;
case 'photo_max':
$res->photo_max = $this->getAvatarUrl('original', $avatar_photo);
break;
case 'photo_id':
$res->photo_id = $avatar_photo ? $avatar_photo->getPrettyId() : NULL;
break;
case 'background':
$res->background = $this->getBackDropPictureURLs();
break;
case 'reg_date':
$res->reg_date = $this->getRegistrationTime()->timestamp();
break;
case 'nickname':
$res->nickname = $this->getPseudo();
break;
case 'rating':
$res->rating = $this->getRating();
break;
case 'status':
$res->status = $this->getStatus();
break;
case 'screen_name':
$res->screen_name = $this->getShortCode() ?? "id".$this->getId();
break;
case 'real_id':
$res->real_id = $this->getRealId();
break;
}
}
return $res;
}
function getAudiosCollectionSize()
{
return (new \openvk\Web\Models\Repositories\Audios)->getUserCollectionSize($this);
@ -1407,7 +1447,40 @@ class User extends RowModel
return $returnArr;
}
function getIgnoredSources(int $offset = 0, int $limit = 10, bool $onlyIds = false)
{
$sources = DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId())->limit($limit, $offset)->order('id DESC');
$output_array = [];
foreach($sources as $source) {
if($onlyIds) {
$output_array[] = (int)$source->source;
} else {
$ignored_source_model = NULL;
$ignored_source_id = (int)$source->source;
if($ignored_source_id > 0)
$ignored_source_model = (new Users)->get($ignored_source_id);
else
$ignored_source_model = (new Clubs)->get(abs($ignored_source_id));
if(!$ignored_source_model)
continue;
$output_array[] = $ignored_source_model;
}
}
return $output_array;
}
function getIgnoredSourcesCount()
{
return DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId())->count();
}
use Traits\TBackDrops;
use Traits\TSubscribable;
use Traits\TAudioStatuses;
use Traits\TIgnorable;
}

View file

@ -34,9 +34,23 @@ class Video extends Media
if(sizeof($durations[1]) === 0)
throw new \DomainException("$filename does not contain any meaningful video streams");
foreach($durations[1] as $duration)
if(floatval($duration) < 1.0)
$length = 0;
foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0)
throw new \DomainException("$filename does not contain any meaningful video streams");
else
$length = max($length, $duration);
}
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
preg_match('%width=([0-9\.]++)%', $streams, $width);
preg_match('%height=([0-9\.]++)%', $streams, $height);
if(!empty($width) && !empty($height)) {
$this->stateChanges("width", $width[1]);
$this->stateChanges("height", $height[1]);
}
try {
if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
@ -118,6 +132,7 @@ class Video extends Media
function getApiStructure(?User $user = NULL): object
{
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
$dimensions = $this->getDimensions();
$res = (object)[
"type" => "video",
"video" => [
@ -130,7 +145,7 @@ class Video extends Media
"comments" => $this->getCommentsCount(),
"date" => $this->getPublicationTime()->timestamp(),
"description" => $this->getDescription(),
"duration" => 0, // я хуй знает как получить длину видео
"duration" => $this->getLength(),
"image" => [
[
"url" => $this->getThumbnailURL(),
@ -139,8 +154,8 @@ class Video extends Media
"with_padding" => 1
]
],
"width" => 640,
"height" => 480,
"width" => $dimensions ? $dimensions[0] : 640,
"height" => $dimensions ? $dimensions[1] : 480,
"id" => $this->getVirtualId(),
"owner_id" => $this->getOwner()->getId(),
"user_id" => $this->getOwner()->getId(),
@ -155,6 +170,7 @@ class Video extends Media
"repeat" => 0,
"type" => "video",
"views" => 0,
"is_processed" => $this->isProcessed(),
"reposts" => [
"count" => 0,
"user_reposted" => 0
@ -224,6 +240,74 @@ class Video extends Media
return $video;
}
function fillDimensions()
{
$hash = $this->getRecord()->hash;
$path = $this->pathFromHash($hash);
if(!file_exists($path)) {
$this->stateChanges("width", 0);
$this->stateChanges("height", 0);
$this->stateChanges("length", 0);
$this->save();
return false;
}
$streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
$durations = [];
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
$length = 0;
foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0)
continue;
else
$length = max($length, $duration);
}
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
preg_match('%width=([0-9\.]++)%', $streams, $width);
preg_match('%height=([0-9\.]++)%', $streams, $height);
if(!empty($width) && !empty($height)) {
$this->stateChanges("width", $width[1]);
$this->stateChanges("height", $height[1]);
}
$this->save();
return true;
}
function getDimensions()
{
if($this->getType() == Video::TYPE_EMBED) return [320, 180];
$width = $this->getRecord()->width;
$height = $this->getRecord()->height;
if(!$width) return NULL;
return $width != 0 ? [$width, $height] : NULL;
}
function getLength()
{
return $this->getRecord()->length;
}
function getFormattedLength(): string
{
$len = $this->getLength();
if(!$len) return "00:00";
$mins = floor($len / 60);
$secs = $len - ($mins * 60);
return (
str_pad((string) $mins, 2, "0", STR_PAD_LEFT)
. ":" .
str_pad((string) $secs, 2, "0", STR_PAD_LEFT)
);
}
function canBeViewedBy(?User $user = NULL): bool
{
@ -248,7 +332,7 @@ class Video extends Media
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->description = $this->getDescription();
$res->duration = "22";
$res->duration = $this->getLength();
$res->link = "/video".$this->getOwner()->getId()."_".$this->getVirtualId();
$res->image = $this->getThumbnailURL();
$res->date = $this->getPublicationTime()->timestamp();

View file

@ -43,6 +43,18 @@ class Clubs
return $this->toClub($this->clubs->get($id));
}
function getByIds(array $ids = []): array
{
$clubs = $this->clubs->select('*')->where('id IN (?)', $ids);
$clubs_array = [];
foreach($clubs as $club) {
$clubs_array[] = $this->toClub($club);
}
return $clubs_array;
}
function find(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false], int $page = 1, ?int $perPage = NULL): \Traversable
{
$query = "%$query%";

View file

@ -33,7 +33,7 @@ class Photos
return new Photo($photo);
}
function getEveryUserPhoto(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
function getEveryUserPhoto(User $user, int $offset = 0, int $limit = 10): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
$photos = $this->photos->where([
@ -41,7 +41,7 @@ class Photos
"deleted" => 0
])->order("id DESC");
foreach($photos->page($page, $perPage) as $photo) {
foreach($photos->limit($limit, $offset) as $photo) {
yield new Photo($photo);
}
}

View file

@ -28,6 +28,18 @@ class Users
{
return $this->toUser($this->users->get($id));
}
function getByIds(array $ids = []): array
{
$users = $this->users->select('*')->where('id IN (?)', $ids);
$users_array = [];
foreach($users as $user) {
$users_array[] = $this->toUser($user);
}
return $users_array;
}
function getByShortURL(string $url): ?User
{

View file

@ -39,6 +39,13 @@ class Videos
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video)
yield new Video($video);
}
function getByUserLimit(User $user, int $offset = 0, int $limit = 10): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->limit($limit, $offset)->order("created DESC") as $video)
yield new Video($video);
}
function getUserVideosCount(User $user): int
@ -49,7 +56,7 @@ class Videos
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0);
$order_str = 'id';
switch($order['type']) {

View file

@ -33,6 +33,7 @@ final class AudioPresenter extends OpenVKPresenter
function renderList(?int $owner = NULL, ?string $mode = "list"): void
{
$this->assertUserLoggedIn();
$this->template->_template = "Audio/List.xml";
$page = (int)($this->queryParam("p") ?? 1);
$audios = [];
@ -501,6 +502,7 @@ final class AudioPresenter extends OpenVKPresenter
function renderPlaylist(int $owner_id, int $virtual_id): void
{
$this->assertUserLoggedIn();
$playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id);
$page = (int)($this->queryParam("p") ?? 1);
if (!$playlist || $playlist->isDeleted())

View file

@ -27,7 +27,12 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) $comment->toggleLike($this->user->identity);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect($_SERVER["HTTP_REFERER"]);
}
@ -70,64 +75,24 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_when_publishing_comment_description"));
}
}
$photos = [];
if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
$horizontal_attachments = [];
$vertical_attachments = [];
if(!empty($this->postParam("horizontal_attachments"))) {
$horizontal_attachments_array = array_slice(explode(",", $this->postParam("horizontal_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($horizontal_attachments_array) > 0) {
$horizontal_attachments = parseAttachments($horizontal_attachments_array, ['photo', 'video']);
}
}
$videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
}
}
$audios = [];
if(!empty($this->postParam("audios"))) {
$un = rtrim($this->postParam("audios"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$audio = (new Audios)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$audio || $audio->isDeleted())
continue;
$audios[] = $audio;
}
if(!empty($this->postParam("vertical_attachments"))) {
$vertical_attachments_array = array_slice(explode(",", $this->postParam("vertical_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($vertical_attachments_array) > 0) {
$vertical_attachments = parseAttachments($vertical_attachments_array, ['audio', 'note']);
}
}
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && sizeof($audios) < 1)
if(empty($this->postParam("text")) && sizeof($horizontal_attachments) < 1 && sizeof($vertical_attachments) < 1)
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty"));
try {
@ -143,15 +108,21 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big"));
}
foreach($photos as $photo)
$comment->attach($photo);
if(sizeof($videos) > 0)
foreach($videos as $vid)
$comment->attach($vid);
foreach($horizontal_attachments as $horizontal_attachment) {
if(!$horizontal_attachment || $horizontal_attachment->isDeleted() || !$horizontal_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
foreach($audios as $audio)
$comment->attach($audio);
$comment->attach($horizontal_attachment);
}
foreach($vertical_attachments as $vertical_attachment) {
if(!$vertical_attachment || $vertical_attachment->isDeleted() || !$vertical_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
$comment->attach($vertical_attachment);
}
if($entity->getOwner()->getId() !== $this->user->identity->getId())
if(($owner = $entity->getOwner()) instanceof User)

View file

@ -43,6 +43,7 @@ final class GroupPresenter extends OpenVKPresenter
}
$this->template->club = $club;
$this->template->ignore_status = $club->isIgnoredBy($this->user->identity);
}
}

View file

@ -121,9 +121,9 @@ final class InternalAPIPresenter extends OpenVKPresenter
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$response[] = [
"url" => $attachment->getURLBySizeId('normal'),
"id" => $attachment->getPrettyId()
$response[$attachment->getPrettyId()] = [
"url" => $attachment->getURLBySizeId('larger'),
"id" => $attachment->getPrettyId(),
];
}
}
@ -133,4 +133,35 @@ final class InternalAPIPresenter extends OpenVKPresenter
]);
}
}
function renderGetPostTemplate(int $owner_id, int $post_id) {
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("ты‍ не по адресу");
}
$type = $this->queryParam("type", false);
if($type == "post") {
$post = (new Posts)->getPostById($owner_id, $post_id, true);
} else {
$post = (new Comments)->get($post_id);
}
if(!$post || !$post->canBeEditedBy($this->user->identity)) {
exit('');
}
header("Content-Type: text/plain");
if($type == 'post') {
$this->template->_template = 'components/post.xml';
$this->template->post = $post;
$this->template->commentSection = false;
} elseif($type == 'comment') {
$this->template->_template = 'components/comment.xml';
$this->template->comment = $post;
} else {
exit('');
}
}
}

View file

@ -176,6 +176,7 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->cCount = $photo->getCommentsCount();
$this->template->cPage = (int) ($this->queryParam("p") ?? 1);
$this->template->comments = iterator_to_array($photo->getComments($this->template->cPage));
$this->template->owner = $photo->getOwner();
}
function renderAbsolutePhoto($id): void
@ -287,7 +288,8 @@ final class PhotosPresenter extends OpenVKPresenter
"id" => $photo->getId(),
"vid" => $photo->getVirtualId(),
"owner" => $photo->getOwner()->getId(),
"link" => $photo->getURL()
"link" => $photo->getURL(),
"pretty_id" => $photo->getPrettyId(),
];
} catch(ISE $ex) {
$name = $album->getName();
@ -354,4 +356,26 @@ final class PhotosPresenter extends OpenVKPresenter
$this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc"));
$this->redirect($redirect);
}
function renderLike(int $wall, int $post_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->assertNoCSRF();
$photo = $this->photos->getByOwnerAndVID($wall, $post_id);
if(!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->user->identity)) $this->notFound();
if(!is_null($this->user)) {
$photo->toggleLike($this->user->identity);
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]");
}
}

View file

@ -54,6 +54,10 @@ final class UserPresenter extends OpenVKPresenter
$this->template->audioStatus = $user->getCurrentAudioStatus();
$this->template->user = $user;
if($id !== $this->user->id) {
$this->template->ignore_status = $user->isIgnoredBy($this->user->identity);
}
}
}

View file

@ -62,6 +62,7 @@ final class VideosPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
if($_SERVER["REQUEST_METHOD"] === "POST") {
$is_ajax = (int)($this->postParam('ajax') ?? '0') == 1;
if(!empty($this->postParam("name"))) {
$video = new Video;
$video->setOwner($this->user->id);
@ -75,18 +76,29 @@ final class VideosPresenter extends OpenVKPresenter
else if(!empty($this->postParam("link")))
$video->setLink($this->postParam("link"));
else
$this->flashFail("err", tr("no_video_error"), tr("no_video_description"));
$this->flashFail("err", tr("no_video_error"), tr("no_video_description"), 10, $is_ajax);
} catch(\DomainException $ex) {
$this->flashFail("err", tr("error_video"), tr("file_corrupted"));
$this->flashFail("err", tr("error_video"), tr("file_corrupted"), 10, $is_ajax);
} catch(ISE $ex) {
$this->flashFail("err", tr("error_video"), tr("link_incorrect"));
$this->flashFail("err", tr("error_video"), tr("link_incorrect"), 10, $is_ajax);
}
if((int)($this->postParam("unlisted") ?? '0') == 1) {
$video->setUnlisted(true);
}
$video->save();
if($is_ajax) {
$object = $video->getApiStructure();
$this->returnJson([
'payload' => $object->video,
]);
}
$this->redirect("/video" . $video->getPrettyId());
} else {
$this->flashFail("err", tr("error_video"), tr("no_name_error"));
$this->flashFail("err", tr("error_video"), tr("no_name_error"), 10, $is_ajax);
}
}
}
@ -151,7 +163,13 @@ final class VideosPresenter extends OpenVKPresenter
if(!is_null($this->user)) {
$video->toggleLike($this->user->identity);
}
$this->returnJson(["success" => true]);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]");
}
}

View file

@ -197,6 +197,16 @@ final class WallPresenter extends OpenVKPresenter
if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0";
if(((int)$this->queryParam('return_banned')) == 0) {
$ignored_sources_ids = $this->user->identity->getIgnoredSources(0, OPENVK_ROOT_CONF['openvk']['preferences']['newsfeed']['ignoredSourcesLimit'] ?? 50, true);
if(sizeof($ignored_sources_ids) > 0) {
$imploded_ids = implode("', '", $ignored_sources_ids);
$queryBase .= " AND `posts`.`wall` NOT IN ('$imploded_ids')";
}
}
$posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " ORDER BY `created` DESC LIMIT " . $pPage . " OFFSET " . ($page - 1) * $pPage);
$count = DatabaseConnection::i()->getConnection()->query("SELECT COUNT(*) " . $queryBase)->fetch()->{"COUNT(*)"};
@ -272,25 +282,22 @@ final class WallPresenter extends OpenVKPresenter
if($this->postParam("force_sign") === "on")
$flags |= 0b01000000;
$photos = [];
if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
$horizontal_attachments = [];
$vertical_attachments = [];
if(!empty($this->postParam("horizontal_attachments"))) {
$horizontal_attachments_array = array_slice(explode(",", $this->postParam("horizontal_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($horizontal_attachments_array) > 0) {
$horizontal_attachments = parseAttachments($horizontal_attachments_array, ['photo', 'video']);
}
}
if(!empty($this->postParam("vertical_attachments"))) {
$vertical_attachments_array = array_slice(explode(",", $this->postParam("vertical_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($vertical_attachments_array) > 0) {
$vertical_attachments = parseAttachments($vertical_attachments_array, ['audio', 'note']);
}
}
try {
$poll = NULL;
$xml = $this->postParam("poll");
@ -302,61 +309,10 @@ final class WallPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid");
}
$note = NULL;
if(!is_null($this->postParam("note")) && $this->postParam("note") != "none") {
$note = (new Notes)->get((int)$this->postParam("note"));
if(!$note || $note->isDeleted() || $note->getOwner()->getId() != $this->user->id) {
$this->flashFail("err", tr("error"), tr("error_attaching_note"));
}
if($note->getOwner()->getPrivacySetting("notes.read") < 1) {
$this->flashFail("err", " ");
}
}
$videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
}
}
$audios = [];
if(!empty($this->postParam("audios"))) {
$un = rtrim($this->postParam("audios"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$audio = (new Audios)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$audio || $audio->isDeleted())
continue;
$audios[] = $audio;
}
}
}
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && sizeof($audios) < 1 && !$poll && !$note)
if(empty($this->postParam("text")) && sizeof($horizontal_attachments) < 1 && sizeof($vertical_attachments) < 1 && !$poll)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
$should_be_suggested = $wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2;
try {
$post = new Post;
$post->setOwner($this->user->id);
@ -373,7 +329,7 @@ final class WallPresenter extends OpenVKPresenter
} catch(\Throwable) {}
}
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2)
if($should_be_suggested)
$post->setSuggested(1);
$post->save();
@ -381,21 +337,24 @@ final class WallPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
}
foreach($photos as $photo)
$post->attach($photo);
if(sizeof($videos) > 0)
foreach($videos as $vid)
$post->attach($vid);
foreach($horizontal_attachments as $horizontal_attachment) {
if(!$horizontal_attachment || $horizontal_attachment->isDeleted() || !$horizontal_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
$post->attach($horizontal_attachment);
}
foreach($vertical_attachments as $vertical_attachment) {
if(!$vertical_attachment || $vertical_attachment->isDeleted() || !$vertical_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
$post->attach($vertical_attachment);
}
if(!is_null($poll))
$post->attach($poll);
if(!is_null($note))
$post->attach($note);
foreach($audios as $audio)
$post->attach($audio);
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
@ -404,9 +363,7 @@ final class WallPresenter extends OpenVKPresenter
if($wall > 0)
$excludeMentions[] = $wall;
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
# Чтобы не было упоминаний из предложки
} else {
if(!$should_be_suggested) {
$mentions = iterator_to_array($post->resolveMentions($excludeMentions));
foreach($mentions as $mentionee)
@ -414,18 +371,7 @@ final class WallPresenter extends OpenVKPresenter
(new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit();
}
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
$suggsCount = $this->posts->getSuggestedPostsCount($wallOwner->getId());
if($suggsCount % 10 == 0) {
$managers = $wallOwner->getManagers();
$owner = $wallOwner->getOwner();
(new NewSuggestedPostsNotification($owner, $wallOwner))->emit();
foreach($managers as $manager)
(new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit();
}
if($should_be_suggested) {
$this->redirect("/club".$wallOwner->getId()."/suggested");
} else {
$this->redirect($wallOwner->getURL());
@ -476,6 +422,12 @@ final class WallPresenter extends OpenVKPresenter
if(!is_null($this->user)) {
$post->toggleLike($this->user->identity);
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]#postGarter=" . $post->getId());
}

View file

@ -17,6 +17,7 @@
{script "js/node_modules/umbrellajs/umbrella.min.js"}
{script "js/l10n.js"}
{script "js/openvk.cls.js"}
{script "js/utils.js"}
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
<script src="/assets/packages/static/openvk/js/node_modules/cropperjs/dist/cropper.js" type="module"></script>
{script "js/al_music.js"}
@ -26,6 +27,7 @@
{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"}
{css "js/node_modules/jquery-ui/themes/base/resizable.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"}
@ -51,7 +53,10 @@
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['testLabel']" id="test-label">FOR TESTING PURPOSES ONLY</div>
<div class="notifications_global_wrap"></div>
<div class="dimmer"></div>
<div class="dimmer">
<div id='absolute_territory'></div>
</div>
<div class="upLeftErrors"></div>
<div class="articleView">
<a id="articleCloseButton" class="button" href="javascript:void(u('body').removeClass('article'));">{_close}</a>
@ -245,9 +250,9 @@
<div class="floating_sidebar">
<a id="minilink-friends" class="minilink" href="/friends{$thisUser->getId()}">
<object type="internal/link" n:if="$thisUser->getFollowersCount() > 0">
<object type="internal/link" n:if="$thisUser->getRequestsCount() > 0">
<div class="counter">
+{$thisUser->getFollowersCount()}
+{$thisUser->getRequestsCount()}
</div>
</object>
<img src="/assets/packages/static/openvk/img/friends.svg">
@ -368,7 +373,7 @@
</div>
<div id="ajloader" class="loader">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif">
</div>
{include "components/cookies.xml"}
@ -389,6 +394,7 @@
{ifset $thisUser}
{script "js/al_notifs.js"}
{script "js/al_feed.js"}
{/ifset}
<script>bsdnHydrate();</script>
@ -430,6 +436,9 @@
window.openvk = {
"audio_genres": {\openvk\Web\Models\Entities\Audio::genres},
"at_search": {$atSearch ?? false},
"max_attachments": {\OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"] ?? 10},
"max_filesize_mb": 5,
"current_id": {$thisUser ? $thisUser->getId() : 0},
}
</script>

View file

@ -385,8 +385,8 @@
<tr>
<td class="e">
Vladimir Barinov (veselcraft), Celestora, Konstantin Kichulkin (kosfurler),
Nikita Volkov (sup_ban), Daniel Myslivets, Maxim Leshchenko (maksales / maksalees)
and n1rwana
Daniel Myslivets, Maxim Leshchenko (maksales / maksalees), n1rwana and
Jillian Österreich (Lumaeris)
</td>
</tr>
</tbody>
@ -400,7 +400,7 @@
<tr>
<td class="e">
Vladimir Barinov (veselcraft) and Konstantin Kichulkin (kosfurler)<br/>
OpenVK is a free open source software that "cosplays" (or imitates) older versions of a Russian social network called VKontakte. VKontakte belongs to Pavel Durov and VK Group.
OpenVK is a free open source software that "cosplays" (or imitates) older versions of a Russian social network called VKontakte. VKontakte belongs to VK (formerly Mail.ru Group).
</td>
</tr>
</tbody>
@ -447,7 +447,7 @@
</tr>
<tr>
<td class="e">Initial hosting</td>
<td class="v">Lumaeris and Celestora</td>
<td class="v">Jillian Österreich (Lumaeris) and Celestora</td>
</tr>
<tr>
<td class="e">Initial bug-tracker hosting</td>
@ -459,7 +459,7 @@
</tr>
<tr>
<td class="e">Illustrations</td>
<td class="v">Ash Defenders, Polina Katunina (RousPhaul)</td>
<td class="v">Ash Defenders, Polina Katunina (ktp0li)</td>
</tr>
<tr>
<td class="e">Best barmaid</td>
@ -479,9 +479,9 @@
</tr>
<tr class="e">
<td>
kovaltim, Vladimir Lapskiy (0x7d5), Alexander Minkin (WerySkok), Polina Katunina (RousPhaul), veth,
kovaltim, Vladimir Lapskiy (0x7d5), Alexander Minkin (WerySkok), Polina Katunina (ktp0li), veth,
Egor Shevchenko, Vadim Korovin (yuni), Ash Defenders,
Pavel Silaev, Dmitriy Daemon, Lumaeris,
Pavel Silaev, Dmitriy Daemon, Jillian Österreich (Lumaeris),
cmed404 and unknown tester, who disappeared shortly after trying to upload post with cat.
</td>
</tr>

View file

@ -5,17 +5,17 @@
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$isAvailable}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
<audio class="audio" />
<div id="miniplayer" class="audioEntry" style="min-height: 39px;">
<div style="display: flex;">
<div id="miniplayer" class="audioEntry">
<div class='audioEntryWrapper' style="display: flex;" draggable='true'>
<div class="playerButton">
<div class="playIcon"></div>
</div>
<div class="status" style="margin-top: 12px;">
<div class="status" draggable='false'>
<div class="mediaInfo noOverflow">
<div class="info">
<strong class="performer">
<a href="/search?query=&section=audios&order=listens&only_performers=on&q={$audio->getPerformer()}">{$audio->getPerformer()}</a>
<a href="/search?section=audios&order=listens&only_performers=on&q={$audio->getPerformer()}">{$audio->getPerformer()}</a>
</strong>
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">{$audio->getTitle()}</span>
@ -27,8 +27,8 @@
</div>
</div>
<div class="volume" style="display: flex; flex-direction: column;width:14%;">
<span class="nobold {if !$hideButtons}hideOnHover{/if}" data-unformatted="{$audio->getLength()}" style="text-align: center;margin-top: 12px;">{$audio->getFormattedLength()}</span>
<div class="volume">
<span class="nobold {if !$hideButtons}hideOnHover{/if}" data-unformatted="{$audio->getLength()}">{$audio->getFormattedLength()}</span>
<div class="buttons">
{php $hasAudio = isset($thisUser) && $audio->isInLibraryOf($thisUser)}
@ -44,7 +44,7 @@
</div>
</div>
</div>
<div class="subTracks">
<div class="subTracks" draggable='false'>
<div class="lengthTrackWrapper">
<div class="track lengthTrack">
<div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">

View file

@ -168,10 +168,10 @@
{/if}
{var $canReport = $thisUser->getId() != $club->getOwner()->getId()}
{if $canReport}
<a class="profile_link" style="display:block;" href="javascript:reportVideo()">{_report}</a>
<a class="profile_link" style="display:block;" href="javascript:reportClub()">{_report}</a>
<script>
function reportVideo() {
function reportClub() {
uReportMsgTxt = tr("going_to_report_club");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
@ -194,6 +194,9 @@
}
</script>
{/if}
<a n:if="!$club->isHideFromGlobalFeedEnabled()" class="profile_link" style="display:block;" id="__ignoreSomeone" data-val='{!$ignore_status ? 1 : 0}' data-id="{$club->getRealId()}">
{if !$ignore_status}{_ignore_club}{else}{_unignore_club}{/if}
</a>
</div>
<div>
<div class="content_title_expanded" onclick="hidePanel(this);">

View file

@ -4,76 +4,72 @@
{block header}
{ifset $album}
<a href="{$album->getOwner()->getURL()}">
{$album->getOwner()->getCanonicalName()}
{var $album_owner = $album->getOwner()}
<a href="{$album_owner->getURL()}">
{$album_owner->getCanonicalName()}
</a>
{if ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
» <a href="/albums{$album->getOwner()->getId() * -1}">{_albums}</a>
{if ($album_owner instanceof openvk\Web\Models\Entities\Club)}
» <a href="/albums{$album_owner->getId() * -1}">{_albums}</a>
{else}
» <a href="/albums{$album->getOwner()->getId()}">{_albums}</a>
» <a href="/albums{$album_owner->getId()}">{_albums}</a>
{/if}
» <a href="/album{$album->getPrettyId()}">{$album->getName()}</a>
{else}
<a href="{$photo->getOwner()->getURL()}">{$photo->getOwner()->getCanonicalName()}</a>
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
{/ifset}
» {_photo}
{/block}
{block content}
<center style="margin-bottom: 8pt;">
<img src="{$photo->getURLBySizeId('large')}" style="max-width: 80%; max-height: 60vh;" />
</center>
<hr/>
<div style="width: 100%; min-height: 100px;" class="ovk-photo-details">
<div style="float: left; min-height: 100px; width: 68%;margin-left: 3px;">
{include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo, custom_id => 999}
<div class='media-page-wrapper photo-page-wrapper'>
<div class='photo-page-wrapper-photo'>
<img src="{$photo->getURLBySizeId('large')}" />
</div>
<div style="float:right;min-height: 100px;width: 30%;margin-left: 1px;">
<div>
<h4>{_information}</h4>
<span style="color: grey;">{_info_description}:</span>
{$photo->getDescription() ?? "(" . tr("none") . ")"}<br/>
<span style="color: grey;">{_info_uploaded_by}:</span>
<a href="{$photo->getOwner()->getURL()}">{$photo->getOwner()->getFullName()}</a><br/>
<span style="color: grey;">{_info_upload_date}:</span>
{$photo->getPublicationTime()}
<div class='ovk-photo-details'>
<div class='media-page-wrapper-description'>
<p n:if='!empty($photo->getDescription())'>{$photo->getDescription()}</p>
<div class='upload_time'>
{_info_upload_date}: {$photo->getPublicationTime()}
{if isset($thisUser)}
|
{var $liked = $photo->hasLikeFrom($thisUser)}
{var $likesCount = $photo->getLikesCount()}
<div class='like_wrap tidy'>
<a href="/photo{$photo->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$likesCount}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
</div>
{/if}
</div>
</div>
<br/>
<h4>{_actions}</h4>
{if isset($thisUser) && $thisUser->getId() != $photo->getOwner()->getId()}
{var canReport = true}
{/if}
<div n:if="isset($thisUser) && $thisUser->getId() === $photo->getOwner()->getId()">
<a href="/photo{$photo->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">{_edit}</a>
<a id="_photoDelete" href="/photo{$photo->getPrettyId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a>
</div>
<a href="{$photo->getURL()}" class="profile_link" target="_blank" style="display:block;width:96%;">{_"open_original"}</a>
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportPhoto()">{_report}</a>
<script n:if="$canReport ?? false">
function reportPhoto() {
uReportMsgTxt = tr("going_to_report_photo");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$photo->getId()} + "?reason=" + res + "&type=photo", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
<hr/>
<div class="media-page-wrapper-details">
<div class='media-page-wrapper-comments'>
{include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo, custom_id => 999}
</div>
<div class='media-page-wrapper-actions'>
<a href="{$owner->getURL()}" class='media-page-author-block'>
<img class='cCompactAvatars' src="{$owner->getAvatarURL('miniscule')}">
<div class='media-page-author-block-name'>
<b>{$owner->getCanonicalName()}</b>
</div>
</a>
<div n:if="isset($thisUser) && $thisUser->getId() === $photo->getOwner()->getId()">
<a href="/photo{$photo->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">{_edit}</a>
<a id="_photoDelete" href="/photo{$photo->getPrettyId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a>
</div>
<a href="{$photo->getURL()}" class="profile_link" target="_blank" style="display:block;width:96%;">{_"open_original"}</a>
<a n:if="isset($thisUser) && $thisUser->getId() != $photo->getOwner()->getId()" class="profile_link" style="display:block;width:96%;" href="javascript:reportPhoto({$photo->getId()})">{_report}</a>
<a n:if="isset($thisUser)" onclick="javascript:repost({$photo->getPrettyId()}, 'photo')" class="profile_link" style="display:block;width:96%;">
{_share}
</a>
</div>
</div>
</div>
</div>
{/block}

View file

@ -166,6 +166,9 @@
{/if}
<a class="profile_link" style="display:block;width:96%;" href="javascript:reportUser()">{_report}</a>
<a n:if="!$user->isHideFromGlobalFeedEnabled()" class="profile_link" style="display:block;width:96%;" id="__ignoreSomeone" data-val='{!$ignore_status ? 1 : 0}' data-id="{$user->getId()}">
{if !$ignore_status}{_ignore_user}{else}{_unignore_user}{/if}
</a>
<script>
function reportUser() {
uReportMsgTxt = tr("going_to_report_user");
@ -322,13 +325,13 @@
</div>
<div style="padding: 5px;">
<div class="ovk-video" n:foreach="$videos as $video">
<a href="/video{$video->getPrettyId()}" class="preview" align="center" id="videoOpen" data-id="{$video->getId()}">
<a href="/video{$video->getPrettyId()}" class="preview" align="center" id="videoOpen" data-id="{$video->getPrettyId()}">
<img
src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" />
</a>
<div>
<b><a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
<b><a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getPrettyId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
<span style="font-size: 10px;">{$video->getPublicationTime()} | {_comments} ({$video->getCommentsCount()})</span>
</div>
</div>

View file

@ -11,91 +11,88 @@
{/block}
{block content}
<center style="margin-bottom: 8pt;">
{if $video->getType() === 0}
<div class="bsdn" data-name="{$video->getName()}" data-author="{$user->getCanonicalName()}">
<video src="{$video->getURL()}"></video>
</div>
{else}
{var $driver = $video->getVideoDriver()}
{if !$driver}
{_unknown_video}
{else}
{$driver->getEmbed()|noescape}
{/if}
{/if}
</center>
<hr/>
<div style="width: 100%; min-height: 100px;">
<div style="float: left; min-height: 100px; width: 68%; margin-right: 2%;" id="comments">
{include "../components/comments.xml",
comments => $comments,
count => $cCount,
page => $cPage,
model => "videos",
parent => $video}
</div>
<div style="float: left; min-height: 100px; width: 30%; overflow: hidden; overflow-wrap: break-word;">
<div>
<h4>{_information}</h4>
<span style="color: grey;">{_info_name}:</span>
{$video->getName()}<br/>
<span style="color: grey;">{_info_description}:</span>
{$video->getDescription() ?? "(" . tr("none") . ")"}<br/>
<span style="color: grey;">{_info_uploaded_by}:</span>
<a href="{$user->getURL()}">{$user->getFullName()}</a><br/>
<span style="color: grey;">{_info_upload_date}:</span>
{$video->getPublicationTime()}
</div>
<br/>
<div>
<div n:if="isset($thisUser) && $thisUser->getId() === $user->getId()">
<h4>{_actions}</h4>
<a href="/video{$video->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">
{_edit}
</a>
<a href="/video{$video->getPrettyId()}/remove" class="profile_link" style="display:block;width:96%;">
{_delete}
</a>
<div class='media-page-wrapper video-page-wrapper'>
<div class='video-page-wrapper-video'>
{if $video->getType() === 0}
<div class="bsdn" data-name="{$video->getName()}" data-author="{$user->getCanonicalName()}">
<video src="{$video->getURL()}"></video>
</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 $thisUser->getId() != $video->getOwner()->getId()}
{var canReport = true}
{else}
{var $driver = $video->getVideoDriver()}
{if !$driver}
{_unknown_video}
{else}
{$driver->getEmbed()|noescape}
{/if}
{/if}
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportVideo()">{_report}</a>
<script n:if="$canReport ?? false">
function reportVideo() {
uReportMsgTxt = tr("going_to_report_video");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$video->getId()} + "?reason=" + res + "&type=video", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
</div>
<div class='ovk-vid-details'>
<div class='media-page-wrapper-description'>
<p><b>{$video->getName()}</b></p>
<p n:if='!empty($video->getDescription())'>{$video->getDescription()}</p>
<div class='upload_time'>
{_info_upload_date}: {$video->getPublicationTime()}
{if isset($thisUser)}
|
{var $liked = $video->hasLikeFrom($thisUser)}
{var $likesCount = $video->getLikesCount()}
<div class='like_wrap tidy'>
<a href="/video{$video->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$likesCount}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
</div>
{/if}
</div>
</div>
<hr/>
<div class="media-page-wrapper-details">
<div class='media-page-wrapper-comments' id="comments">
{include "../components/comments.xml",
comments => $comments,
count => $cCount,
page => $cPage,
model => "videos",
parent => $video}
</div>
<div class='media-page-wrapper-actions'>
<a href="{$user->getURL()}" class='media-page-author-block'>
<img class='cCompactAvatars' src="{$user->getAvatarURL('miniscule')}">
<div class='media-page-author-block-name'>
<b>{$user->getCanonicalName()}</b>
</div>
</a>
<div>
<div n:if="isset($thisUser) && $thisUser->getId() === $user->getId()">
<a href="/video{$video->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">
{_edit}
</a>
<a id='_videoDelete' href="/video{$video->getPrettyId()}/remove" class="profile_link" style="display:block;width:96%;">
{_delete}
</a>
</div>
<a n:if="isset($thisUser)" onclick="javascript:repost({$video->getPrettyId()}, 'video')" class="profile_link" style="display:block;width:96%;">
{_share}
</a>
</div>
{if isset($thisUser)}
{if $thisUser->getId() != $video->getOwner()->getId()}
{var canReport = true}
{/if}
{/if}
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportVideo({$video->getId()})">{_report}</a>
<a n:if="$video->getType() == 0" href="{$video->getURL()}" download="" class="profile_link" style="display:block;width:96%;">{_download_video}</a>
</div>
</div>
</div>
</div>
{/block}

View file

@ -15,6 +15,8 @@
<div n:attr="id => (isset($globalFeed) ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => (isset($globalFeed) ? 'act_tab_a' : 'ki')" href="/feed/all">{_all_news}</a>
</div>
<a href='#' id="__feed_settings_link" data-pagescount='{ceil($paginatorConf->count / $paginatorConf->perPage)}'>{_feed_settings}</a>
</div>
<div n:class="postFeedWrapper, $thisUser->hasMicroblogEnabled() ? postFeedWrapperMicroblog">

View file

@ -1,7 +1,7 @@
{if $attachment instanceof \openvk\Web\Models\Entities\Photo}
{if !$attachment->isDeleted()}
{var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
<a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('normal')}, {$parent->getPrettyId()}, {$attachment->getPrettyId()}, {$parentType})">
<a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('larger')}, {$parent->getPrettyId()}, {$attachment->getPrettyId()}, {$parentType})">
<img class="media media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" loading=lazy />
</a>
{else}
@ -10,39 +10,50 @@
</a>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
{if $attachment->getType() === 0}
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
<video class="media" src="{$attachment->getURL()}"></video>
{if $tilesCount <= 1}
{if $attachment->getType() === 0}
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
<video class="media" src="{$attachment->getURL()}"></video>
</div>
{else}
{var $driver = $attachment->getVideoDriver()}
{if !$driver}
<span style="color:red;">{_version_incompatibility}</span>
{else}
{$driver->getEmbed("100%")|noescape}
{/if}
{/if}
<div class="video-wowzer">
<div class="small-video-ico"></div>
<a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getPrettyId()}">{$attachment->getName()}</a>
<span class="video-wowzer-length" n:if="$attachment->getLength() != NULL">({$attachment->getFormattedLength()})</span>
</div>
{else}
{var $driver = $attachment->getVideoDriver()}
{if !$driver}
<span style="color:red;">{_version_incompatibility}</span>
{else}
{$driver->getEmbed("100%")|noescape}
{/if}
{/if}
<a class='compact_video' id='videoOpen' data-id='{$attachment->getPrettyId()}' href="/video{$attachment->getPrettyId()}">
<div class='play-button'>
<div class='play-button-ico'></div>
</div>
<div class='video-length' n:if="$attachment->getLength() != NULL">
{$attachment->getFormattedLength()}
</div>
<div class="video-wowzer">
<img src="/assets/packages/static/openvk/img/videoico.png" />
<a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getId()}">{$attachment->getName()}</a>
</div>
<img class="media media_makima" src="{$attachment->getThumbnailURL()}" loading=lazy />
</a>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
{presenter "openvk!Poll->view", $attachment->getId()}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Note}
{if !$attachment->isDeleted()}
<div data-att_type='note' data-att_id="{$attachment->getPrettyId()}">
<div class="attachment_note">
<img class="attachment_note_icon" src="/assets/packages/static/openvk/img/note.svg">
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name"><a href="javascript:void(showArticle({$attachment->getId()}));">{ovk_proc_strtr($attachment->getName(), 66)}</a></span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 10"><polygon points="0 0 0 10 8 10 8 4 4 4 4 0 0 0"/><polygon points="5 0 5 3 8 3 5 0"/></svg>
<div class='attachment_note_content'>
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name"><a href="javascript:void(showArticle({$attachment->getId()}));">{ovk_proc_strtr($attachment->getName(), 66)}</a></span>
</div>
</div>
{else}
<div class="attachment_note">
<img class="attachment_note_icon" src="/assets/packages/static/openvk/img/note.svg">
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name">{_deleted}</span>
</div>
{/if}
</div>
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
{php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1}
{if $GLOBALS["_nesAttGloCou"] > 2}
@ -51,7 +62,7 @@
{include "post.xml", post => $attachment, compact => true}
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Audio}
<div style="width:100%;">
<div style="width:100%;" data-att_type='audio' data-att_id="{$attachment->getPrettyId()}">
{include "../Audio/player.xml", audio => $attachment}
</div>
{else}

View file

@ -5,7 +5,7 @@
{var $postId = $target instanceof \openvk\Web\Models\Entities\Post ? $target->getId() : NULL}
<a name="cid={$comment->getId()}"></a>
<table border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$author instanceof $Club}" n:attr="data-post-id => $postId">
<table data-id="1_{$comment->getId()}" border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$author instanceof $Club}" n:attr="data-post-id => $postId">
<tbody>
<tr>
<td width="30" valign="top">
@ -22,12 +22,12 @@
</div>
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
<span data-text="{$comment->getText(false)}" class="really_text">{$comment->getText()|noescape}</span>
<span class="really_text">{$comment->getText()|noescape}</span>
{var $attachmentsLayout = $comment->getChildrenWithLayout(288)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment"}
{include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment", tilesCount => sizeof($attachmentsLayout->tiles)}
</div>
</div>
@ -77,6 +77,7 @@
</span>
</div>
</div>
<div class='post-edit'></div>
</td>
</tr>
</tbody>

View file

@ -18,7 +18,7 @@
{var $commentTextAreaId = $post === NULL ? rand(1,300) : $post->getId()}
<table border="0" style="font-size: 11px;" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
<table border="0" style="font-size: 11px;" data-id="{$post->getPrettyId()}" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
<tbody>
<tr>
<td width="54" valign="top">
@ -68,13 +68,10 @@
{/if}
{if $post->canBeEditedBy($thisUser) && !($forceNoEditLink ?? false) && $compact == false}
<a class="edit" id="editPost"
data-id="{$post->getId()}"
data-nsfw="{(int)$post->isExplicit()}"
{if $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}></a>
<a class="edit" id="editPost"></a>
{/if}
</div>
<div class="post-content" id="{$post->getPrettyId()}">
<div class="post-content" id="{$post->getPrettyId()}" data-localized-nsfw-text="{_nsfw_warning}">
<div class="text" id="text{$post->getPrettyId()}">
<span data-text="{$post->getText(false)}" class="really_text">{$post->getText()|noescape}</span>
@ -84,8 +81,8 @@
{/if}
{var $attachmentsLayout = $post->getChildrenWithLayout($width)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"}
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", tilesCount => sizeof($attachmentsLayout->tiles)}
</div>
</div>
@ -112,6 +109,7 @@
</span>
</div>
</div>
<div class='post-edit' n:if='!$compact'></div>
<div class="post-menu" n:if="$compact == false">
<a href="{if !$suggestion}/wall{$post->getPrettyId()}{else}javascript:void(0){/if}" class="date">{$post->getPublicationTime()}
<span n:if="$post->getEditTime()" class="edited editedMark">({_edited_short})</span>
@ -126,7 +124,7 @@
<a n:if="!($forceNoCommentsLink ?? false) && $commentsCount == 0" href="javascript:expand_comment_textarea({$commentTextAreaId})">{_comment}</a>
<div class="like_wrap">
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" href="javascript:repost('{$post->getPrettyId()}', 'post')">
<div class="repost-icon" style="opacity: 0.4;"></div>
<span class="likeCnt" id="repostsCount{$post->getPrettyId()}">{if $repostsCount > 0}{$repostsCount}{/if}</span>
</a>

View file

@ -15,7 +15,7 @@
<table border="0" style="font-size: 11px;" n:class="post, $post->isExplicit() ? post-nsfw">
<table border="0" style="font-size: 11px;" data-id="{$post->getPrettyId()}" n:class="post, $post->isExplicit() ? post-nsfw">
<tbody>
<tr>
<td width="54" valign="top">
@ -67,7 +67,7 @@
</a>
</a>
</div>
<div class="post-content" id="{$post->getPrettyId()}">
<div class="post-content" id="{$post->getPrettyId()}" data-localized-nsfw-text="{_nsfw_warning}">
<div class="text" id="text{$post->getPrettyId()}">
{var $owner = $author->getId()}
@ -79,8 +79,8 @@
{/if}
{var $attachmentsLayout = $post->getChildrenWithLayout($width)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"}
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", tilesCount => sizeof($attachmentsLayout->tiles)}
</div>
</div>
@ -111,6 +111,7 @@
</span>
</div>
</div>
<div class='post-edit' n:if='!$compact'></div>
<div n:if="!($compact ?? false)" class="post-menu">
{if is_null($thisUser)}
{var $forceNoDeleteLink = true}
@ -119,9 +120,7 @@
{if !($forceNoEditLink ?? false) && $post->canBeEditedBy($thisUser)}
<a id="editPost"
data-id="{$post->getId()}"
data-nsfw="{(int)$post->isExplicit()}"
{if $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}>{_edit}</a> &nbsp;|&nbsp;
data-id="{$post->getPrettyId()}">{_edit}</a> &nbsp;|&nbsp;
{/if}
{if !($forceNoDeleteLink ?? false) && $canBeDeleted}
@ -148,7 +147,7 @@
&nbsp;|&nbsp;
{/if}
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" {ifset $thisUser} href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')"{/ifset}>
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" {ifset $thisUser} href="javascript:repost('{$post->getPrettyId()}', 'post')"{/ifset}>
{_share}
{if $repostsCount > 0}
(<b id="repostsCount{$post->getPrettyId()}">{$repostsCount}</b>)

View file

@ -2,26 +2,18 @@
{var $textAreaId = ($post ?? NULL) === NULL ? (++$GLOBALS["textAreaCtr"]) : $post->getId()}
{var $textAreaId = ($custom_id ?? NULL) === NULL ? $textAreaId : $custom_id}
<div id="write" style="padding: 5px 0;" onfocusin="expand_wall_textarea({$textAreaId});">
<div id="write" class='model_content_textarea' style="padding: 5px 0;">
<form action="{$route}" method="post" enctype="multipart/form-data" style="margin:0;">
<textarea id="wall-post-input{$textAreaId}" placeholder="{_write}" name="text" style="width: 100%;resize: none;" class="small-textarea"></textarea>
<div>
<!-- padding to fix <br/> bug -->
</div>
<div id="post-buttons{$textAreaId}" style="display: none;">
<div class="upload">
</div>
<div class="post-upload">
<span style="color: inherit;"></span>
</div>
<div id="post-buttons{$textAreaId}" class='post-buttons' style="display: none;">
<div class="post-horizontal"></div>
<div class="post-vertical"></div>
<div class="post-has-poll">
{_poll}
</div>
<div class="post-has-note"></div>
<div class="post-has-videos"></div>
<div class="post-has-audios"></div>
<div class="post-source"></div>
<div n:if="$postOpts ?? true" class="post-opts">
@ -62,42 +54,44 @@
<input type="checkbox" name="as_group" /> {_comment_as_group}
</label>
</div>
<input type="hidden" name="photos" value="" />
<input type="hidden" name="videos" value="" />
<input type="hidden" name="audios" value="" />
<input type="hidden" name="horizontal_attachments" value="" />
<input type="hidden" name="vertical_attachments" value="" />
<input type="hidden" name="poll" value="none" />
<input type="hidden" id="note" name="note" value="none" />
<input type="hidden" id="source" name="source" value="none" />
<input type="hidden" name="type" value="1" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<br/>
<input type="submit" value="{_write}" class="button" />
<div style="float: right; display: flex; flex-direction: column;">
<a href="javascript:toggleMenu({$textAreaId});">
<a class='menu_toggler'>
{_attach}
</a>
<div id="wallAttachmentMenu" class="hidden">
<a class="header" href="javascript:toggleMenu({$textAreaId});">
<a class="header menu_toggler">
{_attach}
</a>
<a id="photosAttachments" {if !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)}data-club="{$club->getId()}"{/if}>
<a id="__photoAttachment" {if !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)}data-club="{$club->getId()}"{/if}>
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-egon.png" />
{_photo}
</a>
<a id="videoAttachment">
<a id="__videoAttachment">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
{_video}
</a>
<a id="_audioAttachment">
<a id="__audioAttachment">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/audio-ac3.png" />
{_audio}
</a>
<a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});">
<a n:if="$notes ?? false" id="__notesAttachment">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-srt.png" />
{_note}
</a>
<a n:if="$graffiti ?? false" onclick="initGraffiti(event);">
<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})">
<a n:if="$polls ?? false" onclick="initPoll(event);">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/office-chart-bar-stacked.png" />
{_poll}
</a>
@ -112,13 +106,8 @@
</div>
<script>
$(document).ready(() => {
setupWallPostInputHandlers({$textAreaId});
});
u("#post-buttons{$textAreaId} input[name='videos']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='photos']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='audios']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='horizontal_attachments']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='vertical_attachments']")["nodes"].at(0).value = ""
</script>
{if $graffiti}

View file

@ -3,7 +3,7 @@
<tbody>
<tr>
<td valign="top">
<a href="/video{$video->getPrettyId()}" data-id="{$video->getId()}">
<a href="/video{$video->getPrettyId()}" id='videoOpen' data-id="{$video->getPrettyId()}">
<div class="video-preview">
<img src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" >
@ -15,10 +15,13 @@
{include infotable, x => $dat}
{else}
<a href="/video{$video->getPrettyId()}">
<b class='video_name' {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">
<b class='video_name' id='videoOpen' data-id="{$video->getPrettyId()}">
{$video->getName()}
</b>
</a>
{if $video->getLength()}
({$video->getFormattedLength()})
{/if}
<br/>
<p>
<span class='video_description'>{$video->getDescription() ?? ""}</span>
@ -26,7 +29,7 @@
<span style="color: grey;">{_video_uploaded} {$video->getPublicationTime()}</span><br/>
<p>
<a href="/video{$video->getPrettyId()}" {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">{_view_video}</a>
<a href="/video{$video->getPrettyId()}" id='videoOpen' data-id="{$video->getPrettyId()}">{_view_video}</a>
{if $video->getCommentsCount() > 0}| <a href="/video{$video->getPrettyId()}#comments">{_comments} ({$video->getCommentsCount()})</a>{/if}
</p>
{/ifset}

View file

@ -18,7 +18,7 @@ class Makima
$this->photos = $photos;
}
private function getOrientation(Photo $photo, &$ratio): int
private function getOrientation($photo, &$ratio): int
{
[$width, $height] = $photo->getDimensions();
$ratio = $width / $height;

View file

@ -129,8 +129,6 @@ routes:
handler: "Wall->rss"
- url: "/wall{num}/makePost"
handler: "Wall->makePost"
- url: "/wall/edit"
handler: "Wall->edit"
- url: "/wall{num}_{num}"
handler: "Wall->post"
- url: "/wall{num}_{num}/like"
@ -171,6 +169,8 @@ routes:
handler: "Photos->uploadPhoto"
- url: "/photo{num}_{num}"
handler: "Photos->photo"
- url: "/photo{num}_{num}/like"
handler: "Photos->like"
- url: "/photos/thumbnails/{num}_{text}.jpeg"
handler: "Photos->thumbnail"
- url: "/photos/{text}"
@ -403,6 +403,8 @@ routes:
handler: "About->dev"
- url: "/iapi/getPhotosFromPost/{num}_{num}"
handler: "InternalAPI->getPhotosFromPost"
- url: "/iapi/getPostTemplate/{num}_{num}"
handler: "InternalAPI->getPostTemplate"
- url: "/tour"
handler: "About->tour"
- url: "/{?shortCode}"

View file

@ -267,6 +267,17 @@
color: white;
}
.audioEntry .volume {
display: flex;
flex-direction: column;
width: 14%;
}
.audioEntry .nobold {
text-align: center;
margin-top: 12px;
}
.audioEntry.nowPlaying .volume .nobold {
color: white !important;
}
@ -303,6 +314,7 @@
height: 100%;
position: relative;
width: 100%;
min-height: 39px;
}
.audioEntry .playerButton {
@ -341,6 +353,7 @@
grid-template-columns: 1fr;
width: 85%;
height: 23px;
margin-top: 12px;
}
.audioEntry .status .mediaInfo {
@ -588,7 +601,6 @@
}
.attachAudio {
float: left;
width: 28%;
height: 26px;
padding-top: 11px;
@ -771,6 +783,7 @@
.addToPlaylist {
width: 22%;
float: left;
}
#_addAudioAdditional {

View file

@ -88,3 +88,7 @@ div.ovk-video > div > img
height: 31px;
min-width: 30px;
}
.entity_vertical_list .entity_vertical_list_item img {
border-radius: 20px;
}

View file

@ -24,6 +24,7 @@
gap: 6px;
z-index: 2;
opacity: 0;
height: 43px;
}
.bsdn-player.bsdn-dirty > .bsdn_controls {

View file

@ -7,6 +7,11 @@ body.dimmed > .dimmer {
opacity: .5;
}
body.dimmed > .dimmer #absolute_territory {
margin: 100px;
height: 100%;
}
.ovk-diag-cont {
z-index: 1024;
position: fixed;
@ -59,243 +64,147 @@ body.dimmed > .dimmer {
margin-left: 10px;
}
/* Modal player */
/* fullscreen player */
.ovk-fullscreen-player {
top: 9%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, 0%);
z-index: 6667;
position: absolute;
.ovk-modal-player-window {
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, 0.2);
width: 823px;
min-height: 400px;
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, 0.2);
margin: 9vh auto 0 auto;
}
.top-part span {
.ovk-modal-player-window #ovk-player-part {
height: 70vh;
background: black;
padding: 15px 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.ovk-modal-player-window #ovk-player-part .top-part {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.ovk-modal-player-window #ovk-player-part .top-part b {
color: #515151;
font-size: 13px;
transition: color 200ms ease-in-out;
font-size: 12px;
}
.top-part .clickable:hover {
color: #ffffff;
.ovk-modal-player-window #ovk-player-part .top-part .top-part-buttons, .ovk-modal-player-window #ovk-player-part .bottom-part {
color: #515151;
}
.ovk-fullscreen-player .bsdn_teaserTitleBox span {
color: unset;
font-size: unset;
.ovk-modal-player-window #ovk-player-part .top-part .top-part-buttons a, .ovk-modal-player-window #ovk-player-part .bottom-part a {
color: #515151;
}
.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 {
.ovk-modal-player-window #ovk-player-info {
display: none;
}
.ovk-modal-player-window #ovk-player-info.shown {
display: block;
}
.ovk-modal-player-window #ovk-player-info {
background: white;
padding-bottom: 20px;
padding-top: 30px;
min-height: 200px;
padding: 5px 15px;
}
.left_block {
padding-left: 20px;
/*padding-top: 20px;*/
width: 75%;
float: left;
background: white;
padding-right: 6px;
max-height: 400px;
overflow-y: auto;
.ovk-modal-player-window .media-page-wrapper-comments {
width: 100%;
}
.right_block {
padding-left: 10px;
/*padding-top: 20px;*/
width: 20%;
border-left: 1px solid gray;
float: right;
.ovk-modal-player-window .center-part {
text-align: center;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
.bottom-part span {
font-size: 13px;
}
.bottom-part .gray {
.ovk-modal-player-window .center-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;
.ovk-modal-player-window .center-part .bsdn, .ovk-modal-player-window .center-part .bsdn-player {
height: 100%;
width: 100%;
}
.miniplayer {
position: absolute;
top:0;
background: rgba(54, 54, 54, 0.9);
background: rgba(54, 54, 54, 0.95);
border-radius: 3px;
min-width: 299px;
min-height: 192px;
padding-top: 3px;
z-index: 9999;
z-index: 7777;
padding: 2px 7px;
display: flex;
flex-direction: column;
}
.miniplayer .bsdn-player {
max-height: 150px;
.miniplayer .miniplayer-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.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;
.miniplayer .miniplayer-head b {
color: white;
user-select: none;
}
.ui-draggable {
position:fixed !important;
.miniplayer .miniplayer-head .miniplayer-head-buttons {
display: flex;
align-items: center;
gap: 4px;
}
.miniplayer-actions img {
max-width: 11px;
.miniplayer .miniplayer-head .miniplayer-head-buttons div {
width: 16px;
height: 16px;
background: url(/assets/packages/static/openvk/img/wall.png) no-repeat 1px 0;
background-repeat: no-repeat;
opacity: 0.5;
cursor: pointer;
transition: opacity 200ms ease-in-out;
opacity: 70%;
}
.miniplayer .fplayer iframe {
max-width: 260px;
max-height: 160px;
.miniplayer .miniplayer-head .miniplayer-head-buttons div:hover {
opacity: 1;
}
.miniplayer-actions img:hover {
opacity: 100%;
.miniplayer .miniplayer-head .miniplayer-head-buttons #__miniplayer_return {
background-position: -28px 0px;
}
#vidComments {
margin-top: 10px;
.miniplayer .miniplayer-head .miniplayer-head-buttons #__miniplayer_close {
background-position: -12px 0px;
}
.showMoreComments {
background: #eaeaea;
cursor: pointer;
text-align: center;
padding: 10px;
user-select: none;
margin-top: 10px;
.miniplayer .miniplayer-head-buttons {
height: 20px;
}
.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;
.miniplayer .miniplayer-body {
height: 100%;
}
.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;
.miniplayer .miniplayer-body .bsdn {
height: 100%;
}
.right-arrow img, .left-arrow img {
user-select: none;
opacity: 5%;
transition: all 200ms ease-in-out;
.miniplayer .miniplayer-body .bsdn .bsdn-player {
height: 100%;
}
.right-arrow:hover, .left-arrow:hover {
background: rgba(0, 0, 0, 0.5);
}
.right-arrow img:hover, .left-arrow img:hover {
opacity: 50%;
.miniplayer .miniplayer-body iframe {
width: 100%;
height: 100%;
}

View file

@ -13,6 +13,10 @@ body {
word-wrap: break-word;
}
body, .ovk-fullscreen-dimmer, .ovk-photo-view-dimmer {
scrollbar-gutter: stable both-edges;
}
.nobold,
nobold {
font-weight: normal;
@ -41,6 +45,16 @@ h1 {
margin-top: 0;
}
.display_flex_column {
display: flex;
flex-direction: column;
}
.display_flex_row {
display: flex;
flex-direction: row;
}
.layout {
width: 791px;
margin: 0 auto;
@ -254,6 +268,28 @@ h1 {
width: calc(550px - 16px - 38px - 171px);
}
.upLeftErrors {
position: fixed;
display: flex;
flex-direction: column;
z-index: 1025;
}
.upLeftErrors .upLeftError {
padding: 2px 2px 2px 2px;
display: inline-block;
border-radius: 0px 0px 5px 0px;
font-size: 15px;
}
.upLeftErrors .upLeftError.upLeftErrorRed {
background: #ff7474;
}
.upLeftErrors .upLeftError.upLeftErrorGreen {
background: #74ff92;
}
.page_body {
width: 632px;
float: right;
@ -621,6 +657,16 @@ input[type=checkbox]:checked:disabled {
background-position: 0 -42px;
}
input[type="number"] {
border: 1px solid #C0CAD5;
padding: 3px;
font-size: 11px;
font-family: tahoma, verdana, arial, sans-serif;
width: 100%;
box-sizing: border-box;
outline: none;
}
#auth {
padding: 10px;
}
@ -762,6 +808,14 @@ h4 {
font-size: 9px;
}
.editing .post-content, .post-edit {
display: none;
}
.editing .post-edit {
display: block;
}
.post-content .text {
padding: 4px;
font-size: 11px;
@ -1479,7 +1533,7 @@ body.scrolled .toTop:hover, .toTop.has_down:hover {
color: #3c3c3c;
}
.post-source #remove_source_button {
.post-source #remove_source_button, #small_remove_button {
display: inline-block;
background-repeat: no-repeat;
background: url('/assets/packages/static/openvk/img/arrows.png?v=2');
@ -1493,7 +1547,7 @@ body.scrolled .toTop:hover, .toTop.has_down:hover {
cursor: pointer;
}
.post-source #remove_source_button:hover {
.post-source #remove_source_button:hover, #small_remove_button:hover {
opacity: 0.8;
}
@ -1563,6 +1617,15 @@ body.scrolled .toTop:hover, .toTop.has_down:hover {
float: right;
}
.like_wrap.tidy {
float: none;
display: inline-block;
}
.like_wrap.tidy .post-like-button {
display: flex;
}
.heart {
background: url('/assets/packages/static/openvk/img/like.gif') no-repeat 1px 0;
height: 10px;
@ -1572,12 +1635,22 @@ body.scrolled .toTop:hover, .toTop.has_down:hover {
opacity: 0.4;
}
.like_wrap.tidy .heart {
float: none;
display: inline-block;
}
.likeCnt {
font-size: 10px;
margin-right: 3px;
float: left;
}
.like_wrap.tidy .likeCnt {
float: none;
display: inline;
}
.heart:hover {
opacity: 1 !important;
}
@ -1741,12 +1814,14 @@ body.scrolled .toTop:hover, .toTop.has_down:hover {
#wallAttachmentMenu {
position: absolute;
min-width: 94px;
border: 1px solid darkgrey;
background-color: white;
z-index: 32;
margin-top: -7px;
margin-left: -16px;
box-shadow: 0 1px 3px -1px;
user-select: none;
}
#wallAttachmentMenu>a {
@ -2145,6 +2220,10 @@ table td[width="120"] {
border-radius: 2px;
}
.mb_tab:hover {
background: #e2e0e0;
}
.mb_tab div, .mb_tab > a {
padding: 5px 9px;
display: block;
@ -2400,26 +2479,66 @@ a.poll-retract-vote {
}
}
.upload {
margin-top: 8px;
.post-horizontal, .post-vertical {
margin-top: 0px;
}
.upload .upload-item {
width: 75px;
height: 60px;
.post-horizontal {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 3px;
}
.post-vertical {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 3px;
}
.post-horizontal .upload-item {
/*width: 75px;
height: 60px;*/
overflow: hidden;
display: inline-block;
margin-right: 3px;
display: inline-flex;
justify-content: center;
align-items: center;
position: relative;
min-height: 63px;
}
.upload-item .upload-delete {
.post-horizontal .upload-item .play-button, .compact_video .play-button {
position: absolute;
height: 30px;
width: 30px;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
}
.post-horizontal .upload-item .play-button .play-button-ico, .compact_video .play-button .play-button-ico {
background: url(/assets/packages/static/openvk/img/wall.png) no-repeat 1px 0;
display: inline-block;
height: 15px;
width: 12px;
}
.upload-item.dragged {
filter: contrast(0.5);
}
.post-horizontal .upload-item .upload-delete {
position: absolute;
background: rgba(0,0,0,0.5);
padding: 2px 5px;
text-decoration: none;
color: #fff;
font-size: 11px;
margin-left: 57px; /* мне лень переделывать :DDDD */
right: 0px;
top: 0px;
opacity: 0;
transition: 0.25s;
}
@ -2428,13 +2547,68 @@ a.poll-retract-vote {
opacity: 1;
}
.upload-item img {
.post-horizontal .upload-item img {
width: 100%;
max-height: 60px;
object-fit: cover;
border-radius: 3px;
}
.post-vertical .vertical-attachment {
display: grid;
grid-template-columns: 1fr 0fr;
}
.post-vertical .vertical-attachment:first-of-type {
margin-top: 7px;
}
.post-vertical .vertical-attachment .audioEntry {
max-height: 28px;
min-height: 18px;
}
.post-vertical .vertical-attachment .audioEntry:hover {
background: unset !important;
}
.post-vertical .vertical-attachment .audioEntry .playerButton {
padding: 0px 3px 0px 0px;
}
.post-vertical .vertical-attachment .audioEntry .buttons {
display: none;
}
.post-vertical .vertical-attachment .audioEntry .status {
margin-top: 1px;
margin-left: 2px;
height: 15px;
}
.post-vertical .vertical-attachment .audioEntry .nobold {
margin-top: 1px;
}
.post-vertical .vertical-attachment .audioEntry .subTracks {
padding-bottom: 2px;
padding-left: 3px;
padding-right: 0px;
}
.post-vertical .vertical-attachment .audioEntry .audioEntryWrapper {
height: 18px;
}
.post-vertical .vertical-attachment .vertical-attachment-content {
max-height: 27px;
}
.post-vertical .vertical-attachment .vertical-attachment-content .overflowedName {
position: initial;
width: 100% !important;
}
/* https://imgur.com/a/ihB3JZ4 */
.ovk-photo-view-dimmer {
@ -2452,21 +2626,33 @@ a.poll-retract-vote {
position: relative;
z-index: 999;
background: #fff;
width: 610px;
padding: 20px;
min-width: 600px;
width: fit-content;
padding: 25px;
padding-top: 15px;
padding-bottom: 10px;
box-shadow: 0px 0px 3px 1px #222;
margin: 15px auto 0 auto;
margin: 10px auto 0 auto;
}
.ovk-photo-details {
overflow: auto;
.ovk-photo-view .photo_viewer_wrapper {
position: relative;
height: 80vh;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.ovk-photo-view #ovk-photo-img {
max-width: 100%;
max-height: 80vh;
user-select: none;
}
.photo_com_title {
font-weight: bold;
padding-bottom: 20px;
padding-bottom: 16px;
}
.photo_com_title div {
@ -2478,7 +2664,6 @@ a.poll-retract-vote {
left: 0;
width: 35%;
height: 100%;
max-height: 60vh;
position: absolute;
cursor: pointer;
}
@ -2487,11 +2672,27 @@ a.poll-retract-vote {
right: 0;
width: 35%;
height: 100%;
max-height: 60vh;
position: absolute;
cursor: pointer;
}
.ovk-photo-view .media-page-wrapper-details {
display: flex;
gap: 10px;
}
.ovk-photo-view .media-page-wrapper-comments {
overflow-y: auto;
overflow-x: hidden;
min-height: 300px;
width: 100%;
padding: 0px 2px;
}
.ovk-photo-view .ovk-photo-details {
margin-top: 10px;
}
.client_app > img {
top: 3px;
position: relative;
@ -2549,12 +2750,13 @@ a.poll-retract-vote {
padding: 3px 0;
}
.video-wowzer a::before {
content: "b";
color: transparent;
width: 12px;
background-image: url(/assets/packages/static/openvk/img/videoico.png);
display: none;
.video-wowzer .small-video-ico {
vertical-align: bottom;
display: inline-block;
width: 11px;
height: 14px;
background: url(/assets/packages/static/openvk/img/wall.png?v=2);
background-position: -87px 0px;
}
/* Da search */
@ -2900,17 +3102,33 @@ a.poll-retract-vote {
width: 100%;
}
.attachment_note_icon {
.attachment_note .attachment_note_icon {
max-width: 9px;
}
.attachment_note_text {
.attachment_note .attachment_note_text {
color: #605F63;
margin-left: 2px;
}
.attachment_note {
user-select: none;
display: flex;
align-items: center;
gap: 1px;
}
.attachment_note svg {
width: 8px;
height: 10px;
fill: #605f63;
margin-top: 2px;
}
.attachment_note .attachment_note_content {
display: flex;
gap: 4px;
height: 12px;
}
#notesList
@ -3135,10 +3353,81 @@ body.article .floating_sidebar, body.article .page_content {
font-size: 11px;
}
.topGrayBlock {
.attachment_selector .topGrayBlock {
background: #F0F0F0;
height: 37px;
border-bottom: 1px solid #C7C7C7;
padding: 3px 10px;
align-items: center;
justify-content: center;
justify-content: space-between;
}
.attachment_selector .topGrayBlock #video_query {
width: 160px;
}
.attachment_selector .topGrayBlock #albumSelect {
width: 150px;
}
.attachment_selector #attachment_insert {
padding: 5px;
height: 281px;
overflow-y: scroll;
}
.attachment_selector #attachment_insert #attachment_insert_count {
position: fixed;
z-index: 1007;
width: 92%;
background: white;
margin-top: -5px;
padding-top: 6px;
}
#show_more {
width: 100%;
text-align: center;
background: #f0f0f0;
height: 22px;
padding-top: 9px;
cursor: pointer;
margin-top: 4px;
}
#show_more:hover {
background: #f2f2f2;
}
.attachment_selector #attachment_insert .photosList {
margin-top: 20px;
gap: 3px 6px;
}
.attachment_selector #attachment_insert .photosList .album-photo.selected img {
background-color: #646464;
}
.attachment_selector #attachment_insert .photosList .album-photo {
width: 15.8%;
margin: unset;
}
.attachment_selector #attachment_insert .videosInsert .video_list .video-preview {
height: 75px;
width: 133px;
overflow: hidden;
}
.attachment_selector #attachment_insert .videosInsert .video_list .video-preview img {
max-width: 133px;
height: 75px;
margin: auto;
}
.attachment_selector #attachment_insert .videosInsert .video_list .action_links {
width: 150px;
}
.edited {
@ -3156,16 +3445,20 @@ body.article .floating_sidebar, body.article .page_content {
user-select: none;
}
.editMenu.loading {
.edit_menu.loading {
filter: opacity(0.5);
cursor: progress;
user-select: none;
}
.editMenu.loading * {
.edit_menu.loading * {
pointer-events: none;
}
.edit_menu .edit_menu_buttons {
margin-top: 10px;
}
.lagged *, .lagged {
pointer-events: none;
}
@ -3288,6 +3581,7 @@ hr {
flex-direction: row;
justify-content: space-between;
gap: 4px;
cursor: pointer;
}
.entity_vertical_list .entity_vertical_list_item .first_column {
@ -3325,3 +3619,167 @@ hr {
width: 30px;
height: 7px;
}
.compact_video {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
position: relative;
margin-left: -2px;
margin-top: -2px;
}
.compact_video .video-length {
text-align: center;
position: absolute;
width: 34px;
height: 14px;
bottom: 5px;
right: 5px;
background: rgba(0,0,0,0.5);
color: white;
}
.media-page-wrapper .photo-page-wrapper-photo {
margin-bottom: 8pt;
text-align: center;
}
.media-page-wrapper .video-page-wrapper-video iframe {
width: 100%;
}
.media-page-wrapper .photo-page-wrapper-photo img {
max-width: 85%;
max-height: 60vh;
}
.media-page-wrapper-details {
width: 100%;
min-height: 100px;
display: flex;
justify-content: space-between;
gap: 10px;
}
.media-page-wrapper-comments {
min-height: 100px;
width: 68%;
margin-left: 3px;
}
.media-page-wrapper-actions {
min-height: 100px;
width: 30%;
margin-left: 1px;
}
.media-page-wrapper-description p {
margin: 0px;
}
.media-page-wrapper-description .upload_time {
color: gray;
display: inline-flex;
align-items: center;
height: 16px;
}
.media-page-author-block {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 5px;
}
.media-page-author-block img {
width: 30px;
}
.media-page-author-block .media-page-author-block-name {
display: flex;
flex-direction: column;
justify-content: center;
}
.media-page-author-block .media-page-author-block-name b {
font-weight: bold;
}
.media-page-author-block .media-page-author-block-name span {
text-transform: lowercase;
}
#ajloader {
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;
}
#ajloader.shown {
display: block;
}
.hoverable_color {
transition: all 0.17s ease-in-out;
}
.hoverable_color:hover {
color: white !important;
}
#__feed_settings_link {
float: right;
margin-right: 3px;
margin-top: 2px;
}
#__feed_settings_link:hover {
text-decoration: underline;
}
#_feed_settings_container {
height: 100%;
}
#_feed_settings_container #__content {
display: flex;
flex-direction: column;
gap: 4px;
padding: 5px;
height: 100%;
}
#_feed_settings_container #__content .settings_item {
display: flex;
flex-direction: column;
}
#_feed_settings_container #__content .final_settings_item {
align-self: end;
margin-top: 74px;
}
#_feed_settings_container #pageNumber {
width: 70px;
}
#_feed_settings_container .entity_vertical_list {
height: 206px;
overflow-x: hidden;
}

View file

@ -1,28 +1,28 @@
.post-nsfw .post-content .media {
filter: saturate(0.8) blur(15px);
}
.post-nsfw .post-content .attachment {
overflow: hidden;
.post-nsfw .post-content {
position: relative;
overflow: hidden;
cursor: pointer;
}
.post-nsfw .post-content .attachment:active .media {
filter: none;
.post-nsfw .post-content .text {
filter: saturate(0.8) blur(28px);
pointer-events: none;
}
.post-nsfw .post-content .attachment::after {
.post-nsfw .post-content::after {
position: absolute;
top: calc(50% - 16px);
top: 0;
left: 0;
width: 100%;
padding: 8px 0;
background-color: hsla(0, 0%, 0%, .5);
height: 100%;
z-index: 1;
background-color: hsla(0, 0%, 0%, .7);
color: #fff;
text-align: center;
content: attr(data-localized-nsfw-text);
}
.post-nsfw .post-content .attachment:active::after {
display: none;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

View file

@ -1 +0,0 @@
<svg id="note" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 40"><defs><style>.cls-1{fill:#a0a0a0;}</style></defs><title>note</title><polygon class="cls-1" points="0 0 0 40 35 40 35 20 17.5 20 17.5 0 0 0"/><polygon id="block" class="cls-1" points="20.26 1 35 18 20.26 18 20.26 1"/></svg>

Before

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -34,4 +34,22 @@ window.API = new Proxy(Object.create(null), {
window.API.Types = {};
window.API.Types.Message = (class Message {
});
});
window.OVKAPI = new class {
async call(method, params) {
if(!method) {
return
}
const url = `/method/${method}?auth_mechanism=roaming&${new URLSearchParams(params).toString()}&v=5.200`
const res = await fetch(url)
const json_response = await res.json()
if(json_response.response) {
return json_response.response
} else {
throw new Error(json_response.error_msg)
}
}
}

270
Web/static/js/al_feed.js Normal file
View file

@ -0,0 +1,270 @@
//u('.postFeedPageSelect').attr('style', 'display:none')
// Source ignoring
u(document).on("click", "#__ignoreSomeone", async (e) => {
e.preventDefault()
const TARGET = u(e.target)
const ENTITY_ID = Number(e.target.dataset.id)
const VAL = Number(e.target.dataset.val)
const ACT = VAL == 1 ? 'ignore' : 'unignore'
const METHOD_NAME = ACT == 'ignore' ? 'addBan' : 'deleteBan'
const PARAM_NAME = ENTITY_ID < 0 ? 'group_ids' : 'user_ids'
const ENTITY_NAME = ENTITY_ID < 0 ? 'club' : 'user'
const URL = `/method/newsfeed.${METHOD_NAME}?auth_mechanism=roaming&${PARAM_NAME}=${Math.abs(ENTITY_ID)}`
TARGET.addClass('lagged')
const REQ = await fetch(URL)
const RES = await REQ.json()
TARGET.removeClass('lagged')
if(RES.error_code) {
switch(RES.error_code) {
case -10:
fastError(';/')
break
case -50:
fastError(tr('ignored_sources_limit'))
break
default:
fastError(res.error_msg)
break
}
return
}
if(RES.response == 1) {
if(ACT == 'unignore') {
TARGET.attr('data-val', '1')
TARGET.html(tr(`ignore_${ENTITY_NAME}`))
} else {
TARGET.attr('data-val', '0')
TARGET.html(tr(`unignore_${ENTITY_NAME}`))
}
}
})
u(document).on('click', '#__feed_settings_link', (e) => {
e.preventDefault()
let current_tab = 'main';
const body = `
<div id='_feed_settings_container'>
<div id='_tabs'>
<div class="mb_tabs">
<div class="mb_tab" data-name='main'>
<a>
${tr('main')}
</a>
</div>
<div class="mb_tab" data-name='ignored'>
<a>
${tr('ignored_sources')}
</a>
</div>
</div>
</div>
<div id='__content'></div>
</div>
`
MessageBox(tr("feed_settings"), body, [tr("close")], [Function.noop])
u('.ovk-diag-body').attr('style', 'padding:0px;height: 255px;')
async function __switchTab(tab)
{
current_tab = tab
u(`#_feed_settings_container .mb_tab`).attr('id', 'ki')
u(`#_feed_settings_container .mb_tab[data-name='${tab}']`).attr('id', 'active')
u(`#_feed_settings_container .mb_tabs input`).remove()
switch(current_tab) {
case 'main':
const __temp_url = new URL(location.href)
const PAGES_COUNT = Number(e.target.dataset.pagescount ?? '10')
const CURRENT_PERPAGE = Number(__temp_url.searchParams.get('posts') ?? 10)
const CURRENT_PAGE = Number(__temp_url.searchParams.get('p') ?? 1)
const CURRENT_RETURN_BANNED = Number(__temp_url.searchParams.get('return_banned') ?? 0)
const COUNT = [1, 5, 10, 20, 30, 40, 50]
u('#_feed_settings_container #__content').html(`
<table cellspacing="7" cellpadding="0" border="0" align="center">
<tbody>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr('posts_per_page')}</span>
</td>
<td>
<select id="pageSelect"></select>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr('start_from_page')}</span>
</td>
<td>
<input type='number' min='1' max='${PAGES_COUNT}' id='pageNumber' value='${CURRENT_PAGE}' placeholder='${CURRENT_PAGE}'>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">
<input type='checkbox' id="showIgnored" ${CURRENT_RETURN_BANNED == 1 ? 'checked' : ''}>
</span>
</td>
<td>
${tr('show_ignored_sources')}
</td>
</tr>
<tr>
<td width="120" valign="top">
</td>
<td>
<input class='button' type='button' value='${tr('apply')}'>
</td>
</tr>
</tbody>
</table>
`)
u(`#_feed_settings_container #__content input[type='button']`).on('click', (e) => {
const INPUT_PAGES_COUNT = parseInt(u('#_feed_settings_container #__content #pageSelect').nodes[0].selectedOptions[0].value ?? '10')
const INPUT_PAGE = parseInt(u('#_feed_settings_container #__content #pageNumber').nodes[0].value ?? '1')
const INPUT_IGNORED = Number(u('#_feed_settings_container #__content #showIgnored').nodes[0].checked ?? false)
const FINAL_URL = new URL(location.href)
if(CURRENT_PERPAGE != INPUT_PAGES_COUNT) {
FINAL_URL.searchParams.set('posts', INPUT_PAGES_COUNT)
}
if(CURRENT_PAGE != INPUT_PAGE && INPUT_PAGE <= PAGES_COUNT) {
FINAL_URL.searchParams.set('p', Math.max(1, INPUT_PAGE))
}
if(INPUT_IGNORED == 1) {
FINAL_URL.searchParams.set('return_banned', 1)
} else {
FINAL_URL.searchParams.delete('return_banned')
}
window.location.assign(FINAL_URL.href)
})
COUNT.forEach(item => {
u('#_feed_settings_container #pageSelect').append(`
<option value="${item}" ${item == CURRENT_PERPAGE ? 'selected' : ''}>${item}</option>
`)
})
break
case 'ignored':
u('#_feed_settings_container #__content').html(`
<div id='gif_loader'></div>
`)
if(!window.openvk.ignored_list) {
const IGNORED_RES = await fetch('/method/newsfeed.getBanned?auth_mechanism=roaming&extended=1&fields=real_id,screen_name,photo_50&merge=1')
const IGNORED_LIST = await IGNORED_RES.json()
window.openvk.ignored_list = IGNORED_LIST
}
u('#_feed_settings_container #__content').html(`
<div class='entity_vertical_list mini'></div>
`)
u('#_feed_settings_container .mb_tabs').append(`
<input class='button lagged' id='_remove_ignores' type='button' value='${tr('stop_ignore')}'>
`)
if(window.openvk.ignored_list.error_code) {
fastError(IGNORED_LIST.error_msg)
return
}
if(window.openvk.ignored_list.response.items.length < 1) {
u('#_feed_settings_container #__content').html(`
<div class="information">
${tr('no_ignores_count')}
</div>`)
u('#_remove_ignores').remove()
}
window.openvk.ignored_list.response.items.forEach(ignore_item => {
let name = ignore_item.name
if(!name) {
name = ignore_item.first_name + ' ' + ignore_item.last_name
}
u('#_feed_settings_container #__content .entity_vertical_list').append(`
<label class='entity_vertical_list_item with_third_column' data-id='${ignore_item.real_id}'>
<div class='first_column'>
<a href='/${ignore_item.screen_name}' class='avatar'>
<img src='${ignore_item.photo_50}'>
</a>
<div class='info'>
<b class='noOverflow'>
<a href="/${ignore_item.screen_name}">
${ovk_proc_strtr(escapeHtml(name), 100)}
</a>
</b>
</div>
</div>
<div class='third_column' style="display: grid; align-items: center;">
<input type='checkbox' name='remove_me'>
</div>
</label>
`)
})
u("#_feed_settings_container").on("click", "input[name='remove_me']", async (e) => {
const checks_count = u(`input[name='remove_me']:checked`).length
if(checks_count > 0) {
u('.mb_tabs #_remove_ignores').removeClass('lagged')
} else {
u('.mb_tabs #_remove_ignores').addClass('lagged')
}
if(checks_count > 10) {
e.preventDefault()
}
})
u('#_feed_settings_container').on('click', '#_remove_ignores', async (e) => {
e.target.classList.add('lagged')
const ids = []
u('#__content .entity_vertical_list label').nodes.forEach(item => {
const _checkbox = item.querySelector(`input[type='checkbox'][name='remove_me']`)
if(_checkbox.checked) {
ids.push(item.dataset.id)
}
})
const user_ids = []
const group_ids = []
ids.forEach(id => {
id > 0 ? user_ids.push(id) : group_ids.push(Math.abs(id))
})
const res = await fetch(`/method/newsfeed.deleteBan?auth_mechanism=roaming&user_ids=${user_ids.join(',')}&group_ids=${group_ids.join(',')}`)
const resp = await res.json()
if(resp.error_code) {
console.error(resp.error_msg)
return
}
window.openvk.ignored_list = null
__switchTab('ignored')
})
break
}
}
u("#_feed_settings_container").on("click", ".mb_tab a", async (e) => {
await __switchTab(u(e.target).closest('.mb_tab').attr('data-name'))
})
__switchTab('main')
})

View file

@ -1,13 +1,3 @@
function fmtTime(time) {
const mins = String(Math.floor(time / 60)).padStart(2, '0');
const secs = String(Math.floor(time % 60)).padStart(2, '0');
return `${ mins}:${ secs}`;
}
function fastError(message) {
MessageBox(tr("error"), message, [tr("ok")], [Function.noop])
}
// elapsed это вроде прошедшие, а оставшееся это remaining но ладно уже
function getElapsedTime(fullTime, time) {
let timer = fullTime - time
@ -1354,8 +1344,8 @@ $(document).on("click", "#_deletePlaylist", (e) => {
}, Function.noop])
})
$(document).on("click", "#_audioAttachment", (e) => {
let form = e.currentTarget.closest("form")
$(document).on("click", "#__audioAttachment", (e) => {
const form = e.target.closest("form")
let body = `
<div class="searchBox">
<input name="query" type="text" maxlength="50" placeholder="${tr("header_search")}">
@ -1367,7 +1357,7 @@ $(document).on("click", "#_audioAttachment", (e) => {
<div class="audiosInsert"></div>
`
MessageBox(tr("select_audio"), body, [tr("ok")], [Function.noop])
MessageBox(tr("select_audio"), body, [tr("close")], [Function.noop])
document.querySelector(".ovk-diag-body").style.padding = "0"
document.querySelector(".ovk-diag-cont").style.width = "580px"
@ -1388,23 +1378,24 @@ $(document).on("click", "#_audioAttachment", (e) => {
result.querySelectorAll(".audioEmbed").forEach(el => {
let id = el.dataset.prettyid
let name = el.dataset.name
let isAttached = (form.querySelector("input[name='audios']").value.includes(`${id},`))
const is_attached = (u(form).find(`.post-vertical .vertical-attachment[data-id='${id}']`)).length > 0
document.querySelector(".audiosInsert").insertAdjacentHTML("beforeend", `
<div style="display: table;width: 100%;clear: both;">
<div style="width: 72%;float: left;">${el.outerHTML}</div>
<div class="attachAudio" data-attachmentdata="${id}" data-name="${name}">
<span>${isAttached ? tr("detach_audio") : tr("attach_audio")}</span>
<div class='audio_attachment_header' style="display: flex;width: 100%;">
<div class='player_part' style="width: 72%;">${el.outerHTML}</div>
<div class="attachAudio" data-attachmentdata="${id}">
<span>${is_attached ? tr("detach_audio") : tr("attach_audio")}</span>
</div>
</div>
`)
})
u("#loader").remove()
u('#show_more').remove()
if(thisc.page < pagesCount) {
document.querySelector(".audiosInsert").insertAdjacentHTML("beforeend", `
<div id="showMoreAudios" data-pagesCount="${pagesCount}" data-page="${thisc.page + 1}" class="showMore">
<div id="show_more" data-pagesCount="${pagesCount}" data-page="${thisc.page + 1}" class="showMore">
<span>${tr("show_more_audios")}</span>
</div>`)
}
@ -1424,14 +1415,12 @@ $(document).on("click", "#_audioAttachment", (e) => {
searcher.movePage(1)
$(".audiosInsert").on("click", "#showMoreAudios", (e) => {
u(e.currentTarget).remove()
u(".audiosInsert").on("click", "#show_more", async (e) => {
u(e.target).closest('#show_more').addClass('lagged')
searcher.movePage(Number(e.currentTarget.dataset.page))
})
$(".searchBox input").on("change", async (e) => {
await new Promise(r => setTimeout(r, 500));
if(e.currentTarget.value === document.querySelector(".searchBox input").value) {
searcher.clearContainer()
@ -1462,44 +1451,34 @@ $(document).on("click", "#_audioAttachment", (e) => {
return;
})
function insertAttachment(id) {
let audios = form.querySelector("input[name='audios']")
u(".audiosInsert").on("click", ".attachAudio", (ev) => {
const id = ev.currentTarget.dataset.attachmentdata
const is_attached = u(form).find(`.post-vertical .vertical-attachment[data-id='${id}']`).length > 0
if(!audios.value.includes(id + ",")) {
if(audios.value.split(",").length > 10) {
NewNotification(tr("error"), tr("max_attached_audios"))
return false
// 04.11.2024 19:03
if(is_attached) {
u(form).find(`.post-vertical .vertical-attachment[data-id='${id}']`).remove()
u(ev.currentTarget).find("span").html(tr("attach_audio"))
} else {
if(u(form).find(`.upload-item`).length > window.openvk.max_attachments) {
makeError(tr('too_many_attachments'), 'Red', 10000, 1)
return
}
form.querySelector("input[name='audios']").value += (id + ",")
u(ev.currentTarget).find("span").html(tr("detach_audio"))
return true
} else {
form.querySelector("input[name='audios']").value = form.querySelector("input[name='audios']").value.replace(id + ",", "")
return false
}
}
$(".audiosInsert").on("click", ".attachAudio", (ev) => {
if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) {
u(`.post-has-audios .post-has-audio[data-id='${ev.currentTarget.dataset.attachmentdata}']`).remove()
ev.currentTarget.querySelector("span").innerHTML = tr("attach_audio")
} else {
ev.currentTarget.querySelector("span").innerHTML = tr("detach_audio")
form.querySelector(".post-has-audios").insertAdjacentHTML("beforeend", `
<div class="post-has-audio" id="unattachAudio" data-id="${ev.currentTarget.dataset.attachmentdata}">
<span>${ovk_proc_strtr(escapeHtml(ev.currentTarget.dataset.name), 40)}</span>
const header = u(ev.currentTarget).closest('.audio_attachment_header')
const player = header.find('.player_part')
u(form).find(".post-vertical").append(`
<div class="vertical-attachment upload-item" data-type='audio' data-id="${ev.currentTarget.dataset.attachmentdata}">
<div class='vertical-attachment-content'>
${player.html()}
</div>
<div class='vertical-attachment-remove'>
<div id='small_remove_button'></div>
</div>
</div>
`)
u(`#unattachAudio[data-id='${ev.currentTarget.dataset.attachmentdata}']`).on("click", (e) => {
let id = ev.currentTarget.dataset.attachmentdata
form.querySelector("input[name='audios']").value = form.querySelector("input[name='audios']").value.replace(id + ",", "")
u(e.currentTarget).remove()
})
}
})
})
@ -1509,7 +1488,13 @@ $(document).on("click", ".audioEmbed.processed .playerButton", (e) => {
})
$(document).on("click", ".audioEmbed.withdrawn", (e) => {
MessageBox(tr("error"), tr("audio_embed_withdrawn"), [tr("ok")], [Function.noop])
const msg = new CMessageBox({
title: tr('error'),
body: tr('audio_embed_withdrawn'),
unique_name: 'withdrawn_notify',
buttons: [tr('ok')],
callbacks: [Function.noop]
})
})
$(document).on("click", ".musicIcon.report-icon", (e) => {

View file

@ -64,8 +64,9 @@ function pollRadioPressed(radio) {
form.submit();
}
function initPoll(id) {
let form = $(`#wall-post-input${id}`).parent();
function initPoll(event) {
let form = $(event.target.closest('.post-buttons')).parent();
const id = random_int(0, 100)
let mBody = `
<div id="poll_editor${id}">
@ -87,9 +88,9 @@ function initPoll(id) {
</div>
`;
MessageBox(tr("create_poll"), mBody, [tr("attach"), tr("cancel")], [
const msg = MessageBox(tr("create_poll"), mBody, [tr("attach"), tr("cancel")], [
function() {
let dialog = $(this.$dialog().nodes[0]);
let dialog = $(msg.getNode().nodes[0]);
$("input", dialog).unbind();
let title = $("input[name=title]", dialog).val();
@ -117,9 +118,9 @@ function initPoll(id) {
$(".post-has-poll", form).show();
},
function() {
$("input", $(this.$dialog().nodes[0])).unbind();
$("input", $(msg.getNode())).unbind();
}
]);
], true);
let editor = $(`#poll_editor${id}`);
$("input[name=newOption]", editor).bind("focus", function() {

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,177 @@
Function.noop = () => {};
function MessageBox(title, body, buttons, callbacks) {
if(u(".ovk-diag-cont").length > 0) return false;
document.querySelector("html").style.overflowY = "hidden"
let dialog = u(
`<div class="ovk-diag-cont">
<div class="ovk-diag">
<div class="ovk-diag-head">${title}</div>
<div class="ovk-diag-body">${body}</div>
<div class="ovk-diag-action"></div>
</div>
</div>`);
u("body").addClass("dimmed").append(dialog);
buttons.forEach((text, callback) => {
u(".ovk-diag-action").append(u(`<button class="button">${text}</button>`));
let button = u(u(".ovk-diag-action > button.button").last());
class CMessageBox {
constructor(options = {}) {
const title = options.title ?? 'Untitled'
const body = options.body ?? '<hr>'
const buttons = options.buttons ?? []
const callbacks = options.callbacks ?? []
const close_on_buttons = options.close_on_buttons ?? true
const unique_name = options.unique_name ?? null
const warn_on_exit = options.warn_on_exit ?? false
const custom_template = options.custom_template ?? null
if(unique_name && window.messagebox_stack.find(item => item.unique_name == unique_name) != null) {
return
}
this.title = title
this.body = body
this.id = random_int(0, 10000)
this.close_on_buttons = close_on_buttons
this.unique_name = unique_name
this.warn_on_exit = warn_on_exit
if(!custom_template) {
u('body').addClass('dimmed').append(this.__getTemplate())
} else {
custom_template.addClass('ovk-msg-all')
custom_template.attr('data-id', this.id)
u('body').addClass('dimmed').append(custom_template)
}
button.on("click", function(e) {
let __closeDialog = () => {
if(document.querySelector(".ovk-photo-view-dimmer") == null && document.querySelector(".ovk-fullscreen-player") == null) {
u("body").removeClass("dimmed");
document.querySelector("html").style.overflowY = "scroll"
u('html').attr('style', 'overflow-y:hidden')
buttons.forEach((text, callback) => {
this.getNode().find('.ovk-diag-action').append(u(`<button class="button">${text}</button>`))
let button = u(this.getNode().find('.ovk-diag-action > button.button').last())
button.on("click", (e) => {
callbacks[callback]()
if(close_on_buttons) {
this.close()
}
u(".ovk-diag-cont").remove();
};
Reflect.apply(callbacks[callback], {
closeDialog: () => __closeDialog(),
$dialog: () => u(".ovk-diag-cont")
}, [e]);
})
})
window.messagebox_stack.push(this)
}
__getTemplate() {
return u(
`<div class="ovk-diag-cont ovk-msg-all" data-id="${this.id}">
<div class="ovk-diag">
<div class="ovk-diag-head">${this.title}</div>
<div class="ovk-diag-body">${this.body}</div>
<div class="ovk-diag-action"></div>
</div>
</div>`)
}
getNode() {
return u(`.ovk-msg-all[data-id='${this.id}']`)
}
async __showCloseConfirmationDialog() {
return new Promise((resolve, reject) => {
const msg = new CMessageBox({
title: tr('exit_noun'),
body: tr('exit_confirmation'),
warn_on_exit: false,
unique_name: 'close_confirmation',
buttons: [tr('no'), tr('yes')],
callbacks: [() => {
msg.close()
resolve(false)
}, () => {
this.__exitDialog()
resolve(true)
}]
})
})
}
__exitDialog() {
this.getNode().remove()
if(u('.ovk-msg-all').length < 1) {
u('body').removeClass('dimmed')
u('html').attr('style', 'overflow-y:scroll')
}
const current_item = window.messagebox_stack.find(item => item.id == this.id)
const index_of_item = window.messagebox_stack.indexOf(current_item)
window.messagebox_stack = array_splice(window.messagebox_stack, index_of_item)
__closeDialog();
});
});
return u(".ovk-diag-cont");
delete this
}
close() {
this.__exitDialog()
}
hide() {
u('body').removeClass('dimmed')
u('html').attr('style', 'overflow-y:scroll')
this.getNode().attr('style', 'display: none;')
}
reveal() {
u('body').addClass('dimmed')
u('html').attr('style', 'overflow-y:hidden')
this.getNode().attr('style', 'display: block;')
}
static toggleLoader() {
u('#ajloader').toggleClass('shown')
}
}
window.messagebox_stack = []
function MessageBox(title, body, buttons, callbacks, return_msg = false) {
const msg = new CMessageBox({
title: title,
body: body,
buttons: buttons,
callbacks: callbacks,
})
if(return_msg) {
return msg
}
return msg.getNode()
}
// Close on 'Escape' key
u(document).on('keyup', async (e) => {
if(e.keyCode == 27 && window.messagebox_stack.length > 0) {
const msg = window.messagebox_stack[window.messagebox_stack.length - 1]
if(!msg) {
return
}
if(msg.close_on_buttons) {
msg.close()
return
}
if(msg.warn_on_exit) {
const res = await msg.__showCloseConfirmationDialog()
if(res === true) {
msg.close()
}
}
}
})
// Close when clicking on shadow
u(document).on('click', 'body.dimmed .dimmer', async (e) => {
if(u(e.target).hasClass('dimmer')) {
const msg = window.messagebox_stack[window.messagebox_stack.length - 1]
if(!msg) {
return
}
if(msg.close_on_buttons) {
msg.close()
return
}
if(msg.warn_on_exit) {
const res = await msg.__showCloseConfirmationDialog()
if(res === true) {
msg.close()
}
}
}
})

View file

@ -1,9 +1,5 @@

function expand_wall_textarea(id) {
var el = document.getElementById('post-buttons'+id);
var wi = document.getElementById('wall-post-input'+id);
el.style.display = "block";
wi.className = "expanded-textarea";
if(typeof u == 'undefined') {
console.error('!!! You forgot to install NPM packages !!!')
}
function expand_comment_textarea(id) {
@ -13,19 +9,6 @@ function expand_comment_textarea(id) {
wi.focus();
}
function edit_post(id, wid) {
var el = document.getElementById('text'+wid+'_'+id);
var ed = document.getElementById('text_edit'+wid+'_'+id);
if (el.style.display == "none") {
el.style.display = "block";
ed.style.display = "none";
} else {
el.style.display = "none";
ed.style.display = "block";
}
}
function hidePanel(panel, count = 0)
{
$(panel).toggleClass("content_title_expanded content_title_unexpanded");
@ -56,19 +39,9 @@ function parseAjaxResponse(responseString) {
}
}
function toggleMenu(id) {
if($(`#post-buttons${id} #wallAttachmentMenu`).is('.hidden')) {
$(`#post-buttons${id} #wallAttachmentMenu`).css({ opacity: 0 });
$(`#post-buttons${id} #wallAttachmentMenu`).toggleClass('hidden').fadeTo(250, 1);
} else {
$(`#post-buttons${id} #wallAttachmentMenu`).fadeTo(250, 0, function () {
$(this).toggleClass('hidden');
});
}
}
document.addEventListener("DOMContentLoaded", function() { //BEGIN
$(document).on("click", "#_photoDelete", function(e) {
$(document).on("click", "#_photoDelete, #_videoDelete", function(e) {
var formHtml = "<form id='tmpPhDelF' action='" + u(this).attr("href") + "' >";
formHtml += "<input type='hidden' name='hash' value='" + u("meta[name=csrf]").attr("value") + "' />";
formHtml += "</form>";
@ -186,74 +159,6 @@ document.addEventListener("DOMContentLoaded", function() { //BEGIN
})
}); //END ONREADY DECLS
async function repostPost(id, hash) {
uRepostMsgTxt = `
<b>${tr('auditory')}:</b> <br/>
<input type="radio" name="type" onchange="signs.setAttribute('hidden', 'hidden');document.getElementById('groupId').setAttribute('hidden', 'hidden')" value="wall" checked>${tr("in_wall")}<br/>
<input type="radio" name="type" onchange="signs.removeAttribute('hidden');document.getElementById('groupId').removeAttribute('hidden')" value="group" 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_${id}'></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>
<br/><br/>`;
let clubs = [];
repostsCount = document.getElementById("repostsCount"+id)
prevVal = repostsCount != null ? Number(repostsCount.innerHTML) : 0;
MessageBox(tr('share'), uRepostMsgTxt, [tr('send'), tr('cancel')], [
(function() {
text = document.querySelector("#uRepostMsgInput_"+id).value;
type = "user";
radios = document.querySelectorAll('input[name="type"]')
for(const r of radios)
{
if(r.checked)
{
type = r.value;
break;
}
}
groupId = document.querySelector("#groupId").value;
asGroup = asgroup.checked == true ? 1 : 0;
signed = signed.checked == true ? 1 : 0;
hash = encodeURIComponent(hash);
xhr = new XMLHttpRequest();
xhr.open("POST", "/wall"+id+"/repost?hash="+hash, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = (function() {
if(xhr.responseText.indexOf("wall_owner") === -1)
MessageBox(tr('error'), tr('error_repost_fail'), [tr('ok')], [Function.noop]);
else {
let jsonR = JSON.parse(xhr.responseText);
NewNotification(tr('information_-1'), tr('shared_succ'), null, () => {window.location.href = "/wall" + jsonR.wall_owner});
repostsCount != null ?
repostsCount.innerHTML = prevVal+1 :
document.getElementById("reposts"+id).insertAdjacentHTML("beforeend", "(<b id='repostsCount"+id+"'>1</b>)") //для старого вида постов
}
});
xhr.send('text='+encodeURI(text) + '&type='+type + '&groupId='+groupId + "&asGroup="+asGroup + "&signed="+signed);
}),
Function.noop
]);
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")
}
}
function setClubAdminComment(clubId, adminId, hash) {
MessageBox("Изменить комментарий к администратору", `
<form action="/club${clubId}/setAdmin" method="post" id="uClubAdminCommentForm_${clubId}_${adminId}">
@ -319,17 +224,6 @@ function showCoinsTransferDialog(coinsCount, hash) {
]);
}
function chunkSubstr(string, size) {
const numChunks = Math.ceil(string.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = string.substr(o, size);
}
return chunks;
}
function autoTab(original, next, previous) {
if(original.getAttribute && original.value.length == original.getAttribute("maxlength") && next !== undefined)
next.focus();
@ -361,11 +255,6 @@ function supportFastAnswerDialogOnClick(answer) {
answerInput.focus();
}
function ovk_proc_strtr(string, length = 0) {
const newString = string.substring(0, length);
return newString + (string !== newString ? "…" : "");
}
function showProfileDeactivateDialog(hash) {
MessageBox(tr("profile_deactivate"), `
<div class="messagebox-content-header">
@ -488,57 +377,6 @@ function showIncreaseRatingDialog(coinsCount, userUrl, hash) {
};
}
function escapeHtml(text) {
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function highlightText(searchText, container_selector, selectors = []) {
const container = u(container_selector)
const regexp = new RegExp(`(${searchText})`, 'gi')
function highlightNode(node) {
if(node.nodeType == 3) {
let newNode = escapeHtml(node.nodeValue)
newNode = newNode.replace(regexp, (match, ...args) => {
return `<span class='highlight'>${escapeHtml(match)}</span>`
})
const tempDiv = document.createElement('div')
tempDiv.innerHTML = newNode
while(tempDiv.firstChild) {
node.parentNode.insertBefore(tempDiv.firstChild, node)
}
node.parentNode.removeChild(node)
} else if(node.nodeType === 1 && node.tagName !== 'SCRIPT' && node.tagName !== 'BR' && node.tagName !== 'STYLE' && !node.classList.contains('highlight')) {
Array.from(node.childNodes).forEach(highlightNode);
}
}
selectors.forEach(selector => {
elements = container.find(selector)
if(!elements || elements.length < 1) return;
elements.nodes.forEach(highlightNode)
})
}
String.prototype.escapeHtml = function() {
try {
return escapeHtml(this)
} catch(e) {
return ''
}
}
$(document).on("scroll", () => {
if($(document).scrollTop() > $(".sidebar").height() + 50) {
$(".floating_sidebar")[0].classList.add("show");

File diff suppressed because it is too large Load diff

175
Web/static/js/utils.js Normal file
View file

@ -0,0 +1,175 @@
function escapeHtml(text) {
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function highlightText(searchText, container_selector, selectors = []) {
const container = u(container_selector)
const regexp = new RegExp(`(${searchText})`, 'gi')
function highlightNode(node) {
if(node.nodeType == 3) {
let newNode = escapeHtml(node.nodeValue)
newNode = newNode.replace(regexp, (match, ...args) => {
return `<span class='highlight'>${escapeHtml(match)}</span>`
})
const tempDiv = document.createElement('div')
tempDiv.innerHTML = newNode
while(tempDiv.firstChild) {
node.parentNode.insertBefore(tempDiv.firstChild, node)
}
node.parentNode.removeChild(node)
} else if(node.nodeType === 1 && node.tagName !== 'SCRIPT' && node.tagName !== 'BR' && node.tagName !== 'STYLE' && !node.classList.contains('highlight')) {
Array.from(node.childNodes).forEach(highlightNode);
}
}
selectors.forEach(selector => {
elements = container.find(selector)
if(!elements || elements.length < 1) return;
elements.nodes.forEach(highlightNode)
})
}
String.prototype.escapeHtml = function() {
try {
return escapeHtml(this)
} catch(e) {
return ''
}
}
function fmtTime(time) {
const mins = String(Math.floor(time / 60)).padStart(2, '0');
const secs = String(Math.floor(time % 60)).padStart(2, '0');
return `${ mins}:${ secs}`;
}
function fastError(message) {
MessageBox(tr("error"), message, [tr("ok")], [Function.noop])
}
function humanFileSize(bytes, si) {
var thresh = si ? 1000 : 1024;
if(Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = si
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1)+' '+units[u];
}
function trim(string) {
var newStr = string.substring(0, 10);
if(newStr.length !== string.length)
newStr += "…";
return newStr;
}
function trimNum(string, num) {
return ovk_proc_strtr(string, num);
}
function ovk_proc_strtr(string, length = 0) {
const newString = string.substring(0, length);
return newString + (string !== newString ? "…" : "");
}
function chunkSubstr(string, size) {
const numChunks = Math.ceil(string.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = string.substr(o, size);
}
return chunks;
}
function random_int(min, max) {
return Math.round(Math.random() * (max - min) + min)
}
function makeError(text, color = 'Red', timeout = 10000, uid = 0) {
const rand_id = uid != 0 ? uid : random_int(0, 10000)
if(uid != 0 && u(`.upLeftErrors .upLeftError[data-id='${uid}']`).length > 0) {
return
}
u('.upLeftErrors').append(`
<div class='upLeftError upLeftError${color}' data-id='${rand_id}'>${escapeHtml(text)}</div>
`)
setTimeout(() => {
u(`.upLeftError[data-id='${rand_id}']`).remove()
}, timeout)
}
function array_splice(array, key)
{
let resultArray = [];
for(let i = 0; i < array.length; i++){
if(i != key){
resultArray.push(array[i]);
}
}
return resultArray;
}
function strip_tags(text)
{
return text.replace(/(<([^>]+)>)/gi, "")
}
function find_author(id, profiles, groups)
{
if(id > 0) {
const profile = profiles.find(prof => prof.id == id)
if(profile) {
return profile
}
} else {
const group = groups.find(grou => grou.id == Math.abs(id))
if(group) {
return group
}
}
return null
}
function collect_attachments(target) {
const horizontal_array = []
const horizontal_attachments = target.find(`.post-horizontal > a`)
horizontal_attachments.nodes.forEach(_node => {
horizontal_array.push(`${_node.dataset.type}${_node.dataset.id}`)
})
const vertical_array = []
const vertical_attachments = target.find(`.post-vertical > .vertical-attachment`)
vertical_attachments.nodes.forEach(_node => {
vertical_array.push(`${_node.dataset.type}${_node.dataset.id}`)
})
return horizontal_array.concat(vertical_array)
}

View file

@ -1,424 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@atlassian/aui@^9.6.0":
version "9.6.0"
resolved "https://registry.yarnpkg.com/@atlassian/aui/-/aui-9.6.0.tgz#9f35e67359022f1e6d5efa2653e79aeec95b9e77"
integrity sha512-o/bCufj0tUU6pRk3AWoXlcyVMTMx4QswB1UY5oJWSjopA+z/QUx0fhc4rRIIbxP0MrJMNRDpgPyuzkoPb7Z7ow==
dependencies:
"@atlassian/tipsy" "1.3.3"
"@popperjs/core" "2.11.6"
backbone "1.4.1"
css.escape "1.5.1"
dompurify "2.4.5"
fancy-file-input "2.0.4"
jquery-ui "1.13.2"
skatejs "0.13.17"
skatejs-template-html "0.0.0"
trim-extra-html-whitespace "1.3.0"
underscore "1.13.6"
"@atlassian/tipsy@1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@atlassian/tipsy/-/tipsy-1.3.3.tgz#3f77754c4c70324c5c938e41abaa2ca682f22036"
integrity sha512-6jd9wdoiPdCbwsNi1Xrn/oMdGz22dKPeCoZ/cCGKqjnh+UYkBKb5W3spW+WNqRSxGvVtfUEEg6TXotRK/FPDaw==
"@popperjs/core@2.11.6", "@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"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
backbone@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.1.tgz#099a78184bc07b034048a8332229c2ccca1e3e62"
integrity sha512-ADy1ztN074YkWbHi8ojJVFe3vAanO/lrzMGZWUClIP7oDD/Pjy2vrASraUP+2EVCfIiTtCW4FChVow01XneivA==
dependencies:
underscore ">=1.8.3"
codem-isoboxer@0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/codem-isoboxer/-/codem-isoboxer-0.3.6.tgz#867f670459b881d44f39168d5ff2a8f14c16151d"
integrity sha512-LuO8/7LW6XuR5ERn1yavXAfodGRhuY2yP60JTZIw5yNYMCE5lUVbk3NFUCJxjnphQH+Xemp5hOGb1LgUXm00Xw==
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
create-react-class@^15.7.0:
version "15.7.0"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
cropperjs@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.6.1.tgz#fd132021d93b824b1b0f2c2c3b763419fb792d89"
integrity sha512-F4wsi+XkDHCOMrHMYjrTEE4QBOrsHHN5/2VsVAaRq8P7E5z7xQpT75S+f/9WikmBEailas3+yo+6zPIomW+NOA==
css.escape@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
dashjs@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/dashjs/-/dashjs-4.3.0.tgz#cccda5a490cabf6c3b48aa887ec8c8ac0df1a233"
integrity sha512-cqpnJaPQpEY4DsEdF9prwD00+5dp5EGHCFc7yo9n2uuAH9k4zPkZJwXQ8dXmVRhPf3M89JfKSoAYIP3dbXmqcg==
dependencies:
codem-isoboxer "0.3.6"
es6-promise "^4.2.8"
fast-deep-equal "2.0.1"
html-entities "^1.2.1"
imsc "^1.0.2"
localforage "^1.7.1"
dompurify@2.4.5:
version "2.4.5"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.5.tgz#0e89a27601f0bad978f9a924e7a05d5d2cccdd87"
integrity sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA==
encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
es6-promise@^4.2.8:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
event-lite@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.2.tgz#838a3e0fdddef8cc90f128006c8e55a4e4e4c11b"
integrity sha512-HnSYx1BsJ87/p6swwzv+2v6B4X+uxUteoDfRxsAb1S1BePzQqOLevVmkdA15GHJVd9A9Ok6wygUR18Hu0YeV9g==
fancy-file-input@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fancy-file-input/-/fancy-file-input-2.0.4.tgz#698c216482e07649a827681c4db3054fddc9a32b"
integrity sha512-l+J0WwDl4nM/zMJ/C8qleYnXMUJKsLng7c5uWH/miAiHoTvPDtEoLW1tmVO6Cy2O8i/1VfA+2YOwg/Q3+kgO6w==
fast-deep-equal@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fbjs@^0.8.0:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
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"
html-entities@^1.2.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
iconv-lite@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
id3js@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/id3js/-/id3js-2.1.1.tgz#0c307d0d2f194bc5fa7a809bbed0b1a93577f16d"
integrity sha512-9Gi+sG0RHSa5qn8hkwi2KCl+2jV8YrtiZidXbOO3uLfRAxc2jilRg0fiQ3CbeoAmR7G7ap3RVs1kqUVhIyZaog==
ieee754@^1.1.8:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
imsc@^1.0.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/imsc/-/imsc-1.1.3.tgz#e96a60a50d4000dd7b44097272768b9fd6a4891d"
integrity sha512-IY0hMkVTNoqoYwKEp5UvNNKp/A5jeJUOrIO7judgOyhHT+xC6PA4VBOMAOhdtAYbMRHx9DTgI8p6Z6jhYQPFDA==
dependencies:
sax "1.2.1"
int64-buffer@^0.1.9:
version "0.1.10"
resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.10.tgz#277b228a87d95ad777d07c13832022406a473423"
integrity sha1-J3siiofZWtd30HwTgyAiQGpHNCM=
is-stream@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
isarray@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
jquery-ui@1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.2.tgz#de03580ae6604773602f8d786ad1abfb75232034"
integrity sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==
dependencies:
jquery ">=1.8.0 <4.0.0"
"jquery@>=1.8.0 <4.0.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
jquery@^3.0.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612"
integrity sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
knockout@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/knockout/-/knockout-3.5.1.tgz#62c81e81843bea2008fd23c575edd9ca978e75cf"
integrity sha512-wRJ9I4az0QcsH7A4v4l0enUpkS++MBx0BnL/68KaLzJg7x1qmbjSlwEoCNol7KTYZ+pmtI7Eh2J0Nu6/2Z5J/Q==
ky@^0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/ky/-/ky-0.19.0.tgz#d6ad117e89efe2d85a1c2e91462d48ca1cda1f7a"
integrity sha512-RkDgbg5ahMv1MjHfJI2WJA2+Qbxq0iNSLWhreYiCHeHry9Q12sedCnP5KYGPt7sydDvsyH+8UcG6Kanq5mpsyw==
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"
literallycanvas@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/literallycanvas/-/literallycanvas-0.5.2.tgz#7d4800a8d9c4b38a593e91695d52466689586abd"
integrity sha512-SPxZ0DfzUWOXBPnsFD/Y1OulGKpv33IKGB+DCoUkNKQsc38kRDy1mGjbWbs/JAAyIGM1C9Y0CYI23SQmtXR5aw==
dependencies:
react-addons-pure-render-mixin "^15.1"
localforage@^1.7.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
dependencies:
lie "3.1.1"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
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"
integrity sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ==
msgpack-lite@^0.1.26:
version "0.1.26"
resolved "https://registry.yarnpkg.com/msgpack-lite/-/msgpack-lite-0.1.26.tgz#dd3c50b26f059f25e7edee3644418358e2a9ad89"
integrity sha1-3TxQsm8FnyXn7e42REGDWOKprYk=
dependencies:
event-lite "^0.1.1"
ieee754 "^1.1.8"
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"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
plotly.js-dist@^1.52.3:
version "1.52.3"
resolved "https://registry.yarnpkg.com/plotly.js-dist/-/plotly.js-dist-1.52.3.tgz#4c16c6da6adab6cdba169087b5005bdddbf10834"
integrity sha512-kpuNwveRk6M/5cCW1ZgJTbMLD0bRZhJlLK0cUHVkTsP/PWKCVJqO3QiiqrypFGE/xEhWfHCY+YKAKjMmEbo4Gw==
promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
dependencies:
asap "~2.0.3"
react-addons-pure-render-mixin@^15.1:
version "15.6.3"
resolved "https://registry.yarnpkg.com/react-addons-pure-render-mixin/-/react-addons-pure-render-mixin-15.6.3.tgz#5dc73af0fa32186dbc4887f20667b46d3f9caed7"
integrity sha512-e7F2OsLiyYGr9SHWHGlI/FfHRh+kbYx0hNfdN5zivHIf4vzeno7gsRJKXg71E35CpUCnre+JfM6UgWWgsvJBzA==
dependencies:
object-assign "^4.1.0"
react-dom-factories@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.2.tgz#eb7705c4db36fb501b3aa38ff759616aa0ff96e0"
integrity sha1-63cFxNs2+1AbOqOP91lhaqD/luA=
react-dom@15.1:
version "15.1.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.1.0.tgz#d0c2b24c8b47a41a2b9ec766662d4e686f353153"
integrity sha1-0MKyTItHpBornsdmZi1OaG81MVM=
react@15.1:
version "15.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-15.1.0.tgz#5f7a9f085a00509898efd2b24cb12ea1dfaf8b40"
integrity sha1-X3qfCFoAUJiY79KyTLEuod+vi0A=
dependencies:
fbjs "^0.8.0"
loose-envify "^1.1.0"
object-assign "^4.1.0"
requirejs@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9"
integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
skatejs-template-html@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/skatejs-template-html/-/skatejs-template-html-0.0.0.tgz#e990c1a7d4b58b7305ffcc3338939bf402023df7"
integrity sha1-6ZDBp9S1i3MF/8wzOJOb9AICPfc=
skatejs@0.13.17:
version "0.13.17"
resolved "https://registry.yarnpkg.com/skatejs/-/skatejs-0.13.17.tgz#7a21fbb3434da45e52b47b61647168ee9e778071"
integrity sha1-eiH7s0NNpF5StHthZHFo7p53gHE=
soundjs@^1.0.1:
version "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"
integrity sha1-tH77DRpfKlaoXMRc6lJWUek0BM8=
ua-parser-js@^0.7.18:
version "0.7.33"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
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"
integrity sha512-3qichMg1Q6EetLweBAT0L55O2W6CJe9qyiSt1RBnf+bcOqwJ4R7e2PDcoIUrCsg+uRo3DXOvurWdklBu0ia7fg==
underscore@1.13.6:
version "1.13.6"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441"
integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==
underscore@>=1.8.3:
version "1.13.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==
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==

View file

@ -232,49 +232,104 @@ function ovk_is_ssl(): bool
return $GLOBALS["requestIsSSL"];
}
function parseAttachments(string $attachments): array
function parseAttachments($attachments, array $allow_types = ['photo', 'video', 'note', 'audio']): array
{
$attachmentsArr = explode(",", $attachments);
$returnArr = [];
$exploded_attachments = is_array($attachments) ? $attachments : explode(",", $attachments);
$exploded_attachments = array_slice($exploded_attachments, 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"] ?? 10);
$exploded_attachments = array_unique($exploded_attachments);
$imploded_types = implode('|', $allow_types);
$output_attachments = [];
$repositories = [
'photo' => [
'repo' => 'openvk\Web\Models\Repositories\Photos',
'method' => 'getByOwnerAndVID',
],
'video' => [
'repo' => 'openvk\Web\Models\Repositories\Videos',
'method' => 'getByOwnerAndVID',
],
'audio' => [
'repo' => 'openvk\Web\Models\Repositories\Audios',
'method' => 'getByOwnerAndVID',
],
'note' => [
'repo' => 'openvk\Web\Models\Repositories\Notes',
'method' => 'getNoteById',
],
'poll' => [
'repo' => 'openvk\Web\Models\Repositories\Polls',
'method' => 'get',
'onlyId' => true,
],
];
foreach($attachmentsArr as $attachment) {
$attachmentType = NULL;
if(str_contains($attachment, "photo"))
$attachmentType = "photo";
elseif(str_contains($attachment, "video"))
$attachmentType = "video";
elseif(str_contains($attachment, "note"))
$attachmentType = "note";
elseif(str_contains($attachment, "audio"))
$attachmentType = "audio";
$attachmentIds = str_replace($attachmentType, "", $attachment);
$attachmentOwner = (int) explode("_", $attachmentIds)[0];
$gatoExplotano = explode("_", $attachmentIds);
$attachmentId = (int) end($gatoExplotano);
switch($attachmentType) {
case "photo":
$attachmentObj = (new openvk\Web\Models\Repositories\Photos)->getByOwnerAndVID($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
case "video":
$attachmentObj = (new openvk\Web\Models\Repositories\Videos)->getByOwnerAndVID($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
case "note":
$attachmentObj = (new openvk\Web\Models\Repositories\Notes)->getNoteById($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
case "audio":
$attachmentObj = (new openvk\Web\Models\Repositories\Audios)->getByOwnerAndVID($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
foreach($exploded_attachments as $attachment_string) {
if(preg_match("/$imploded_types/", $attachment_string, $matches) == 1) {
try {
$attachment_type = $matches[0];
if(!$repositories[$attachment_type])
continue;
$attachment_ids = str_replace($attachment_type, '', $attachment_string);
if($repositories[$attachment_type]['onlyId']) {
[$attachment_id] = array_map('intval', explode('_', $attachment_ids));
$repository_class = $repositories[$attachment_type]['repo'];
if(!$repository_class) continue;
$attachment_model = (new $repository_class)->{$repositories[$attachment_type]['method']}($attachment_id);
$output_attachments[] = $attachment_model;
} else {
[$attachment_owner, $attachment_id] = array_map('intval', explode('_', $attachment_ids));
$repository_class = $repositories[$attachment_type]['repo'];
if(!$repository_class) continue;
$attachment_model = (new $repository_class)->{$repositories[$attachment_type]['method']}($attachment_owner, $attachment_id);
$output_attachments[] = $attachment_model;
}
} catch(\Throwable) {continue;}
}
}
return $returnArr;
return $output_attachments;
}
function get_entity_by_id(int $id)
{
if($id > 0)
return (new openvk\Web\Models\Repositories\Users)->get($id);
return (new openvk\Web\Models\Repositories\Clubs)->get(abs($id));
}
function get_entities(array $ids = []): array
{
$main_result = [];
$users = [];
$clubs = [];
foreach($ids as $id) {
$id = (int)$id;
if($id < 0)
$clubs[] = abs($id);
if($id > 0)
$users[] = $id;
}
if(sizeof($users) > 0) {
$users_tmp = (new openvk\Web\Models\Repositories\Users)->getByIds($users);
foreach($users_tmp as $user) {
$main_result[] = $user;
}
}
if(sizeof($clubs) > 0) {
$clubs_tmp = (new openvk\Web\Models\Repositories\Clubs)->getByIds($clubs);
foreach($clubs_tmp as $club) {
$main_result[] = $club;
}
}
return $main_result;
}
function ovk_scheme(bool $with_slashes = false): string

373
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0d3b0eb1916eaf088444ef637743284a",
"content-hash": "987e26c5520b71fccd0cd31de00eead2",
"packages": [
{
"name": "al/emoji-detector",
@ -197,16 +197,16 @@
},
{
"name": "chillerlan/php-settings-container",
"version": "3.2.0",
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6"
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/8f93648fac8e6bacac8e00a8d325eba4950295e6",
"reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681",
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681",
"shasum": ""
},
"require": {
@ -214,15 +214,16 @@
"php": "^8.1"
},
"require-dev": {
"phan/phan": "^5.4",
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-deprecation-rules": "^1.2",
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.9"
"squizlabs/php_codesniffer": "^3.10"
},
"type": "library",
"autoload": {
"psr-4": {
"chillerlan\\Settings\\": "src/"
"chillerlan\\Settings\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -258,7 +259,7 @@
"type": "ko_fi"
}
],
"time": "2024-03-02T20:07:15+00:00"
"time": "2024-07-16T11:13:48+00:00"
},
{
"name": "erusev/parsedown",
@ -266,20 +267,20 @@
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "1ff038273949df7d6a455352659a878f3c89b29c"
"reference": "582f9f9cd12a894a0901bef1652854a3c5a1d874"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/1ff038273949df7d6a455352659a878f3c89b29c",
"reference": "1ff038273949df7d6a455352659a878f3c89b29c",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/582f9f9cd12a894a0901bef1652854a3c5a1d874",
"reference": "582f9f9cd12a894a0901bef1652854a3c5a1d874",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
"phpunit/phpunit": "^7.5|^8.5|^9.6"
},
"default-branch": true,
"type": "library",
@ -309,7 +310,7 @@
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/master"
},
"time": "2024-03-12T05:27:45+00:00"
"time": "2024-10-12T07:06:08+00:00"
},
{
"name": "ezyang/htmlpurifier",
@ -317,12 +318,12 @@
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "4828fdf45a93eeeacfcbcc855f96f9a7e6b4ed44"
"reference": "cb56001e54359df7ae76dc522d08845dc741621b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/4828fdf45a93eeeacfcbcc855f96f9a7e6b4ed44",
"reference": "4828fdf45a93eeeacfcbcc855f96f9a7e6b4ed44",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b",
"reference": "cb56001e54359df7ae76dc522d08845dc741621b",
"shasum": ""
},
"require": {
@ -369,9 +370,9 @@
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/master"
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0"
},
"time": "2024-03-13T03:41:45+00:00"
"time": "2024-11-01T03:51:45+00:00"
},
{
"name": "guzzlehttp/guzzle",
@ -678,12 +679,12 @@
"source": {
"type": "git",
"url": "https://github.com/JamesHeinrich/getID3.git",
"reference": "143af3325ee40e77c5d041d3f674c8bdd4762dc7"
"reference": "eb358a0d4943c0cc23e783f1a72141f46153c13a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/143af3325ee40e77c5d041d3f674c8bdd4762dc7",
"reference": "143af3325ee40e77c5d041d3f674c8bdd4762dc7",
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/eb358a0d4943c0cc23e783f1a72141f46153c13a",
"reference": "eb358a0d4943c0cc23e783f1a72141f46153c13a",
"shasum": ""
},
"require": {
@ -738,7 +739,7 @@
"issues": "https://github.com/JamesHeinrich/getID3/issues",
"source": "https://github.com/JamesHeinrich/getID3/tree/master"
},
"time": "2024-03-26T12:26:30+00:00"
"time": "2024-09-20T19:45:51+00:00"
},
{
"name": "league/uri",
@ -1099,20 +1100,20 @@
},
{
"name": "psr/http-factory",
"version": "1.0.2",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "e616d01114759c4c489f93b099585439f795fe35"
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
"reference": "e616d01114759c4c489f93b099585439f795fe35",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
@ -1136,7 +1137,7 @@
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
@ -1148,9 +1149,9 @@
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
"source": "https://github.com/php-fig/http-factory"
},
"time": "2023-04-10T20:10:41+00:00"
"time": "2024-04-15T12:06:14+00:00"
},
{
"name": "psr/http-message",
@ -1318,21 +1319,22 @@
"source": {
"type": "git",
"url": "https://github.com/scssphp/scssphp.git",
"reference": "5659341b5da400dafd5672171215f4534c06236b"
"reference": "12d3cdf0fc2a73e9438ded5a5a82fb353b68d718"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/scssphp/scssphp/zipball/5659341b5da400dafd5672171215f4534c06236b",
"reference": "5659341b5da400dafd5672171215f4534c06236b",
"url": "https://api.github.com/repos/scssphp/scssphp/zipball/12d3cdf0fc2a73e9438ded5a5a82fb353b68d718",
"reference": "12d3cdf0fc2a73e9438ded5a5a82fb353b68d718",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"league/uri": "^7.4@dev",
"league/uri-interfaces": "^7.3",
"league/uri": "^7.4",
"league/uri-interfaces": "^7.4",
"php": ">=8.1",
"symfony/filesystem": "^5.4 || ^6.0 || ^7.0"
"symfony/filesystem": "^5.4 || ^6.0 || ^7.0",
"symfony/polyfill-mbstring": "^1.30"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4",
@ -1347,8 +1349,7 @@
"zurb/foundation": "~6.7.0"
},
"suggest": {
"ext-iconv": "Can be used as fallback when ext-mbstring is not available",
"ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv"
"ext-mbstring": "For best performance, mbstring should be installed as it is faster than the polyfill"
},
"default-branch": true,
"type": "library",
@ -1392,7 +1393,7 @@
"issues": "https://github.com/scssphp/scssphp/issues",
"source": "https://github.com/scssphp/scssphp/tree/main"
},
"time": "2024-01-13T13:17:46+00:00"
"time": "2024-10-24T15:03:30+00:00"
},
{
"name": "symfony/console",
@ -1400,12 +1401,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e"
"reference": "108d436c2af470858bdaba3257baab3a74172017"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e",
"reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e",
"url": "https://api.github.com/repos/symfony/console/zipball/108d436c2af470858bdaba3257baab3a74172017",
"reference": "108d436c2af470858bdaba3257baab3a74172017",
"shasum": ""
},
"require": {
@ -1491,20 +1492,20 @@
"type": "tidelift"
}
],
"time": "2024-02-20T16:33:57+00:00"
"time": "2024-10-08T07:27:17+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.4.0",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": ""
},
"require": {
@ -1513,7 +1514,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1542,7 +1543,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
},
"funding": [
{
@ -1558,27 +1559,30 @@
"type": "tidelift"
}
],
"time": "2023-05-23T14:45:45+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/filesystem",
"version": "v6.4.3",
"version": "v7.2.0-BETA1",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb"
"reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb",
"reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
"reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
"shasum": ""
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
"symfony/process": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
@ -1605,7 +1609,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.3"
"source": "https://github.com/symfony/filesystem/tree/v7.2.0-BETA1"
},
"funding": [
{
@ -1621,24 +1625,24 @@
"type": "tidelift"
}
],
"time": "2024-01-23T14:51:35+00:00"
"time": "2024-10-25T15:15:23+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@ -1684,7 +1688,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@ -1700,24 +1704,24 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
"reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@ -1762,7 +1766,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
},
"funding": [
{
@ -1778,26 +1782,25 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "a287ed7475f85bf6f61890146edbc932c0fff919"
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919",
"reference": "a287ed7475f85bf6f61890146edbc932c0fff919",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773",
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773",
"shasum": ""
},
"require": {
"php": ">=7.1",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php72": "^1.10"
"php": ">=7.2",
"symfony/polyfill-intl-normalizer": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
@ -1846,7 +1849,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0"
},
"funding": [
{
@ -1862,24 +1865,24 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
"reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@ -1927,7 +1930,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
},
"funding": [
{
@ -1943,24 +1946,24 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@ -2007,7 +2010,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@ -2023,97 +2026,24 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25",
"reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "21bd091060673a1177ae842c0ef8fe30893114d2"
"reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2",
"reference": "21bd091060673a1177ae842c0ef8fe30893114d2",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
"reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"type": "library",
"extra": {
@ -2156,7 +2086,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0"
},
"funding": [
{
@ -2172,24 +2102,24 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.29.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"type": "library",
"extra": {
@ -2236,7 +2166,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
},
"funding": [
{
@ -2252,25 +2182,26 @@
"type": "tidelift"
}
],
"time": "2024-01-29T20:11:03+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.4.1",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0",
"reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/container": "^1.1|^2.0"
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
@ -2278,7 +2209,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -2318,7 +2249,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
"source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
},
"funding": [
{
@ -2334,20 +2265,20 @@
"type": "tidelift"
}
],
"time": "2023-12-26T14:02:43+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/string",
"version": "v6.4.4",
"version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "4e465a95bdc32f49cf4c7f07f751b843bbd6dcd9"
"reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/4e465a95bdc32f49cf4c7f07f751b843bbd6dcd9",
"reference": "4e465a95bdc32f49cf4c7f07f751b843bbd6dcd9",
"url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627",
"reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627",
"shasum": ""
},
"require": {
@ -2404,7 +2335,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.4.4"
"source": "https://github.com/symfony/string/tree/v6.4.13"
},
"funding": [
{
@ -2420,7 +2351,7 @@
"type": "tidelift"
}
],
"time": "2024-02-01T13:16:41+00:00"
"time": "2024-09-25T14:18:03+00:00"
},
{
"name": "vearutop/php-obscene-censor-rus",
@ -2525,12 +2456,12 @@
"source": {
"type": "git",
"url": "https://github.com/wapmorgan/Morphos.git",
"reference": "302b56636cf604ad07e20020076d8a4880554286"
"reference": "d22876709756b538a52b30a50a3f37cd89efeb91"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wapmorgan/Morphos/zipball/302b56636cf604ad07e20020076d8a4880554286",
"reference": "302b56636cf604ad07e20020076d8a4880554286",
"url": "https://api.github.com/repos/wapmorgan/Morphos/zipball/d22876709756b538a52b30a50a3f37cd89efeb91",
"reference": "d22876709756b538a52b30a50a3f37cd89efeb91",
"shasum": ""
},
"require": {
@ -2592,7 +2523,7 @@
"issues": "https://github.com/wapmorgan/Morphos/issues",
"source": "https://github.com/wapmorgan/Morphos/tree/master"
},
"time": "2024-03-26T07:24:27+00:00"
"time": "2024-09-26T12:41:09+00:00"
},
{
"name": "whichbrowser/parser",
@ -2600,12 +2531,12 @@
"source": {
"type": "git",
"url": "https://github.com/WhichBrowser/Parser-PHP.git",
"reference": "1044880bc792dbce5948fbff22ae731c43c280d9"
"reference": "581d614d686bfbec3529ad60562a5213ac5d8d72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/WhichBrowser/Parser-PHP/zipball/1044880bc792dbce5948fbff22ae731c43c280d9",
"reference": "1044880bc792dbce5948fbff22ae731c43c280d9",
"url": "https://api.github.com/repos/WhichBrowser/Parser-PHP/zipball/581d614d686bfbec3529ad60562a5213ac5d8d72",
"reference": "581d614d686bfbec3529ad60562a5213ac5d8d72",
"shasum": ""
},
"require": {
@ -2629,8 +2560,7 @@
"autoload": {
"psr-4": {
"WhichBrowser\\": [
"src/",
"tests/src/"
"src/"
]
}
},
@ -2655,28 +2585,28 @@
],
"support": {
"issues": "https://github.com/WhichBrowser/Parser-PHP/issues",
"source": "https://github.com/WhichBrowser/Parser-PHP/tree/v2.1.7"
"source": "https://github.com/WhichBrowser/Parser-PHP/tree/v2.1.8"
},
"time": "2022-04-19T20:14:54+00:00"
"time": "2024-04-17T12:47:41+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "beta",
"stability-flags": {
"whichbrowser/parser": 20,
"james-heinrich/getid3": 20,
"rybakit/msgpack": 20,
"wapmorgan/binary-stream": 20,
"al/emoji-detector": 20,
"ezyang/htmlpurifier": 20,
"scssphp/scssphp": 20,
"lfkeitel/phptotp": 20,
"vearutop/php-obscene-censor-rus": 20,
"erusev/parsedown": 20,
"bhaktaraz/php-rss-generator": 20,
"erusev/parsedown": 20,
"ezyang/htmlpurifier": 20,
"james-heinrich/getid3": 20,
"lfkeitel/phptotp": 20,
"rybakit/msgpack": 20,
"scssphp/scssphp": 20,
"symfony/console": 20,
"wapmorgan/morphos": 20
"vearutop/php-obscene-censor-rus": 20,
"wapmorgan/binary-stream": 20,
"wapmorgan/morphos": 20,
"whichbrowser/parser": 20
},
"prefer-stable": false,
"prefer-lowest": false,
@ -2684,8 +2614,9 @@
"php": "~7.3||~8.1",
"ext-openssl": "*",
"ext-simplexml": "*",
"ext-sodium": "*"
"ext-sodium": "*",
"ext-iconv": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View file

@ -30,7 +30,7 @@ docker build -t ghcr.io/openvk/openvk/php:8.2-apache ../../.. -f base-php-apache
DB images:
```
docker build -t ghcr.io/openvk/openvk/mariadb:10.9-primary ../../.. -f mariadb-primary.Dockerfile
docker build -t ghcr.io/openvk/openvk/mariadb:10.9-eventdb ../../.. --f mariadb-eventdb.Dockerfile
docker build -t ghcr.io/openvk/openvk/mariadb:10.9-eventdb ../../.. -f mariadb-eventdb.Dockerfile
```
OpenVK main image:
```

View file

@ -1,5 +1,5 @@
ARG GITREPO=openvk/openvk
FROM ghcr.io/${GITREPO}/php:8.2-cli as builder
FROM ghcr.io/${GITREPO}/php:8.2-cli AS builder
WORKDIR /opt
@ -27,15 +27,15 @@ ADD composer.* .
RUN composer install
FROM docker.io/node:14 as nodejs
FROM docker.io/node:20 AS nodejs
COPY --from=builder /opt/chandler /opt/chandler
WORKDIR /opt/chandler/extensions/available/openvk/Web/static/js
ADD Web/static/js/package.json Web/static/js/package-lock.json Web/static/js/yarn.lock ./
ADD Web/static/js/package.json Web/static/js/package-lock.json ./
RUN yarn install
RUN npm ci
WORKDIR /opt/chandler/extensions/available/openvk
@ -57,4 +57,6 @@ VOLUME [ "/opt/chandler/extensions/available/openvk/tmp/api-storage/audios" ]
VOLUME [ "/opt/chandler/extensions/available/openvk/tmp/api-storage/photos" ]
VOLUME [ "/opt/chandler/extensions/available/openvk/tmp/api-storage/videos" ]
USER www-data
USER www-data
WORKDIR /opt/chandler/extensions/available/openvk

View file

@ -0,0 +1,2 @@
CREATE TABLE `ignored_sources` (`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT , `owner` BIGINT(20) UNSIGNED NOT NULL , `source` BIGINT(20) NOT NULL , PRIMARY KEY (`id`), INDEX (`owner`)) ENGINE = InnoDB;
ALTER TABLE `ignored_sources` ADD INDEX `owner_source` (`owner`, `source`);

View file

@ -0,0 +1,2 @@
ALTER TABLE `videos` ADD `length` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `name`, ADD INDEX `length` (`length`);
ALTER TABLE `videos` ADD `height` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `length`, ADD `width` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `height`;

View file

@ -219,6 +219,7 @@
"my_news" = "My news";
"all_news" = "All news";
"posts_per_page" = "Number of posts per page";
"show_ignored_sources" = "Show ignored sources";
"attachment" = "Attachment";
"post_as_group" = "Post as group";
@ -251,12 +252,24 @@
"edited_short" = "edited";
"feed_settings" = "Feed settings";
"ignored_sources" = "Ignored sources";
"ignore_user" = "Ignore user";
"unignore_user" = "Unignore user";
"ignore_club" = "Ignore club";
"unignore_club" = "Unignore club";
"no_ignores_count" = "No ignored sources.";
"stop_ignore" = "Unignore";
"start_from_page" = "Start from page";
"all_posts" = "All posts";
"users_posts" = "Posts by $1";
"clubs_posts" = "Group's posts";
"others_posts" = "Others posts";
"show_more" = "Show more";
"has_repost" = "Has repost";
/* Friends */
@ -505,6 +518,7 @@
"click_to_go_to_album" = "Click here to go to album.";
"error_uploading_photo" = "Error when uploading photo";
"too_many_pictures" = "No more than 10 pictures";
"too_many_attachments" = "Too many attachments.";
"drag_files_here" = "Drag files here";
"only_images_accepted" = "File \"$1\" is not an image";
@ -836,6 +850,9 @@
"no_videos" = "You don't have uploaded videos.";
"no_videos_results" = "No results.";
"video_file_upload" = "Upload file";
"video_youtube_upload" = "Add from YouTube";
"change_video" = "Change video";
"unknown_video" = "This video is not supported in your version of OpenVK.";
@ -1544,6 +1561,9 @@
"group_is_banned" = "Group was successfully banned";
"description_too_long" = "Description is too long.";
"invalid_club" = "This group does not exists.";
"invalid_user" = "This user does not exists.";
"ignored_sources_limit" = "Limit of ignored sources has exceed";
"invalid_audio" = "Invalid audio.";
"do_not_have_audio" = "You don't have this audio.";
@ -1762,6 +1782,9 @@
"question_confirm" = "This action can't be undone. Do you really wanna do it?";
"confirm_m" = "Confirm";
"action_successfully" = "Success";
"exit_noun" = "Exit";
"exit_confirmation" = "Are you sure want to exit?";
"apply" = "Apply";
/* User Alerts */
@ -2066,7 +2089,7 @@
"showing_x_y" = "(showing $1—$2)";
"no_results_by_this_query" = "Nothing was found by this query.";
"s_additional" = "Additional";
"s_it_is_you" = "it is you";
"s_it_is_you" = "that's you";
/* BadBrowser */

View file

@ -13,7 +13,7 @@ list:
flag: "ua"
name: "Ukrainian"
native_name: "Українcька"
author: "Aqukie (yaroslav.bielograd@ukr.net), Andrej Lenťaj, Maxim Hrabovi (dechioyo) and Kirill (mbsoft)"
author: "Jaroslav (ovk.to/id6908), Andrej Lenťaj, Maxim Hrabovi (dechioyo) and Kirill (mbsoft)"
- code: "by"
flag: "by"
name: "Belarussian"

View file

@ -204,6 +204,7 @@
"my_news" = "Мои новости";
"all_news" = "Все новости";
"posts_per_page" = "Количество записей на странице";
"show_ignored_sources" = "Показывать игнорируемые источники";
"attachment" = "Вложение";
"post_as_group" = "От имени сообщества";
"comment_as_group" = "От имени сообщества";
@ -231,11 +232,23 @@
"post_is_ad" = "Этот пост был размещён за взятку.";
"edited_short" = "ред.";
"feed_settings" = "Настройки ленты";
"ignored_sources" = "Игнорируемые источники";
"ignore_user" = "Игнорировать пользователя";
"unignore_user" = "Не игнорировать пользователя";
"ignore_club" = "Игнорировать группу";
"unignore_club" = "Не игнорировать группу";
"no_ignores_count" = "Игнорируемых источников нет.";
"stop_ignore" = "Не игнорировать";
"start_from_page" = "Начинать со страницы";
"all_posts" = "Все записи";
"users_posts" = "Записи $1";
"clubs_posts" = "Записи сообщества";
"others_posts" = "Чужие записи";
"show_more" = "Показать больше";
"has_repost" = "Содержит репост";
/* Friends */
@ -488,9 +501,10 @@
"click_to_go_to_album" = "Нажмите, чтобы перейти к альбому.";
"error_uploading_photo" = "Не удалось загрузить фотографию";
"too_many_pictures" = "Не больше 10 фотографий";
"too_many_attachments" = "Слишком много вложений.";
"drag_files_here" = "Перетащите файлы сюда";
"only_images_accepted" = "Файл \"$1\" не является изображением";
"only_images_accepted" = "Файл \"$1\" не является изображением или видео.";
"max_filesize" = "Максимальный размер файла — $1 мегабайт";
"uploading_photos_from_computer" = "Загрузка фотографий с Вашего компьютера";
@ -795,6 +809,9 @@
"no_videos" = "У вас нет видео.";
"no_videos_results" = "Нет результатов.";
"video_file_upload" = "Загрузить файл";
"video_youtube_upload" = "Добавить с YouTube";
/* Audios */
"my" = "Моё";
@ -1445,6 +1462,11 @@
"group_owner_is_banned" = "Создатель сообщества успешно забанен.";
"group_is_banned" = "Сообщество успешно забанено";
"description_too_long" = "Описание слишком длинное.";
"invalid_club" = "Такой группы не существует.";
"invalid_user" = "Такого пользователя не существует.";
"ignored_sources_limit" = "Превышен лимит игнорируемых источников.";
"invalid_audio" = "Такой аудиозаписи не существует.";
"do_not_have_audio" = "У вас нет этой аудиозаписи.";
"do_have_audio" = "У вас уже есть эта аудиозапись.";
@ -1464,7 +1486,7 @@
"error_adding_source_regex" = "Ошибка добавления источника: некорректная ссылка.";
"error_adding_source_long" = "Ошибка добавления источника: слишком длинная ссылка.";
"error_adding_source_sus" = "Ошибка добавления источника: подозрительная ссылка.";
"error_adding_source_sus" = "Ошибка добавления источника: гиперссылка заблокирована.";
/* Admin actions */
@ -1652,6 +1674,9 @@
"question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?";
"confirm_m" = "Подтвердить";
"action_successfully" = "Операция успешна";
"exit_noun" = "Выход";
"exit_confirmation" = "Уверены, что хотите выйти?";
"apply" = "Применить";
/* User alerts */
@ -2010,7 +2035,7 @@
/* Fullscreen player */
"hide_player" = "Скрыть";
"hide_player" = "Свернуть";
"close_player" = "Закрыть";
"show_comments" = "Показать информацию";
"close_comments" = "Скрыть информацию";

File diff suppressed because it is too large Load diff

View file

@ -52,12 +52,15 @@ openvk:
strict: false
music:
exposeOriginalURLs: true
newsfeed:
ignoredSourcesLimit: 50
wall:
christian: false
anonymousPosting:
enable: false
account: 100
postSizes:
maxAttachments: 10
maxSize: 60000
processingLimit: 3000
emojiProcessingLimit: 1000

View file

@ -1,7 +1,7 @@
.page_header {
background-image: url('/themepack/midnight/0.0.3.0/resource/xheader.png') !important;
background-image: url('/themepack/midnight/0.0.3.1/resource/xheader.png') !important;
}
.page_custom_header {
background-image: url('/themepack/midnight/0.0.3.0/resource/xheader_custom.png') !important;
background-image: url('/themepack/midnight/0.0.3.1/resource/xheader_custom.png') !important;
}

View file

@ -186,8 +186,12 @@ hr {
.menu_divider,
.ovk-diag-action,
.minilink .counter,
.topGrayBlock {
background-color: #2c2640;
.topGrayBlock,
.showMore,
.showMoreAudiosPlaylist,
#showMorePhotos,
#showMoreVideos {
background-color: #2c2640 !important;
}
.bsdn_contextMenu {
@ -313,11 +317,11 @@ tr.v,
}
.content_title_expanded {
background-image: url("/themepack/midnight/0.0.3.0/resource/flex_arrow_open.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/flex_arrow_open.png") !important;
}
.content_title_unexpanded {
background-image: url("/themepack/midnight/0.0.3.0/resource/flex_arrow_shut.gif") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/flex_arrow_shut.gif") !important;
}
.ovk-video>.preview,
@ -344,17 +348,17 @@ tr.h {
.page_yellowheader {
color: #c6d2e8;
background-image: url("/themepack/midnight/0.0.3.0/resource/header_purple.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/header_purple.png") !important;
background-color: #231f34;
border-color: #231f34;
}
.page_header {
background-image: url("/themepack/midnight/0.0.3.0/resource/header.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/header.png") !important;
}
.page_custom_header {
background-image: url("/themepack/midnight/0.0.3.0/resource/header_custom.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/header_custom.png") !important;
}
.page_yellowheader span,
@ -392,11 +396,11 @@ select,
}
input[type="checkbox"] {
background-image: url("/themepack/midnight/0.0.3.0/resource/checkbox.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/checkbox.png") !important;
}
input[type="radio"] {
background-image: url("/themepack/midnight/0.0.3.0/resource/radio.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/radio.png") !important;
}
.header_navigation .link, .header_navigation .header_divider_stick {
@ -404,20 +408,20 @@ input[type="radio"] {
}
.heart {
background-image: url("/themepack/midnight/0.0.3.0/resource/like.gif") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/like.gif") !important;
}
.pinned-mark,
.post-author .pin {
background-image: url("/themepack/midnight/0.0.3.0/resource/pin.png") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/pin.png") !important;
}
.repost-icon {
background-image: url("/themepack/midnight/0.0.3.0/resource/published.gif") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/published.gif") !important;
}
.post-author .delete {
background-image: url("/themepack/midnight/0.0.3.0/resource/input_clear.gif") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/input_clear.gif") !important;
}
.user-alert {
@ -450,7 +454,7 @@ input[type="radio"] {
}
#backdropEditor {
background-image: url("/themepack/midnight/0.0.3.0/resource/backdrop-editor.gif") !important;
background-image: url("/themepack/midnight/0.0.3.1/resource/backdrop-editor.gif") !important;
border-color: #473e66 !important;
}
@ -588,11 +592,6 @@ ul {
cursor: pointer;
}
.showMore,
.showMoreAudiosPlaylist {
background: #181826 !important;
}
/* Tour */
.rightNav h1 {
background: #000;

View file

@ -1,5 +1,5 @@
id: midnight
version: "0.0.3.0"
version: "0.0.3.1"
openvk_version: 0
enabled: 1
metadata: