transition from site-mirzaev
|
@ -1,8 +1,3 @@
|
||||||
# ---> Composer
|
!.gitignore
|
||||||
composer.phar
|
composer.phar
|
||||||
/vendor/
|
vendor
|
||||||
|
|
||||||
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
|
|
||||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
|
||||||
# composer.lock
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"name": "mirzaev/site-account",
|
||||||
|
"description": "Мой персональный сайт",
|
||||||
|
"readme": "README.md",
|
||||||
|
"keywords": [
|
||||||
|
"site",
|
||||||
|
"api",
|
||||||
|
"authentication",
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"type": "site",
|
||||||
|
"homepage": "https://git.mirzaev.sexy/mirzaev/site-account",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||||
|
"email": "arsen@mirzaev.sexy",
|
||||||
|
"homepage": "https://mirzaev.sexy",
|
||||||
|
"role": "Programmer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "arsen@mirzaev.sexy",
|
||||||
|
"wiki": "https://git.mirzaev.sexy/mirzaev/site-account/wiki",
|
||||||
|
"issues": "https://git.mirzaev.sexy/mirzaev/site-account/issues"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "funding",
|
||||||
|
"url": "https://fund.mirzaev.sexy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "~8.1",
|
||||||
|
"ext-sodium": "~8.1",
|
||||||
|
"mirzaev/minimal": "^2.0.x-dev",
|
||||||
|
"mirzaev/accounts": "~1.2.x-dev",
|
||||||
|
"mirzaev/arangodb": "^1.0.0",
|
||||||
|
"mirzaev/vk": "^5.0",
|
||||||
|
"triagens/arangodb": "~3.9.x-dev",
|
||||||
|
"twig/twig": "^3.4",
|
||||||
|
"guzzlehttp/guzzle": "^7.5"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~9.5"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\site\\account\\": "mirzaev/site/account/system"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\site\\account\\tests\\": "mirzaev/site/account/tests"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\controllers\core;
|
||||||
|
use mirzaev\site\account\models\account_model as account;
|
||||||
|
use mirzaev\site\account\models\session_model as session;
|
||||||
|
use mirzaev\site\account\models\vk_model as vk;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
// Фреймворк для ВКонтакте
|
||||||
|
use mirzaev\vk\core as api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер аккаунтов
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class account_controller extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Страница профиля
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function index(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function initialization(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
if ($this->variables['account'] instanceof _document) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
if ($this->variables['vk'] instanceof _document) {
|
||||||
|
// Найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта ВКонтакте
|
||||||
|
vk::parse($this->variables['vk'], $this->variables['errors']['vk']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code(200);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// Не найден аккаунт
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code(401);
|
||||||
|
|
||||||
|
// Запись заголовка ответа с ключом аккаунта
|
||||||
|
header('session: ' . $this->variables['session']->hash);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Связь аккаунта с аккаунтом ВКонтакте
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function connect(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
if ($this->variables['session']->hash === $parameters['state']) {
|
||||||
|
// Совпадает хеш сессии с полученным хешем из ответа ВКонтакте
|
||||||
|
|
||||||
|
if (!empty($response = vk::key($parameters['code'], $this->variables['errors']['vk']))) {
|
||||||
|
// Получены данные аккаунта ВКонтакте
|
||||||
|
|
||||||
|
if (($this->variables['vk'] = vk::initialization($response, $this->variables['errors']['vk'])) instanceof _document) {
|
||||||
|
// Инициализирован аккаунт ВКонтакте
|
||||||
|
|
||||||
|
if (($this->variables['account'] = vk::account($this->variables['vk'])) instanceof _document) {
|
||||||
|
// Найден аккаунт (существующий)
|
||||||
|
|
||||||
|
if (session::connect($this->variables['session'], $this->variables['account'], $this->variables['errors']['session'])) {
|
||||||
|
// Связана сессия с аккаунтом
|
||||||
|
}
|
||||||
|
} else if (($this->variables['account'] = account::create($this->variables['errors']['account'])) instanceof _document) {
|
||||||
|
// Найден аккаунт (создан новый)
|
||||||
|
|
||||||
|
if (session::connect($this->variables['session'], $this->variables['account'], $this->variables['errors']['session'])) {
|
||||||
|
// Связана сессия с аккаунтом
|
||||||
|
|
||||||
|
if (account::connect($this->variables['account'], $this->variables['vk'], $this->variables['errors']['account'])) {
|
||||||
|
// Связан аккаунт с аккаунтом ВКонтакте
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация робота для аккаунта ВКонтакте
|
||||||
|
$this->vk = api::init()->user(key: $this->variables['vk']->access['key']);
|
||||||
|
|
||||||
|
if ($this->variables['vk'] instanceof _document) {
|
||||||
|
// Инициализирован робот для аккаунта ВКонтакте
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта ВКонтакте
|
||||||
|
$data = vk::parse($this->vk, $this->variables['errors']['vk']);
|
||||||
|
var_dump($data); die;
|
||||||
|
|
||||||
|
if ($data instanceof stdClass) {
|
||||||
|
// Получены данные ВКонтакте
|
||||||
|
|
||||||
|
// Запись в базу данных
|
||||||
|
vk::update($this->variables['vk'], $data, $this->variables['errors']['vk']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'account' . DIRECTORY_SEPARATOR . 'vk.html', $this->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация панели аккаунта
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function panel(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'account' . DIRECTORY_SEPARATOR . 'panel.html', $this->variables);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\views\manager;
|
||||||
|
use mirzaev\site\account\models\core as models;
|
||||||
|
use mirzaev\site\account\models\account_model as account;
|
||||||
|
use mirzaev\site\account\models\session_model as session;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Фреймворк PHP
|
||||||
|
use mirzaev\minimal\controller;
|
||||||
|
|
||||||
|
// Фреймворк ВКонтакте
|
||||||
|
use mirzaev\vk\core as vk;
|
||||||
|
use mirzaev\vk\robots\user as robot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ядро контроллеров
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class core extends controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Переменные окружения
|
||||||
|
*/
|
||||||
|
protected robot $vk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переменные окружения
|
||||||
|
*/
|
||||||
|
protected array $variables = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Инициализация ядра моделей (соединение с базой данных...)
|
||||||
|
new models();
|
||||||
|
|
||||||
|
// Инициализация журнала ошибок
|
||||||
|
$this->variables['errors'] = [
|
||||||
|
'session' => [],
|
||||||
|
'account' => [],
|
||||||
|
'vk' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация даты до которой будет активна сессия
|
||||||
|
$expires = time() + 604800;
|
||||||
|
|
||||||
|
// Инициализация сессии (без журналирования)
|
||||||
|
$this->variables['session'] = session::initialization($_COOKIE["session"] ?? null, $expires) ?? header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию');
|
||||||
|
|
||||||
|
if ($_COOKIE["session"] ?? null !== $this->variables['session']->hash) {
|
||||||
|
// Изменился хеш сессии (подразумевается, что сессия устарела)
|
||||||
|
|
||||||
|
// Запись хеша новой сессии
|
||||||
|
setcookie('session', $this->variables['session']->hash, [
|
||||||
|
'expires' => $expires,
|
||||||
|
'domain' => 'mirzaev.sexy',
|
||||||
|
'path' => '/',
|
||||||
|
'secure' => true,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'strict'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация аккаунта (без журналирования)
|
||||||
|
$this->variables['account'] = session::account($this->variables['session']);
|
||||||
|
|
||||||
|
if ($this->variables['account'] instanceof _document) {
|
||||||
|
// Инициализирован аккаунт
|
||||||
|
|
||||||
|
// Инициализация аккаунта ВКонтакте (без журналирования)
|
||||||
|
$this->variables['vk'] = account::vk($this->variables['account']);
|
||||||
|
|
||||||
|
if ($this->variables['vk'] instanceof _document) {
|
||||||
|
// Инициализирован аккаунт ВКонтакте
|
||||||
|
|
||||||
|
// Инициализация робота для аккаунта ВКонтакте
|
||||||
|
$this->vk = vk::init()->user(key: $this->variables['vk']->access['key']);
|
||||||
|
} else unset($this->variables['account'], $this->variables['vk']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация препроцессора представления
|
||||||
|
$this->view = new manager;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\controllers\core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер ошибок
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class error_controller extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Страница с ошибкой
|
||||||
|
*
|
||||||
|
* @param array $parameters
|
||||||
|
*/
|
||||||
|
public function index(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
// Запись текста ошибки в переменную окружения
|
||||||
|
$this->variables['text'] = $parameters['text'] ?? null;
|
||||||
|
|
||||||
|
if (isset($parameters['code'])) {
|
||||||
|
// Получен код ошибки
|
||||||
|
|
||||||
|
// Запись кода ошибки в переменную окружения
|
||||||
|
$this->variables['code'] = $parameters['code'];
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code($parameters['code']);
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . 'index.html', $this->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . ($parameters['code'] ?? 'index') . '.html', $this->variables);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\mirzaev\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\mirzaev\controllers\core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер графика
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\mirzaev\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class graph_controller extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Страница с графиком
|
||||||
|
*
|
||||||
|
* Можно использовать совместно с элементом <iframe> для изоляции
|
||||||
|
* содержимого бегущей строки от поисковых роботов
|
||||||
|
*
|
||||||
|
* @param array $parameters
|
||||||
|
*/
|
||||||
|
public function index(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
// Инициализация элементов для генерации в головном элементе
|
||||||
|
$this->variables['head'] = [
|
||||||
|
'title' => 'Бегущая строка',
|
||||||
|
'metas' => [
|
||||||
|
[
|
||||||
|
'attributes' => [
|
||||||
|
'name' => 'robots',
|
||||||
|
'content' => 'nofollow'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация бегущей строки
|
||||||
|
$this->variables['graph'] = [
|
||||||
|
'id' => $this->variables['request']['id'] ?? 'graph'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация аттрибутов бегущей строки
|
||||||
|
$this->variables['graph']['attributes'] = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация элементов бегущей строки
|
||||||
|
$this->variables['graph']['elements'] = [
|
||||||
|
];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'graph' . DIRECTORY_SEPARATOR . 'index.html', $this->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\controllers\core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер бегущей строки
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class hotline_controller extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Страница с бегущей строкой
|
||||||
|
*
|
||||||
|
* Можно использовать совместно с элементом <iframe> для изоляции
|
||||||
|
* содержимого бегущей строки от поисковых роботов
|
||||||
|
*
|
||||||
|
* @param array $parameters
|
||||||
|
*/
|
||||||
|
public function index(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
// Инициализация элементов для генерации в головном элементе
|
||||||
|
$this->variables['head'] = [
|
||||||
|
'title' => 'Бегущая строка',
|
||||||
|
'metas' => [
|
||||||
|
[
|
||||||
|
'attributes' => [
|
||||||
|
'name' => 'robots',
|
||||||
|
'content' => 'nofollow'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация бегущей строки
|
||||||
|
$this->variables['hotline'] = [
|
||||||
|
'id' => $this->variables['request']['id'] ?? 'hotline'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация параметров бегущей строки
|
||||||
|
$this->variables['hotline']['parameters'] = [
|
||||||
|
// 'step' => 2
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация аттрибутов бегущей строки
|
||||||
|
$this->variables['hotline']['attributes'] = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация элементов бегущей строки
|
||||||
|
$this->variables['hotline']['elements'] = [
|
||||||
|
['content' => '1'],
|
||||||
|
[
|
||||||
|
'tag' => 'article',
|
||||||
|
'content' => '2'
|
||||||
|
],
|
||||||
|
['content' => '3'],
|
||||||
|
['content' => '4'],
|
||||||
|
['content' => '5'],
|
||||||
|
['content' => '6'],
|
||||||
|
['content' => '7'],
|
||||||
|
['content' => '8'],
|
||||||
|
['content' => '9'],
|
||||||
|
['content' => '10'],
|
||||||
|
['content' => '11'],
|
||||||
|
['content' => '12'],
|
||||||
|
['content' => '13'],
|
||||||
|
['content' => '14'],
|
||||||
|
['content' => '15']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'index.html', $this->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\controllers\core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер основной страницы
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class index_controller extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Главная страница
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function index(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
// Инициализация загружаемых категорий
|
||||||
|
$this->variables['include'] = [
|
||||||
|
'head' => ['self'],
|
||||||
|
'body' => ['self']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация бегущей строки
|
||||||
|
$this->variables['hotline'] = [
|
||||||
|
'id' => $this->variables['request']['id'] ?? 'hotline'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация параметров бегущей строки
|
||||||
|
$this->variables['hotline']['parameters'] = [
|
||||||
|
// 'step' => 2
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация аттрибутов бегущей строки
|
||||||
|
$this->variables['hotline']['attributes'] = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация элементов бегущей строки
|
||||||
|
$this->variables['hotline']['elements'] = [
|
||||||
|
['content' => '1'],
|
||||||
|
[
|
||||||
|
'tag' => 'article',
|
||||||
|
'content' => '2'
|
||||||
|
],
|
||||||
|
['content' => '3'],
|
||||||
|
['content' => '4'],
|
||||||
|
['content' => '5'],
|
||||||
|
['content' => '6'],
|
||||||
|
['content' => '7'],
|
||||||
|
['content' => '8'],
|
||||||
|
['content' => '9'],
|
||||||
|
['content' => '10'],
|
||||||
|
['content' => '11'],
|
||||||
|
['content' => '12'],
|
||||||
|
['content' => '13'],
|
||||||
|
['content' => '14'],
|
||||||
|
['content' => '15']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация бегущей строки
|
||||||
|
$this->variables['graph'] = [
|
||||||
|
'id' => $this->variables['request']['id'] ?? 'graph'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация аттрибутов бегущей строки
|
||||||
|
$this->variables['graph']['attributes'] = [
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация элементов бегущей строки
|
||||||
|
$this->variables['graph']['elements'] = [
|
||||||
|
];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', $this->variables);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\models;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\models\vk_model as vk;
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Встроенные библиотеки
|
||||||
|
use exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель регистрации, аутентификации и авторизации
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class account_model extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Коллекция
|
||||||
|
*/
|
||||||
|
public const COLLECTION = 'account';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создать
|
||||||
|
*
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта, если удалось создать
|
||||||
|
*/
|
||||||
|
public static function create(array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
// Запись аккаунта в базу данных
|
||||||
|
$_id = document::write(static::$db->session, self::COLLECTION);
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d._id == '$_id'
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION
|
||||||
|
))) {
|
||||||
|
// Найден созданный аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} else throw new exception('Не удалось создать аккаунт');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Связь аккаунта с аккаунтом ВКонтакте
|
||||||
|
*
|
||||||
|
* @param _document $account Инстанция аккаунта
|
||||||
|
* @param _document $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return bool Статус выполнения
|
||||||
|
*/
|
||||||
|
public static function connect(_document $account, _document $vk, array &$errors = []): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, vk::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализированы коллекции
|
||||||
|
|
||||||
|
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, [
|
||||||
|
'_from' => $account->getId(),
|
||||||
|
'_to' => $vk->getId()
|
||||||
|
])) {
|
||||||
|
// Создано ребро: account -> vk
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else throw new exception('Не удалось создать ребро: account -> vk');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск связанного аккаунта ВКонтакте
|
||||||
|
*
|
||||||
|
* @param _document $account Инстанция аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта, если удалось найти
|
||||||
|
*/
|
||||||
|
public static function vk(_document $account, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, vk::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
if ($vk = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR document IN %s
|
||||||
|
LET edge = (
|
||||||
|
FOR edge IN %s
|
||||||
|
FILTER edge._from == '%s'
|
||||||
|
SORT edge._key DESC
|
||||||
|
LIMIT 1
|
||||||
|
RETURN edge
|
||||||
|
)
|
||||||
|
FILTER document._id == edge[0]._to
|
||||||
|
LIMIT 1
|
||||||
|
RETURN document
|
||||||
|
AQL,
|
||||||
|
vk::COLLECTION,
|
||||||
|
self::COLLECTION . '_edge_' . vk::COLLECTION,
|
||||||
|
$account->getId()
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
return $vk;
|
||||||
|
} else throw new exception('Не удалось найти аккаунт ВКонтакте');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\models;
|
||||||
|
|
||||||
|
use mirzaev\minimal\model;
|
||||||
|
|
||||||
|
use mirzaev\arangodb\connection;
|
||||||
|
|
||||||
|
use exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ядро моделей
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class core extends model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Коллекция в которой хранятся аккаунты
|
||||||
|
*/
|
||||||
|
public const SETTINGS = '../settings/arangodb.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Соединение с базой данных
|
||||||
|
*/
|
||||||
|
protected static connection $db;
|
||||||
|
|
||||||
|
public function __construct(connection $db = null)
|
||||||
|
{
|
||||||
|
if (isset($db)) {
|
||||||
|
// Получена инстанция соединения с базой данных
|
||||||
|
|
||||||
|
// Запись и инициализация соединения с базой данных
|
||||||
|
$this->__set('db', $db);
|
||||||
|
} else {
|
||||||
|
// Не получена инстанция соединения с базой данных
|
||||||
|
|
||||||
|
// Инициализация соединения с базой данных по умолчанию
|
||||||
|
$this->__get('db');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Записать свойство
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
* @param mixed $value Значение
|
||||||
|
*/
|
||||||
|
public function __set(string $name, mixed $value = null): void
|
||||||
|
{
|
||||||
|
match ($name) {
|
||||||
|
'db' => (function () use ($value) {
|
||||||
|
if ($this->__isset('db')) {
|
||||||
|
// Свойство уже было инициализировано
|
||||||
|
|
||||||
|
// Выброс исключения (неудача)
|
||||||
|
throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500);
|
||||||
|
} else {
|
||||||
|
// Свойство ещё не было инициализировано
|
||||||
|
|
||||||
|
if ($value instanceof connection) {
|
||||||
|
// Передано подходящее значение
|
||||||
|
|
||||||
|
// Запись свойства (успех)
|
||||||
|
self::$db = $value;
|
||||||
|
} else {
|
||||||
|
// Передано неподходящее значение
|
||||||
|
|
||||||
|
// Выброс исключения (неудача)
|
||||||
|
throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией mirzaev\arangodb\connection', 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
default => parent::__set($name, $value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать свойство
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
*
|
||||||
|
* @return mixed Содержимое
|
||||||
|
*/
|
||||||
|
public function __get(string $name): mixed
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'db' => (function () {
|
||||||
|
if (!$this->__isset('db')) {
|
||||||
|
// Свойство не инициализировано
|
||||||
|
|
||||||
|
// Инициализация значения по умолчанию исходя из настроек
|
||||||
|
$this->__set('db', new connection(require static::SETTINGS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$db;
|
||||||
|
})(),
|
||||||
|
default => parent::__get($name)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить свойство на инициализированность
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
*/
|
||||||
|
public function __isset(string $name): bool
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
default => parent::__isset($name)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить свойство
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
*/
|
||||||
|
public function __unset(string $name): void
|
||||||
|
{
|
||||||
|
match ($name) {
|
||||||
|
default => parent::__isset($name)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Статический вызов
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
* @param array $arguments Параметры
|
||||||
|
*/
|
||||||
|
public static function __callStatic(string $name, array $arguments): mixed
|
||||||
|
{
|
||||||
|
match ($name) {
|
||||||
|
'db' => (new static)->__get('db'),
|
||||||
|
default => throw new exception("Не найдено свойство или функция: $name", 500)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\models;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\models\account_model as account;
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Встроенные библиотеки
|
||||||
|
use exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель сессий
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class session_model extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Коллекция
|
||||||
|
*/
|
||||||
|
public const COLLECTION = 'session';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param ?string $hash Хеш сессии в базе данных
|
||||||
|
* @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция сессии, если удалось найти или создать
|
||||||
|
*/
|
||||||
|
public static function initialization(?string $hash = null, ?int $expires = null, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
if (isset($hash) && $session = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d.hash == '$hash' && d.expires > %d
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION,
|
||||||
|
time()
|
||||||
|
))) {
|
||||||
|
// Найдена сессия по хешу
|
||||||
|
|
||||||
|
// Возврат сессии
|
||||||
|
return $session;
|
||||||
|
} else if ($session = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d.ip == '%s' && d.expires > %d
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION,
|
||||||
|
$_SERVER['REMOTE_ADDR'],
|
||||||
|
time()
|
||||||
|
))) {
|
||||||
|
// Найдена сессия по данным пользователя
|
||||||
|
|
||||||
|
// Возврат сессии
|
||||||
|
return $session;
|
||||||
|
} else {
|
||||||
|
// Не найдена сессия
|
||||||
|
|
||||||
|
// Запись сессии в базу данных
|
||||||
|
$_id = document::write(static::$db->session, self::COLLECTION, [
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||||
|
'expires' => $expires ?? time() + 604800
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($session = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d._id == '$_id' && d.expires > %d
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION,
|
||||||
|
time()
|
||||||
|
))) {
|
||||||
|
// Найдена созданная сессия
|
||||||
|
|
||||||
|
// Запись хеша
|
||||||
|
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
|
||||||
|
|
||||||
|
if (document::update(static::$db->session, $session)) {
|
||||||
|
// Записано обновление
|
||||||
|
|
||||||
|
return $session;
|
||||||
|
} else throw new exception('Не удалось записать данные сессии');
|
||||||
|
} else throw new exception('Не удалось создать или найти созданную сессию');
|
||||||
|
}
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Связь сессии с аккаунтом
|
||||||
|
*
|
||||||
|
* @param _document $session Инстанция сессии
|
||||||
|
* @param _document $account Инстанция аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return bool Статус выполнения
|
||||||
|
*/
|
||||||
|
public static function connect(_document $session, _document $account, array &$errors = []): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, account::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
|
||||||
|
'_from' => $session->getId(),
|
||||||
|
'_to' => $account->getId()
|
||||||
|
])) {
|
||||||
|
// Создано ребро: session -> account
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else throw new exception('Не удалось создать ребро: session -> account');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск связанного аккаунта
|
||||||
|
*
|
||||||
|
* @param _document $session Инстанция сессии
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта, если удалось найти
|
||||||
|
*/
|
||||||
|
public static function account(_document $session, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, account::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализированы коллекции
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR document IN %s
|
||||||
|
LET edge = (
|
||||||
|
FOR edge IN %s
|
||||||
|
FILTER edge._from == '%s'
|
||||||
|
SORT edge._key DESC
|
||||||
|
LIMIT 1
|
||||||
|
RETURN edge
|
||||||
|
)
|
||||||
|
FILTER document._id == edge[0]._to
|
||||||
|
LIMIT 1
|
||||||
|
RETURN document
|
||||||
|
AQL,
|
||||||
|
account::COLLECTION,
|
||||||
|
self::COLLECTION . '_edge_' . account::COLLECTION,
|
||||||
|
$session->getId()
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} else throw new exception('Не удалось найти аккаунт');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,555 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\models;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\models\account_model as account;
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Фреймворк ВКонтакте
|
||||||
|
use mirzaev\vk\robots\user as robot;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Библиотека браузера
|
||||||
|
use GuzzleHttp\Client as browser;
|
||||||
|
|
||||||
|
// Встроенные библиотеки
|
||||||
|
use exception;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель аккаунта ВКонтакте
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class vk_model extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Коллекция
|
||||||
|
*/
|
||||||
|
public const COLLECTION = 'vk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param string $response Ответ сервера ВКонтакте с данными аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось создать
|
||||||
|
*/
|
||||||
|
public static function initialization(string $response = '', array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта ВКонтакте
|
||||||
|
$data = json_decode($response);
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d.id == $data->user_id
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} else {
|
||||||
|
// Не найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
return self::create($response, $errors);
|
||||||
|
}
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание
|
||||||
|
*
|
||||||
|
* @param string $response Ответ сервера ВКонтакте с данными аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось создать
|
||||||
|
*/
|
||||||
|
public static function create(string $response = '', array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
// Запись аккаунта в базу данных
|
||||||
|
$_id = document::write(static::$db->session, self::COLLECTION);
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d._id == '$_id'
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION
|
||||||
|
))) {
|
||||||
|
// Найден созданный аккаунт ВКонтакте
|
||||||
|
|
||||||
|
if (document::update(static::$db->session, $account)) {
|
||||||
|
// Записано обновление
|
||||||
|
|
||||||
|
// Запись данных об аккаунте ВКонтакте и возврат (bool)
|
||||||
|
return self::update($account, json_decode($response), $errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new exception('Не удалось создать аккаунт ВКонтакте');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запросить ключ
|
||||||
|
*
|
||||||
|
* @param string $code Код полученный от ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?string Тело ответа, если получен код ответа 200
|
||||||
|
*/
|
||||||
|
public static function key(string $code = '', array &$errors = []): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Инициализация браузера
|
||||||
|
$browser = new browser();
|
||||||
|
|
||||||
|
// Запрос
|
||||||
|
$response = $browser->request('GET', "https://oauth.vk.com/access_token?client_id=51447080&client_secret=KYlk0nGELW0A9ds7NQi6&redirect_uri=https://mirzaev.sexy/account/vk/connect&code=$code");
|
||||||
|
|
||||||
|
if ($response->getStatusCode() === 200) {
|
||||||
|
// Ответ сервера: 200
|
||||||
|
|
||||||
|
return (string) $response->getBody();
|
||||||
|
} else throw new exception('Не удалось получить ключ ВКонтакте (' . $response->getStatusCode() . ')');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск связанного аккаунта
|
||||||
|
*
|
||||||
|
* @param _document $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта, если удалось найти
|
||||||
|
*/
|
||||||
|
public static function account(_document $vk, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, account::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, account::COLLECTION . '_edge_' . self::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализированы коллекции
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR document IN %s
|
||||||
|
LET edge = (
|
||||||
|
FOR edge IN %s
|
||||||
|
FILTER edge._to == '%s'
|
||||||
|
SORT edge._key DESC
|
||||||
|
LIMIT 1
|
||||||
|
RETURN edge
|
||||||
|
)
|
||||||
|
FILTER document._id == edge[0]._from
|
||||||
|
LIMIT 1
|
||||||
|
RETURN document
|
||||||
|
AQL,
|
||||||
|
account::COLLECTION,
|
||||||
|
account::COLLECTION . '_edge_' . self::COLLECTION,
|
||||||
|
$vk->getId()
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} else throw new exception('Не удалось найти аккаунт');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запрос данных аккаунта ВКонтакте с серверов ВКонтакте
|
||||||
|
*
|
||||||
|
* @param robot $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?stdClass Данные аккаунта ВКонтакте, если получены
|
||||||
|
*/
|
||||||
|
public static function parse(robot $vk, array &$errors = []): ?stdClass
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Запрос к API-серверу ВКонтакте
|
||||||
|
$response = $vk->user->get(fields: [
|
||||||
|
'activities',
|
||||||
|
'about',
|
||||||
|
// 'blacklisted',
|
||||||
|
// 'blacklisted_by_me',
|
||||||
|
'books',
|
||||||
|
'bdate',
|
||||||
|
'can_be_invited_group',
|
||||||
|
'can_post',
|
||||||
|
'can_see_all_posts',
|
||||||
|
'can_see_audio',
|
||||||
|
'can_send_friend_request',
|
||||||
|
'can_write_private_message',
|
||||||
|
'career',
|
||||||
|
'common_count',
|
||||||
|
'connections',
|
||||||
|
'contacts',
|
||||||
|
'city',
|
||||||
|
'country',
|
||||||
|
'crop_photo',
|
||||||
|
'domain',
|
||||||
|
'education',
|
||||||
|
'exports',
|
||||||
|
'followers_count',
|
||||||
|
'friend_status',
|
||||||
|
'has_photo',
|
||||||
|
'has_mobile',
|
||||||
|
'home_town',
|
||||||
|
'photo_50',
|
||||||
|
'photo_100',
|
||||||
|
'photo_200',
|
||||||
|
'photo_200_orig',
|
||||||
|
'photo_400_orig',
|
||||||
|
'photo_max',
|
||||||
|
'photo_max_orig',
|
||||||
|
'sex',
|
||||||
|
'site',
|
||||||
|
'schools',
|
||||||
|
'screen_name',
|
||||||
|
'status',
|
||||||
|
'verified',
|
||||||
|
'games',
|
||||||
|
'interests',
|
||||||
|
'is_favorite',
|
||||||
|
'is_friend',
|
||||||
|
'is_hidden_from_feed',
|
||||||
|
'last_seen',
|
||||||
|
'maiden_name',
|
||||||
|
'military',
|
||||||
|
'movies',
|
||||||
|
'music',
|
||||||
|
'nickname',
|
||||||
|
'occupation',
|
||||||
|
'online',
|
||||||
|
'personal',
|
||||||
|
'photo_id',
|
||||||
|
'quotes',
|
||||||
|
'relation',
|
||||||
|
'relatives',
|
||||||
|
'timezone',
|
||||||
|
'tv',
|
||||||
|
'universities'
|
||||||
|
])[0];
|
||||||
|
|
||||||
|
if (!empty($response)) {
|
||||||
|
// Получен ответ
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновление данных аккаунта ВКонтакте
|
||||||
|
*
|
||||||
|
* Все файлы (аватар, например) будут скачаны на сервер
|
||||||
|
*
|
||||||
|
* @param _document $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param stdClass $data Информация об аккаунте (self::parse() или json_decode())
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось обновить
|
||||||
|
*/
|
||||||
|
public static function update(_document $vk, stdClass $data, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
if (empty($vk->id) and isset($data->user_id) || isset($data->id)) {
|
||||||
|
// Получен идентификатор
|
||||||
|
|
||||||
|
// Запись
|
||||||
|
$vk->id = $data->user_id ?? $data->id;
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->user_id, $data->id);
|
||||||
|
} else if (empty($vk->id)) throw new exception('Не удалось найти идентификатор аккаунта ВКонтакте');
|
||||||
|
|
||||||
|
if (isset($data->access_token, $data->expires_in)) {
|
||||||
|
// Получен ключ
|
||||||
|
|
||||||
|
// Запись
|
||||||
|
$vk->access = [
|
||||||
|
'key' => $data->access_token,
|
||||||
|
'expires' => $data->expires_in
|
||||||
|
];
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->access_token, $data->expires_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация браузера
|
||||||
|
$browser = new browser();
|
||||||
|
|
||||||
|
// Инициализация директории с обложкой
|
||||||
|
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
|
||||||
|
mkdir($path, 0775, true);
|
||||||
|
|
||||||
|
if (isset($data->photo_50)) {
|
||||||
|
// Получено изображение 50x50
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_50, ['sink' => $file = "$path/50x50.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'50x50' => ($vk->cover['50x50'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_50,
|
||||||
|
'public' => "/storage/$vk->id/cover/50x50.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 50x50 с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация директории с обложкой
|
||||||
|
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
|
||||||
|
mkdir($path, 0775, true);
|
||||||
|
|
||||||
|
if (isset($data->photo_100)) {
|
||||||
|
// Получено изображение 100x100
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_100, ['sink' => $file = "$path/100x100.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'100x100' => ($vk->cover['100x100'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_100,
|
||||||
|
'public' => "/storage/$vk->id/cover/100x100.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 100x100 с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация директории с обложкой
|
||||||
|
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
|
||||||
|
mkdir($path, 0775, true);
|
||||||
|
|
||||||
|
if (isset($data->photo_200)) {
|
||||||
|
// Получено изображение 200x200
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_200, ['sink' => $file = "$path/200x200.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'200x200' => ($vk->cover['200x200'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_200,
|
||||||
|
'public' => "/storage/$vk->id/cover/200x200.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 200x200 с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_200_orig)) {
|
||||||
|
// Получено изображение 200x
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_200_orig, ['sink' => $file = "$path/200x.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'200x' => ($vk->cover['200x'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_200_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/200x.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 200x с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_200_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_400_orig)) {
|
||||||
|
// Получено изображение 400x
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_400_orig, ['sink' => $file = "$path/400x.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'400x' => ($vk->cover['400x'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_400_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/400x.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 400x с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_400_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_max)) {
|
||||||
|
// Получено изображение MAXxMAX
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_max, ['sink' => $file = "$path/MAXxMAX.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'MAXxMAX' => ($vk->cover['MAXxMAX'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_max,
|
||||||
|
'public' => "/storage/$vk->id/cover/MAXxMAX.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение MAXxMAX с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_max_orig)) {
|
||||||
|
// Получено изображение MAXx
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_max_orig, ['sink' => $file = "$path/MAXx.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'MAXx' => ($vk->cover['MAXx'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_max_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/MAXx.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение MAXx с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_max_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->crop_photo)) {
|
||||||
|
// Получено изображение MAXx
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_max_orig, ['sink' => $file = "$path/MAXx.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'MAXx' => ($vk->cover['MAXx'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_max_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/MAXx.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение MAXx с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_max_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перебор оставшихся параметров
|
||||||
|
foreach ($data as $key => $value) $vk->{$key} = $value;
|
||||||
|
|
||||||
|
if (document::update(static::$db->session, $vk)) {
|
||||||
|
// Записано обновление
|
||||||
|
|
||||||
|
return $vk;
|
||||||
|
} else throw new exception('Не удалось записать данные аккаунта ВКонтакте');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
section#account {
|
||||||
|
z-index : 10000;
|
||||||
|
right : 40px;
|
||||||
|
bottom : 40px;
|
||||||
|
position: absolute;
|
||||||
|
display : flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#account>button#login {
|
||||||
|
width : 80px;
|
||||||
|
height : 80px;
|
||||||
|
display : flex;
|
||||||
|
border-radius : 100%;
|
||||||
|
cursor : pointer;
|
||||||
|
overflow : hidden;
|
||||||
|
background-color: var(--background-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
section#account>button#login>i.icon.authentication {
|
||||||
|
width : 12px;
|
||||||
|
margin : auto;
|
||||||
|
color : var(--text-inverse);
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#account>button#login:hover>i.icon.authentication {
|
||||||
|
background-color: var(--grey-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
section#account>button#login:active>i.icon.authentication {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
main>section#books {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>* {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>form.upload {
|
||||||
|
width: calc(100% / 3 - 20px - 9px * 2);
|
||||||
|
height: calc(220px - 9px * 2);
|
||||||
|
margin: 5px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>form.upload>p {
|
||||||
|
font-size: 3rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>article.book {
|
||||||
|
width: calc(100% / 3 - 20px);
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>article.book:nth-child(3) {
|
||||||
|
width: calc(100% / 3);
|
||||||
|
margin-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>article.book>img {
|
||||||
|
height: 220px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: right;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>article.book>h4 {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>section#books>article.book>p {
|
||||||
|
margin: unset;
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
@keyframes node-select {
|
||||||
|
from {
|
||||||
|
outline-offset: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
outline : 2px solid var(--grey-light);
|
||||||
|
outline-offset: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes node-select-revert {
|
||||||
|
from {
|
||||||
|
outline : 2px solid var(--grey-light);
|
||||||
|
outline-offset: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
outline-offset: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes description-select {
|
||||||
|
from {
|
||||||
|
outline-offset: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
outline : 2px solid var(--grey);
|
||||||
|
outline-offset: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes description-select-revert {
|
||||||
|
from {
|
||||||
|
outline : 2px solid var(--grey);
|
||||||
|
outline-offset: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
outline-offset: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph {
|
||||||
|
width : 100vw;
|
||||||
|
height : 100vh;
|
||||||
|
position : absolute;
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph * {
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph:active {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node {
|
||||||
|
z-index : 500;
|
||||||
|
position : absolute;
|
||||||
|
display : flex;
|
||||||
|
cursor : grab;
|
||||||
|
border-radius : 100%;
|
||||||
|
background-color : var(--node-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.animated {
|
||||||
|
animation-duration : 0.1s;
|
||||||
|
animation-name : node-select-revert;
|
||||||
|
animation-fill-mode : forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.animated:hover {
|
||||||
|
animation-duration : 0.1s;
|
||||||
|
animation-name : node-select;
|
||||||
|
animation-fill-mode : forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node:active {
|
||||||
|
cursor : grabbing;
|
||||||
|
background-color: var(--node-background-important);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>h4.title {
|
||||||
|
margin : auto;
|
||||||
|
text-align: center;
|
||||||
|
cursor : pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description {
|
||||||
|
z-index : 1000;
|
||||||
|
position : absolute;
|
||||||
|
text-align : justify;
|
||||||
|
text-align-last : center;
|
||||||
|
border-radius : 100%;
|
||||||
|
overflow : hidden;
|
||||||
|
background-color: var(--node-background-completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section.graph article.node>div.description.animated {
|
||||||
|
animation-duration : 0.1s;
|
||||||
|
animation-name : description-select-revert;
|
||||||
|
animation-fill-mode : forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description.animated:hover {
|
||||||
|
animation-duration : 0.1s;
|
||||||
|
animation-name : description-select;
|
||||||
|
animation-fill-mode : forwards;
|
||||||
|
} */
|
||||||
|
|
||||||
|
section.graph article.node * {
|
||||||
|
transition: 0.1s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>span.wrapper {
|
||||||
|
width : 50%;
|
||||||
|
height : 100%;
|
||||||
|
shape-margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>span.left.wrapper {
|
||||||
|
float : left;
|
||||||
|
shape-outside: polygon(100% 0%, 0% 0%, 0% 100%, 100% 100%, 68% 98%, 38% 90%, 10% 72%, 0% 50%, 10% 28%, 20% 20%, 100% 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>span.right.wrapper {
|
||||||
|
float : right;
|
||||||
|
shape-outside: polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%, 0% 20%, 82% 20%, 90% 28%, 100% 50%, 90% 72%, 60% 90%, 32% 98%);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>p {
|
||||||
|
z-index : 1500;
|
||||||
|
position : relative;
|
||||||
|
margin : 0;
|
||||||
|
opacity : 0;
|
||||||
|
word-break: break-all;
|
||||||
|
color : var(--text-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description:hover>p {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>a.source {
|
||||||
|
z-index : 2000;
|
||||||
|
top : calc(20% - 1.3em + (1em - 1.3ex));
|
||||||
|
left : 0;
|
||||||
|
position : absolute;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size : 1.3em;
|
||||||
|
opacity : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.white>div.description>a.source {
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.white>div.description>a.source:active {
|
||||||
|
color: var(--text-inverse-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.white>div.description>a.source:hover {
|
||||||
|
color: var(--text-inverse-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.red>div.description>a.source {
|
||||||
|
color: var(--text-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.red>div.description>a.source:active {
|
||||||
|
color: var(--text-red-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node.red>div.description>a.source:hover {
|
||||||
|
color: var(--text-red-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>a.source.red:active {
|
||||||
|
color: var(--text-red-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description:hover>a.source {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>a.source:visited ::after {
|
||||||
|
content : '';
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
background-color: var(--node-background-completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description>img.cover {
|
||||||
|
left : 0;
|
||||||
|
top : 0;
|
||||||
|
position : absolute;
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
object-fit : cover;
|
||||||
|
pointer-events: none;
|
||||||
|
filter : unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>div.description:hover>img.cover {
|
||||||
|
filter: blur(5px) brightness(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>i.close {
|
||||||
|
z-index : -2000;
|
||||||
|
top : -10%;
|
||||||
|
right : -10%;
|
||||||
|
position : absolute;
|
||||||
|
scale : 0;
|
||||||
|
opacity : 0;
|
||||||
|
cursor : pointer;
|
||||||
|
color : var(--text);
|
||||||
|
transition: 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>i.close:hover {
|
||||||
|
scale : 1.4 !important;
|
||||||
|
color : var(--text-hover);
|
||||||
|
transition: 0.1s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph article.node>i.close:active {
|
||||||
|
scale : 1.2 !important;
|
||||||
|
color : var(--text-active);
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph svg.connection {
|
||||||
|
z-index : -500;
|
||||||
|
position: absolute;
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.graph svg.connection>line {
|
||||||
|
stroke: var(--connection);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
section.hotline {
|
||||||
|
display: inline-flex;
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline * {
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline:last-child {
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline>article {
|
||||||
|
margin-right : 18px;
|
||||||
|
width : 140px;
|
||||||
|
height : 190px;
|
||||||
|
display : flex;
|
||||||
|
align-self : flex-end;
|
||||||
|
border-radius : 3px;
|
||||||
|
background-color: var(--background-light-1);
|
||||||
|
box-shadow : 0px -6px 6px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline>article:last-child {
|
||||||
|
margin-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.hotline>article>* {
|
||||||
|
margin: auto;
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
.icon.authentication {
|
||||||
|
box-sizing : border-box;
|
||||||
|
position : relative;
|
||||||
|
display : block;
|
||||||
|
transform : scale(var(--ggs, 1));
|
||||||
|
width : 6px;
|
||||||
|
height : 16px;
|
||||||
|
border : 2px solid;
|
||||||
|
border-left : 0;
|
||||||
|
border-top-right-radius : 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
margin-right : -10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.authentication::after,
|
||||||
|
.icon.authentication::before {
|
||||||
|
content : "";
|
||||||
|
display : block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position : absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.authentication::after {
|
||||||
|
border-top : 2px solid;
|
||||||
|
border-right: 2px solid;
|
||||||
|
transform : rotate(45deg);
|
||||||
|
width : 8px;
|
||||||
|
height : 8px;
|
||||||
|
left : -8px;
|
||||||
|
bottom : 2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.authentication::before {
|
||||||
|
border-radius: 3px;
|
||||||
|
width : 10px;
|
||||||
|
height : 2px;
|
||||||
|
background : currentColor;
|
||||||
|
left : -11px;
|
||||||
|
bottom : 5px
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
.icon.close {
|
||||||
|
box-sizing : border-box;
|
||||||
|
position : relative;
|
||||||
|
display : block;
|
||||||
|
transform : scale(var(--ggs, 1));
|
||||||
|
width : 22px;
|
||||||
|
height : 22px;
|
||||||
|
border : 2px solid transparent;
|
||||||
|
border-radius: 40px
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.close::after,
|
||||||
|
.icon.close::before {
|
||||||
|
content : "";
|
||||||
|
display : block;
|
||||||
|
box-sizing : border-box;
|
||||||
|
position : absolute;
|
||||||
|
width : 16px;
|
||||||
|
height : 2px;
|
||||||
|
background : currentColor;
|
||||||
|
transform : rotate(45deg);
|
||||||
|
border-radius: 5px;
|
||||||
|
top : 8px;
|
||||||
|
left : 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.close::after {
|
||||||
|
transform: rotate(-45deg)
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
@import url('/fonts/comissioner.ttf');
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--background : #eee;
|
||||||
|
--background-inverse : #221e1e;
|
||||||
|
--background-inverse-dark : #120f0f;
|
||||||
|
--node-background-important: #c3eac3;
|
||||||
|
--node-background-completed: #b0c0b0;
|
||||||
|
--node-background : #bdb;
|
||||||
|
--connection : #b2b7b2;
|
||||||
|
--connection-completed : #d1d1d1;
|
||||||
|
--text : #131313;
|
||||||
|
--text-hover : #292929;
|
||||||
|
--text-active : #0c0c0c;
|
||||||
|
--text-inverse : #e6e6e6;
|
||||||
|
--text-inverse-hover : #fff;
|
||||||
|
--text-inverse-active : #d0d0d0;
|
||||||
|
--text-red : #f8a2a2;
|
||||||
|
--text-red-hover : #ffbcbc;
|
||||||
|
--text-red-active : #e69191;
|
||||||
|
--link : #3c76ff;
|
||||||
|
--link-hover : #6594ff;
|
||||||
|
--link-active : #3064dd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background-light-3: #403939;
|
||||||
|
--background-light-2: #322d2d;
|
||||||
|
--background-light-1: #2b2525;
|
||||||
|
--background-light : #252020;
|
||||||
|
--background : #221e1e;
|
||||||
|
--block-background : ;
|
||||||
|
--node-background : #221e1e;
|
||||||
|
--text : #e6e6e6;
|
||||||
|
--text-hover : #fff;
|
||||||
|
--text-active : #d0d0d0;
|
||||||
|
--text-inverse : 'dark';
|
||||||
|
--red-light-1 : #dc4343;
|
||||||
|
--red-light : #bf3737;
|
||||||
|
--red : #a43333;
|
||||||
|
--red-dark : #8d2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--grey-light: #c0c0c0;
|
||||||
|
--grey : #858585;
|
||||||
|
--grey-dark : #565656;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
text-decoration: none;
|
||||||
|
outline : none;
|
||||||
|
border : none;
|
||||||
|
color : var(--text);
|
||||||
|
font-family : 'Commissioner', sans-serif;
|
||||||
|
transition : 0.1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselectable {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select : none;
|
||||||
|
-khtml-user-select : none;
|
||||||
|
-moz-user-select : none;
|
||||||
|
-ms-user-select : none;
|
||||||
|
user-select : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--link-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active {
|
||||||
|
color: var(--link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height : 100vh;
|
||||||
|
margin : 0;
|
||||||
|
overflow : hidden;
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
z-index : 500;
|
||||||
|
grid-column: 1/ 4;
|
||||||
|
grid-row : 2;
|
||||||
|
overflow : hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
z-index : 5000;
|
||||||
|
position : absolute;
|
||||||
|
display : flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow : 2px 0 5px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
header>menu {
|
||||||
|
margin : unset;
|
||||||
|
padding : 20px;
|
||||||
|
display : flex;
|
||||||
|
flex-direction : column;
|
||||||
|
flex-grow : 1;
|
||||||
|
background-color: var(--background-light-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
header>#account>button#login {
|
||||||
|
z-index: 1500;
|
||||||
|
}
|
||||||
|
|
||||||
|
header>menu a {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display : flex;
|
||||||
|
align-items : center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header>menu a:last-child {
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
header>menu a svg {
|
||||||
|
margin-right: 8px;
|
||||||
|
height : 1.2rem;
|
||||||
|
position : relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
header>menu a:hover svg {
|
||||||
|
margin-left : -5px;
|
||||||
|
margin-right: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header>menu a svg path {
|
||||||
|
fill: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
header>section {
|
||||||
|
background-color: var(--background-light-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
header :is(button, a[type="button"]) {
|
||||||
|
width : 100%;
|
||||||
|
height : 40px;
|
||||||
|
display : flex;
|
||||||
|
justify-content : center;
|
||||||
|
align-items : center;
|
||||||
|
cursor : pointer;
|
||||||
|
background-color: var(--red);
|
||||||
|
transition : unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
header button {
|
||||||
|
font-weight : bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
header :is(button, a[type="button"]):hover {
|
||||||
|
background-color: var(--red-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
header :is(button, a[type="button"]):active {
|
||||||
|
background-color: var(--red-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
header>nav {
|
||||||
|
margin-top : auto;
|
||||||
|
display : flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
z-index : 3000;
|
||||||
|
position: absolute;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
form.upload {
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
border: 4px dashed #e5ddd1;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.upload:hover {
|
||||||
|
background-color: #ccc6bd;
|
||||||
|
border: 4px dashed #fff7ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.upload>p {
|
||||||
|
margin: auto;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #eee6d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.upload:hover>p {
|
||||||
|
color: #fff7ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.upload>input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.upload:hover>input {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg height="32" width="32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h32v32H0z"/><path d="M30 17.349V12h-2v-2h2V2H2v8h2v2H2v8h2v2H2v8h15.349A8.97 8.97 0 0 0 23 32a9.002 9.002 0 0 0 9-9 8.968 8.968 0 0 0-2-5.651zM14.059 22H6v-2h8.522a8.932 8.932 0 0 0-.463 2zM26 12H6v-2h20v2zM4 6h4v2H4V6zm0 10h4v2H4v-2zm4 12H4v-2h4v2zm15 1.883A6.898 6.898 0 0 1 16.115 23 6.898 6.898 0 0 1 23 16.115 6.898 6.898 0 0 1 29.883 23 6.898 6.898 0 0 1 23 29.883z"/><path d="m19 25 2 2 2-2 2 2 2-2-2-2 2-2-2-2-2 2-2-2-2 2 2 2z"/></svg>
|
After Width: | Height: | Size: 552 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M0 6c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm2 0v2h2V6H2zm1 3v2h2V9H3zm-1 3v2h2v-2H2zm3 0v2h10v-2H5zm11 0v2h2v-2h-2zM6 9v2h2V9H6zm3 0v2h2V9H9zm3 0v2h2V9h-2zm3 0v2h2V9h-2zM5 6v2h2V6H5zm3 0v2h2V6H8zm3 0v2h2V6h-2zm3 0v2h4V6h-4z"/></svg>
|
After Width: | Height: | Size: 328 B |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h256v256H0z"/><path d="M224 177.3V78.7a8.1 8.1 0 0 0-4.1-7l-88-49.5a7.8 7.8 0 0 0-7.8 0l-88 49.5a8.1 8.1 0 0 0-4.1 7v98.6a8.1 8.1 0 0 0 4.1 7l88 49.5a7.8 7.8 0 0 0 7.8 0l88-49.5a8.1 8.1 0 0 0 4.1-7Z" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m222.9 74.6-94 53.4-95.8-53.4M128.9 128l-.9 106.8"/></svg>
|
After Width: | Height: | Size: 537 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m11.136 12.117-.596 2.415c.736.185 3.004.921 3.34-.441.35-1.421-2.009-1.789-2.744-1.974Zm.813-3.297-.54 2.191c.612.154 2.5.784 2.806-.455.318-1.293-1.654-1.581-2.266-1.736ZM12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Zm4.358 8.575a1.743 1.743 0 0 1-1.385 1.611 1.933 1.933 0 0 1 .997 2.661c-.586 1.692-1.977 1.835-3.827 1.481l-.449 1.82-1.085-.274.443-1.795c-.28-.07-.568-.145-.864-.227l-.445 1.804-1.084-.273.45-1.824c-.254-.065-.511-.135-.774-.201l-1.412-.356.539-1.256s.8.215.788.199a.394.394 0 0 0 .498-.26l1.217-4.939a.583.583 0 0 0-.505-.638c.016-.011-.789-.198-.789-.198l.29-1.172 1.495.378-.001.006c.225.056.457.11.693.164l.444-1.802 1.085.274-.436 1.766c.291.068.584.135.87.207l.432-1.755 1.085.274-.445 1.802c1.37.477 2.372 1.193 2.175 2.523Z"/></svg>
|
After Width: | Height: | Size: 825 B |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 397 KiB |
After Width: | Height: | Size: 295 KiB |
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account;
|
||||||
|
|
||||||
|
use mirzaev\minimal\core;
|
||||||
|
use mirzaev\minimal\router;
|
||||||
|
|
||||||
|
ini_set('error_reporting', E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
|
||||||
|
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
|
||||||
|
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
|
||||||
|
define('INDEX', __DIR__);
|
||||||
|
|
||||||
|
// Автозагрузка
|
||||||
|
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
// Инициализация маршрутазитора
|
||||||
|
$router = new router;
|
||||||
|
|
||||||
|
// Запись маршрутов
|
||||||
|
$router->write('/', 'index', 'index');
|
||||||
|
$router->write('/system/hotline', 'hotline', 'index');
|
||||||
|
$router->write('/system/graph', 'graph', 'index');
|
||||||
|
$router->write('/account/initialization', 'account', 'initialization', 'PUT');
|
||||||
|
$router->write('/account/vk/connect', 'account', 'connect');
|
||||||
|
$router->write('/account/panel', 'account', 'panel');
|
||||||
|
|
||||||
|
// Инициализация ядра
|
||||||
|
$core = new core(namespace: __NAMESPACE__, router: $router);
|
||||||
|
|
||||||
|
// Обработка запроса
|
||||||
|
echo $core->start();
|
|
@ -0,0 +1,26 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
class account {
|
||||||
|
static async initialization() {
|
||||||
|
// Запрос
|
||||||
|
return fetch('https://auth.mirzaev.sexy/account/initialization', {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static authentication() {
|
||||||
|
// Инициализация аккаунта
|
||||||
|
alert(1);
|
||||||
|
this.initialization()
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
alert(2);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static deauthentication() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,668 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Бегущая строка
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Простой, но мощный класс для создания бегущих строк. Поддерживает
|
||||||
|
* перемещение мышью и прокрутку колесом, полностью настраивается очень гибок
|
||||||
|
* для настроек в CSS и подразумевается, что отлично индексируется поисковыми роботами.
|
||||||
|
* Имеет свой препроцессор, благодаря которому можно создавать бегущие строки
|
||||||
|
* без программирования - с помощью HTML-аттрибутов, а так же возможность
|
||||||
|
* изменять параметры (data-hotline-* аттрибуты) на лету. Есть возможность вызывать
|
||||||
|
* события при выбранных действиях для того, чтобы пользователь имел возможность
|
||||||
|
* дорабатывать функционал без изучения и изменения моего кода
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* сonst hotline = new hotline();
|
||||||
|
* hotline.step = '-5';
|
||||||
|
* hotline.start();
|
||||||
|
*
|
||||||
|
* @todo
|
||||||
|
* 1. Бесконечный режим - элементы не удаляются если видны на экране (будут дубликаты)
|
||||||
|
*
|
||||||
|
* @copyright WTFPL
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class hotline {
|
||||||
|
// Идентификатор
|
||||||
|
#id = 0;
|
||||||
|
|
||||||
|
// Оболочка (instanceof HTMLElement)
|
||||||
|
#shell = document.getElementById("hotline");
|
||||||
|
|
||||||
|
// Инстанция горячей строки
|
||||||
|
#instance = null;
|
||||||
|
|
||||||
|
// Перемещение
|
||||||
|
#transfer = true;
|
||||||
|
|
||||||
|
// Движение
|
||||||
|
#move = true;
|
||||||
|
|
||||||
|
// Наблюдатель
|
||||||
|
#observer = null;
|
||||||
|
|
||||||
|
// Наблюдатель
|
||||||
|
#block = new Set(["events"]);
|
||||||
|
|
||||||
|
// Настраиваемые параметры
|
||||||
|
transfer = null;
|
||||||
|
move = null;
|
||||||
|
delay = 10;
|
||||||
|
step = 1;
|
||||||
|
hover = true;
|
||||||
|
movable = true;
|
||||||
|
sticky = false;
|
||||||
|
wheel = false;
|
||||||
|
delta = null;
|
||||||
|
vertical = false;
|
||||||
|
observe = false;
|
||||||
|
events = new Map([
|
||||||
|
["start", false],
|
||||||
|
["stop", false],
|
||||||
|
["move", false],
|
||||||
|
["move.block", false],
|
||||||
|
["move.unblock", false],
|
||||||
|
["offset", false],
|
||||||
|
["transfer.start", true],
|
||||||
|
["transfer.end", true],
|
||||||
|
["onmousemove", false]
|
||||||
|
]);
|
||||||
|
|
||||||
|
constructor(id, shell) {
|
||||||
|
// Запись идентификатора
|
||||||
|
if (typeof id === "string" || typeof id === "number") this.#id = id;
|
||||||
|
|
||||||
|
// Запись оболочки
|
||||||
|
if (shell instanceof HTMLElement) this.#shell = shell;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (this.#instance === null) {
|
||||||
|
// Нет запущенной инстанции бегущей строки
|
||||||
|
|
||||||
|
// Инициализация ссылки на ядро
|
||||||
|
const _this = this;
|
||||||
|
|
||||||
|
// Запуск движения
|
||||||
|
this.#instance = setInterval(function () {
|
||||||
|
if (_this.#shell.childElementCount > 1) {
|
||||||
|
// Найдено содержимое бегущей строки (2 и более)
|
||||||
|
|
||||||
|
// Инициализация буфера для временных данных
|
||||||
|
let buffer;
|
||||||
|
|
||||||
|
// Инициализация данных первого элемента в строке
|
||||||
|
const first = {
|
||||||
|
element: (buffer = _this.#shell.firstElementChild),
|
||||||
|
coords: buffer.getBoundingClientRect()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_this.vertical) {
|
||||||
|
// Вертикальная бегущая строка
|
||||||
|
|
||||||
|
// Инициализация сдвига у первого элемента (движение)
|
||||||
|
first.offset = isNaN(
|
||||||
|
(buffer = parseFloat(first.element.style.marginTop))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer;
|
||||||
|
|
||||||
|
// Инициализация отступа до второго элемента у первого элемента (разделение)
|
||||||
|
first.separator = isNaN(
|
||||||
|
(buffer = parseFloat(
|
||||||
|
getComputedStyle(first.element).marginBottom
|
||||||
|
))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer;
|
||||||
|
|
||||||
|
// Инициализация крайнего с конца ребра первого элемента в строке
|
||||||
|
first.end = first.coords.y + first.coords.height + first.separator;
|
||||||
|
} else {
|
||||||
|
// Горизонтальная бегущая строка
|
||||||
|
|
||||||
|
// Инициализация отступа у первого элемента (движение)
|
||||||
|
first.offset = isNaN(
|
||||||
|
(buffer = parseFloat(first.element.style.marginLeft))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer;
|
||||||
|
|
||||||
|
// Инициализация отступа до второго элемента у первого элемента (разделение)
|
||||||
|
first.separator = isNaN(
|
||||||
|
(buffer = parseFloat(getComputedStyle(first.element).marginRight))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer;
|
||||||
|
|
||||||
|
// Инициализация крайнего с конца ребра первого элемента в строке
|
||||||
|
first.end = first.coords.x + first.coords.width + first.separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(_this.vertical &&
|
||||||
|
Math.round(first.end) < _this.#shell.offsetTop) ||
|
||||||
|
(!_this.vertical && Math.round(first.end) < _this.#shell.offsetLeft)
|
||||||
|
) {
|
||||||
|
// Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(_this.transfer === null && _this.#transfer) ||
|
||||||
|
_this.transfer === true
|
||||||
|
) {
|
||||||
|
// Перенос разрешен
|
||||||
|
|
||||||
|
if (_this.vertical) {
|
||||||
|
// Вертикальная бегущая строка
|
||||||
|
|
||||||
|
// Удаление отступов (движения)
|
||||||
|
first.element.style.marginTop = null;
|
||||||
|
} else {
|
||||||
|
// Горизонтальная бегущая строка
|
||||||
|
|
||||||
|
// Удаление отступов (движения)
|
||||||
|
first.element.style.marginLeft = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Копирование первого элемента в конец строки
|
||||||
|
_this.#shell.appendChild(first.element);
|
||||||
|
|
||||||
|
if (_this.events.get("transfer.end")) {
|
||||||
|
// Запрошен вызов события: "перемещение в конец"
|
||||||
|
|
||||||
|
// Вызов события: "перемещение в конец"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.transfer.end`, {
|
||||||
|
detail: {
|
||||||
|
element: first.element,
|
||||||
|
offset: -(
|
||||||
|
(_this.vertical
|
||||||
|
? first.coords.height
|
||||||
|
: first.coords.width) + first.separator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(_this.vertical &&
|
||||||
|
Math.round(first.coords.y) > _this.#shell.offsetTop) ||
|
||||||
|
(!_this.vertical &&
|
||||||
|
Math.round(first.coords.x) > _this.#shell.offsetLeft)
|
||||||
|
) {
|
||||||
|
// Передняя (движущая) граница первого элемента вышла из области видимости
|
||||||
|
|
||||||
|
if (
|
||||||
|
(_this.transfer === null && _this.#transfer) ||
|
||||||
|
_this.transfer === true
|
||||||
|
) {
|
||||||
|
// Перенос разрешен
|
||||||
|
|
||||||
|
// Инициализация отступа у последнего элемента (разделение)
|
||||||
|
const separator =
|
||||||
|
(buffer = isNaN(
|
||||||
|
(buffer = parseFloat(
|
||||||
|
getComputedStyle(_this.#shell.lastElementChild)[
|
||||||
|
_this.vertical ? "marginBottom" : "marginRight"
|
||||||
|
]
|
||||||
|
))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer) === 0
|
||||||
|
? first.separator
|
||||||
|
: buffer;
|
||||||
|
|
||||||
|
// Инициализация координат первого элемента в строке
|
||||||
|
const coords = _this.#shell.lastElementChild.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (_this.vertical) {
|
||||||
|
// Вертикальная бегущая строка
|
||||||
|
|
||||||
|
// Удаление отступов (движения)
|
||||||
|
_this.#shell.lastElementChild.style.marginTop =
|
||||||
|
-coords.height - separator + "px";
|
||||||
|
} else {
|
||||||
|
// Горизонтальная бегущая строка
|
||||||
|
|
||||||
|
// Удаление отступов (движения)
|
||||||
|
_this.#shell.lastElementChild.style.marginLeft =
|
||||||
|
-coords.width - separator + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Копирование последнего элемента в начало строки
|
||||||
|
_this.#shell.insertBefore(
|
||||||
|
_this.#shell.lastElementChild,
|
||||||
|
first.element
|
||||||
|
);
|
||||||
|
|
||||||
|
// Удаление отступов у второго элемента в строке (движения)
|
||||||
|
_this.#shell.children[1].style[
|
||||||
|
_this.vertical ? "marginTop" : "marginLeft"
|
||||||
|
] = null;
|
||||||
|
|
||||||
|
if (_this.events.get("transfer.start")) {
|
||||||
|
// Запрошен вызов события: "перемещение в начало"
|
||||||
|
|
||||||
|
// Вызов события: "перемещение в начало"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.transfer.start`, {
|
||||||
|
detail: {
|
||||||
|
element: _this.#shell.lastElementChild,
|
||||||
|
offset:
|
||||||
|
(_this.vertical ? coords.height : coords.width) +
|
||||||
|
separator
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Элемент в области видимости
|
||||||
|
|
||||||
|
if ((_this.move === null && _this.#move) || _this.move === true) {
|
||||||
|
// Движение разрешено
|
||||||
|
|
||||||
|
// Запись новых координат сдвига
|
||||||
|
const offset = first.offset + _this.step;
|
||||||
|
|
||||||
|
// Запись сдвига (движение)
|
||||||
|
_this.offset(offset);
|
||||||
|
|
||||||
|
if (_this.events.get("move")) {
|
||||||
|
// Запрошен вызов события: "движение"
|
||||||
|
|
||||||
|
// Вызов события: "движение"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.move`, {
|
||||||
|
detail: {
|
||||||
|
from: first.offset,
|
||||||
|
to: offset
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _this.delay);
|
||||||
|
|
||||||
|
if (this.hover) {
|
||||||
|
// Запрошена возможность останавливать бегущую строку
|
||||||
|
|
||||||
|
// Инициализация сдвига
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
// Инициализация слушателя события при перемещении элемента в бегущей строке
|
||||||
|
const listener = function (e) {
|
||||||
|
// Увеличение сдвига
|
||||||
|
offset += e.detail.offset ?? 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Инициализация обработчика наведения курсора (остановка движения)
|
||||||
|
this.#shell.onmouseover = function (e) {
|
||||||
|
// Курсор наведён на бегущую строку
|
||||||
|
|
||||||
|
// Блокировка движения
|
||||||
|
_this.#move = false;
|
||||||
|
|
||||||
|
if (_this.events.get("move.block")) {
|
||||||
|
// Запрошен вызов события: "блокировка движения"
|
||||||
|
|
||||||
|
// Вызов события: "блокировка движения"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.move.block`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_this.movable) {
|
||||||
|
// Запрошена возможность двигать бегущую строку
|
||||||
|
|
||||||
|
_this.#shell.onmousedown = function (onmousedown) {
|
||||||
|
// Курсор активирован
|
||||||
|
|
||||||
|
// Инициализация слушателей события перемещения элемента в бегущей строке
|
||||||
|
document.addEventListener(
|
||||||
|
`hotline.${_this.#id}.transfer.start`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
document.addEventListener(
|
||||||
|
`hotline.${_this.#id}.transfer.end`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
|
||||||
|
// Инициализация буфера для временных данных
|
||||||
|
let buffer;
|
||||||
|
|
||||||
|
// Инициализация данных первого элемента в строке
|
||||||
|
const first = {
|
||||||
|
offset: isNaN(
|
||||||
|
(buffer = parseFloat(
|
||||||
|
_this.vertical
|
||||||
|
? _this.#shell.firstElementChild.style.marginTop
|
||||||
|
: _this.#shell.firstElementChild.style.marginLeft
|
||||||
|
))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
document.onmousemove = function (onmousemove) {
|
||||||
|
// Курсор движется
|
||||||
|
|
||||||
|
if (_this.vertical) {
|
||||||
|
// Вертикальная бегущая строка
|
||||||
|
|
||||||
|
// Инициализация буфера местоположения
|
||||||
|
const from = _this.#shell.firstElementChild.style.marginTop;
|
||||||
|
const to = onmousemove.pageY - (onmousedown.pageY + offset - first.offset);
|
||||||
|
|
||||||
|
// Движение
|
||||||
|
_this.#shell.firstElementChild.style.marginTop = to +
|
||||||
|
"px";
|
||||||
|
|
||||||
|
if (_this.events.get("onmousemove")) {
|
||||||
|
// Запрошен вызов события: "перемещение мышью"
|
||||||
|
|
||||||
|
// Вызов события: "перемещение мышью"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.onmousemove`, {
|
||||||
|
detail: { from, to }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Горизонтальная бегущая строка
|
||||||
|
|
||||||
|
// Инициализация буфера местоположения
|
||||||
|
const from = _this.#shell.firstElementChild.style.marginLeft;
|
||||||
|
const to = onmousemove.pageX - (onmousedown.pageX + offset - first.offset);
|
||||||
|
|
||||||
|
// Движение
|
||||||
|
_this.#shell.firstElementChild.style.marginLeft = to + "px";
|
||||||
|
|
||||||
|
if (_this.events.get("onmousemove")) {
|
||||||
|
// Запрошен вызов события: "перемещение мышью"
|
||||||
|
|
||||||
|
// Вызов события: "перемещение мышью"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.onmousemove`, {
|
||||||
|
detail: { from, to }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись курсора
|
||||||
|
_this.#shell.style.cursor = "grabbing";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Перещапись событий браузера (чтобы не дёргалось)
|
||||||
|
_this.#shell.ondragstart = null;
|
||||||
|
|
||||||
|
_this.#shell.onmouseup = function () {
|
||||||
|
// Курсор деактивирован
|
||||||
|
|
||||||
|
// Остановка обработки движения
|
||||||
|
document.onmousemove = null;
|
||||||
|
|
||||||
|
// Сброс сдвига
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
document.removeEventListener(
|
||||||
|
`hotline.${_this.#id}.transfer.start`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
document.removeEventListener(
|
||||||
|
`hotline.${_this.#id}.transfer.end`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
|
||||||
|
// Восстановление курсора
|
||||||
|
_this.#shell.style.cursor = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Инициализация обработчика отведения курсора (остановка движения)
|
||||||
|
this.#shell.onmouseleave = function (onmouseleave) {
|
||||||
|
// Курсор отведён от бегущей строки
|
||||||
|
|
||||||
|
if (!_this.sticky) {
|
||||||
|
// Отключено прилипание
|
||||||
|
|
||||||
|
// Остановка обработки движения
|
||||||
|
document.onmousemove = null;
|
||||||
|
|
||||||
|
document.removeEventListener(
|
||||||
|
`hotline.${_this.#id}.transfer.start`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
document.removeEventListener(
|
||||||
|
`hotline.${_this.#id}.transfer.end`,
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
|
||||||
|
// Восстановление курсора
|
||||||
|
_this.#shell.style.cursor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сброс сдвига
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
// Разблокировка движения
|
||||||
|
_this.#move = true;
|
||||||
|
|
||||||
|
if (_this.events.get("move.unblock")) {
|
||||||
|
// Запрошен вызов события: "разблокировка движения"
|
||||||
|
|
||||||
|
// Вызов события: "разблокировка движения"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${_this.#id}.move.unblock`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wheel) {
|
||||||
|
// Запрошена возможность прокручивать колесом мыши
|
||||||
|
|
||||||
|
// Инициализация обработчика наведения курсора (остановка движения)
|
||||||
|
this.#shell.onwheel = function (e) {
|
||||||
|
// Курсор наведён на бегущую
|
||||||
|
|
||||||
|
// Инициализация буфера для временных данных
|
||||||
|
let buffer;
|
||||||
|
|
||||||
|
// Перемещение
|
||||||
|
_this.offset(
|
||||||
|
(isNaN(
|
||||||
|
(buffer = parseFloat(
|
||||||
|
_this.#shell.firstElementChild.style[
|
||||||
|
_this.vertical ? "marginTop" : "marginLeft"
|
||||||
|
]
|
||||||
|
))
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: buffer) +
|
||||||
|
(_this.delta === null
|
||||||
|
? e.wheelDelta
|
||||||
|
: e.wheelDelta > 0
|
||||||
|
? _this.delta
|
||||||
|
: -_this.delta)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.observe) {
|
||||||
|
// Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки
|
||||||
|
|
||||||
|
if (this.#observer === null) {
|
||||||
|
// Отсутствует наблюдатель
|
||||||
|
|
||||||
|
// Инициализация ссылки на ядро
|
||||||
|
const _this = this;
|
||||||
|
|
||||||
|
// Инициализация наблюдателя
|
||||||
|
this.#observer = new MutationObserver(function (mutations) {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === "attributes") {
|
||||||
|
// Запись параметра в инстанцию бегущей строки
|
||||||
|
_this.write(mutation.attributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перезапуск бегущей строки
|
||||||
|
_this.restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Активация наблюдения
|
||||||
|
this.#observer.observe(this.#shell, {
|
||||||
|
attributes: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (this.#observer instanceof MutationObserver) {
|
||||||
|
// Запрошено отключение наблюдения
|
||||||
|
|
||||||
|
// Деактивация наблюдения
|
||||||
|
this.#observer.disconnect();
|
||||||
|
|
||||||
|
// Удаление наблюдателя
|
||||||
|
this.#observer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.events.get("start")) {
|
||||||
|
// Запрошен вызов события: "запуск"
|
||||||
|
|
||||||
|
// Вызов события: "запуск"
|
||||||
|
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.start`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
// Остановка бегущей строки
|
||||||
|
clearInterval(this.#instance);
|
||||||
|
|
||||||
|
// Удаление инстанции интервала
|
||||||
|
this.#instance = null;
|
||||||
|
|
||||||
|
if (this.events.get("stop")) {
|
||||||
|
// Запрошен вызов события: "остановка"
|
||||||
|
|
||||||
|
// Вызов события: "остановка"
|
||||||
|
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
// Остановка бегущей строки
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
// Запуск бегущей строки
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
write(attribute) {
|
||||||
|
// Инициализация названия параметра
|
||||||
|
const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1];
|
||||||
|
|
||||||
|
if (typeof parameter === "string") {
|
||||||
|
// Параметр найден
|
||||||
|
|
||||||
|
// Проверка на разрешение изменения
|
||||||
|
if (this.#block.has(parameter)) return;
|
||||||
|
|
||||||
|
// Инициализация значения параметра
|
||||||
|
const value = this.#shell.getAttribute(attribute);
|
||||||
|
|
||||||
|
// Инициализация буфера для временных данных
|
||||||
|
let buffer;
|
||||||
|
|
||||||
|
// Запись параметра
|
||||||
|
this[parameter] = isNaN((buffer = parseFloat(value)))
|
||||||
|
? value === "true"
|
||||||
|
? true
|
||||||
|
: value === "false"
|
||||||
|
? false
|
||||||
|
: value
|
||||||
|
: buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset(value) {
|
||||||
|
// Запись отступа
|
||||||
|
this.#shell.firstElementChild.style[
|
||||||
|
this.vertical ? "marginTop" : "marginLeft"
|
||||||
|
] = value + "px";
|
||||||
|
|
||||||
|
if (this.events.get("offset")) {
|
||||||
|
// Запрошен вызов события: "сдвиг"
|
||||||
|
|
||||||
|
// Вызов события: "сдвиг"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.${this.#id}.offset`, {
|
||||||
|
detail: {
|
||||||
|
to: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static preprocessing(event = false) {
|
||||||
|
// Инициализация счётчиков инстанций горячей строки
|
||||||
|
const success = new Set();
|
||||||
|
let error = 0;
|
||||||
|
|
||||||
|
for (const element of document.querySelectorAll('*[data-hotline="true"]')) {
|
||||||
|
// Перебор бегущих строк
|
||||||
|
|
||||||
|
if (typeof element.id === "string") {
|
||||||
|
// Найден идентификатор
|
||||||
|
|
||||||
|
// Инициализация инстанции бегущей строки
|
||||||
|
const hotline = new this(element.id, element);
|
||||||
|
|
||||||
|
for (const attribute of element.getAttributeNames()) {
|
||||||
|
// Перебор аттрибутов
|
||||||
|
|
||||||
|
// Запись параметра в инстанцию бегущей строки
|
||||||
|
hotline.write(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск бегущей строки
|
||||||
|
hotline.start();
|
||||||
|
|
||||||
|
// Запись инстанции бегущей строки в элемент
|
||||||
|
element.hotline = hotline;
|
||||||
|
|
||||||
|
// Запись в счётчик успешных инициализаций
|
||||||
|
success.add(hotline);
|
||||||
|
} else ++error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
// Запрошен вызов события: "предварительная подготовка"
|
||||||
|
|
||||||
|
// Вызов события: "предварительная подготовка"
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(`hotline.preprocessed`, {
|
||||||
|
detail: {
|
||||||
|
success,
|
||||||
|
error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("hotline.loaded", {
|
||||||
|
detail: { hotline }
|
||||||
|
})
|
||||||
|
);
|
|
@ -0,0 +1,2 @@
|
||||||
|
/*! js-cookie v3.0.1 | MIT */
|
||||||
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}return function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"})}));
|
|
@ -0,0 +1,127 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
class troller {
|
||||||
|
static what = {
|
||||||
|
enable() {
|
||||||
|
document.body.onmouseleave = function () {
|
||||||
|
// if (Math.random() > 0.90) {
|
||||||
|
// 10%
|
||||||
|
|
||||||
|
troller.what.start();
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.onmouseenter = function () {
|
||||||
|
troller.what.end();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
disable() {
|
||||||
|
document.body.onmouseleave = document.body.onmouseenter = undefined;
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
// Отображение изображения
|
||||||
|
document.getElementById('what_image').classList.add('active');
|
||||||
|
|
||||||
|
// Инициализация элемента со звуком
|
||||||
|
const what_sound = document.getElementById('what_sound');
|
||||||
|
|
||||||
|
// Воспроизведение звука
|
||||||
|
what_sound.currentTime = 0;
|
||||||
|
what_sound.play();
|
||||||
|
},
|
||||||
|
end() {
|
||||||
|
// Сокрытие изображения
|
||||||
|
document.getElementById('what_image').classList.remove('active');
|
||||||
|
|
||||||
|
// Остановка звука
|
||||||
|
document.getElementById('what_sound').pause();
|
||||||
|
},
|
||||||
|
single(event = 'onmouseleave') {
|
||||||
|
if (typeof event === 'string') {
|
||||||
|
// Получены обязательные входные параметры
|
||||||
|
// Отображение изображения
|
||||||
|
document.getElementById('what_image').classList.add('active');
|
||||||
|
|
||||||
|
// Инициализация элемента со звуком
|
||||||
|
const what_sound = document.getElementById('what_sound');
|
||||||
|
|
||||||
|
// Воспроизведение звука
|
||||||
|
what_sound.currentTime = 0;
|
||||||
|
what_sound.play();
|
||||||
|
|
||||||
|
document.body[event] = function () {
|
||||||
|
troller.what.end();
|
||||||
|
|
||||||
|
document.body[event] = undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static vk() {
|
||||||
|
setInterval(function () {
|
||||||
|
const sound = document.getElementById('sound_vk');
|
||||||
|
|
||||||
|
if (Math.random() > 0.95) {
|
||||||
|
// 5%
|
||||||
|
|
||||||
|
// Воспроизведение звука
|
||||||
|
sound.currentTime = 0;
|
||||||
|
sound.play();
|
||||||
|
}
|
||||||
|
}, 85000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static whatsapp() {
|
||||||
|
setInterval(function () {
|
||||||
|
const sound = document.getElementById('sound_whatsup');
|
||||||
|
|
||||||
|
if (Math.random() > 0.97) {
|
||||||
|
// 3%
|
||||||
|
|
||||||
|
// Воспроизведение звука
|
||||||
|
sound.currentTime = 0;
|
||||||
|
sound.play();
|
||||||
|
}
|
||||||
|
}, 125000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static iphone() {
|
||||||
|
setInterval(function () {
|
||||||
|
const sound = document.getElementById('sound_iphone');
|
||||||
|
|
||||||
|
if (Math.random() > 0.98) {
|
||||||
|
// 2%
|
||||||
|
|
||||||
|
// Воспроизведение звука
|
||||||
|
sound.currentTime = 0;
|
||||||
|
sound.play();
|
||||||
|
}
|
||||||
|
}, 265000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.random() > 0.90) {
|
||||||
|
// 10%
|
||||||
|
|
||||||
|
troller.what.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.random() > 0.90) {
|
||||||
|
// 10%
|
||||||
|
|
||||||
|
troller.vk();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Math.random() > 0.90) {
|
||||||
|
// 10%
|
||||||
|
|
||||||
|
troller.whatsapp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.random() > 0.90) {
|
||||||
|
// 10%
|
||||||
|
|
||||||
|
troller.iphone();
|
||||||
|
}
|
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1 @@
|
||||||
|
arangodb.php
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'endpoint' => 'unix:///var/run/arangodb3/arango.sock',
|
||||||
|
'database' => '',
|
||||||
|
'name' => '',
|
||||||
|
'password' => ''
|
||||||
|
];
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% block css %}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/account.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/icon_authentication.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<section id="account">
|
||||||
|
{% if account %}
|
||||||
|
{{ account.getKey() }}
|
||||||
|
{% if vk %}
|
||||||
|
{{ vk.mail }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<button id="login" title="Войти в аккаунт" onclick="return account.authentication()"><i class='icon authentication'></i></button>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript" src="/js/account.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{% use 'account/element.html' with css as account_css, body as account_body, js as account_js %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ block('account_css') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<aside>
|
||||||
|
{{ block('account_body') }}
|
||||||
|
</aside>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ block('account_js') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_init %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="ru">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{% use 'head.html' with title as head_title, meta as head_meta, css as head_css %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ block('head_title') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
{{ block('head_meta') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ block('head_css') }}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% block body %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{% include 'js.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_init %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<footer>
|
||||||
|
<!-- <p><a href="http://www.anybrowser.org/campaign/"><img src="/img/logos/any_browser.gif" width="278" height="44" alt="Доступно на любом браузере" /></a></p> -->
|
||||||
|
<!-- <p><a href="/browsers"><img src="/img/logos/any_browser.gif" width="278" height="44" alt="Доступно на любом браузере" /></a></p> -->
|
||||||
|
</footer>
|
|
@ -0,0 +1,86 @@
|
||||||
|
{% block css %}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/graph.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/icon_close.css" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% if graph.id != empty %}
|
||||||
|
<section id="{{ graph.id }}" class="graph unselectable" {% for name, value in graph.attributes %} {{ name }}="{{value}}"
|
||||||
|
{% endfor %}>
|
||||||
|
{% for element in graph.elements %}
|
||||||
|
<{{element.tag??'article'}}>{{ element.content }}</{{element.tag??'article'}}>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script type="module" src="/js/victor.js" defer></script>
|
||||||
|
<script type="module" src="/js/graph.js" defer></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_init %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('graph.loaded', function (e) {
|
||||||
|
// Инициализация графика
|
||||||
|
{% if graph.id != empty %}
|
||||||
|
const core = new e.detail.graph(document.getElementById('{{ graph.id }}'));
|
||||||
|
|
||||||
|
core.write({
|
||||||
|
title: 'бебра'
|
||||||
|
});
|
||||||
|
const mirzaev = core.write({
|
||||||
|
title: 'Арсен Мирзаев',
|
||||||
|
description: ' абабабаба абабабабаабабабабаабабабабаабабабаба абабабаба абабабаба абабабабаабабабаба абабабаба абабабаба абабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабаба',
|
||||||
|
cover: 'https://sun9-east.userapi.com/sun9-27/s/v1/ig2/qOBvWvsDwPmMjOTqQbl0TuGHaoMwWQhhd81nxD847v32dT-pyYa9kxw2MY7moQBVBoN4iVLnUZx6WmE4x4HnIwAu.jpg?size=810x1080&quality=95&type=album',
|
||||||
|
link: {
|
||||||
|
name: 'Арсен Мирзаев',
|
||||||
|
title: 'Читать статью полностью',
|
||||||
|
href: 'https://google.com',
|
||||||
|
class: ['source']
|
||||||
|
},
|
||||||
|
popup: 'Для подробной информации читайте статью полностью',
|
||||||
|
color: 'red'
|
||||||
|
});
|
||||||
|
const berbi = core.write({
|
||||||
|
title: 'берби'
|
||||||
|
});
|
||||||
|
const anarchy = core.write({
|
||||||
|
title: 'анархия'
|
||||||
|
});
|
||||||
|
core.connect(
|
||||||
|
berbi,
|
||||||
|
mirzaev);
|
||||||
|
core.connect(
|
||||||
|
anarchy,
|
||||||
|
mirzaev);
|
||||||
|
core.connect(
|
||||||
|
core.write({
|
||||||
|
title: 'бабы'
|
||||||
|
}),
|
||||||
|
mirzaev);
|
||||||
|
core.connect(
|
||||||
|
core.write({
|
||||||
|
title: 'Ксения Велькович',
|
||||||
|
description: 'А меня вписать в кружочек?',
|
||||||
|
cover: 'https://storage.mirzaev.sexy/2022/mirzaev.sexy/nodes/ksenia_velkovich.jpg',
|
||||||
|
link: {
|
||||||
|
name: 'Ксения Велькович',
|
||||||
|
title: 'Страница ВКонтакте',
|
||||||
|
href: 'https://vk.com/id720261644',
|
||||||
|
class: ['source']
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
mirzaev);
|
||||||
|
core.connect(
|
||||||
|
core.write({
|
||||||
|
title: 'чокопайки'
|
||||||
|
}),
|
||||||
|
mirzaev);
|
||||||
|
core.connect(
|
||||||
|
anarchy,
|
||||||
|
berbi);
|
||||||
|
{% endif %}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% block title %}
|
||||||
|
<title>{% if head.title != empty %}{{head.title}}{% else %}Мирзаев{% endif %}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
{% for meta in head.metas %}
|
||||||
|
<meta {% for name, value in meta.attributes %}{{name}}="{{value}}" {% endfor %}>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/main.css" />
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% block css %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<header>
|
||||||
|
</header>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_init %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{% block css %}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/hotline.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% if hotline.id != empty %}
|
||||||
|
<section id="{{ hotline.id }}" class="hotline unselectable" data-hotline="true" {% for name, value in hotline.parameters
|
||||||
|
%} data-hotline-{{ name }}="{{value}}" {% endfor %} {% for name, value in hotline.attributes %} {{ name
|
||||||
|
}}="{{value}}" {% endfor %}>
|
||||||
|
{% for element in hotline.elements %}
|
||||||
|
<{{element.tag??'article'}}>{{ element.content }}</{{element.tag??'article'}}>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript" src="/js/hotline.js" defer></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_init %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('hotline.loaded', function (e) {
|
||||||
|
// Запуск препроцессора бегущих строк
|
||||||
|
e.detail.hotline.preprocessing();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,42 @@
|
||||||
|
{% extends "core.html" %}
|
||||||
|
|
||||||
|
{% use "core.html" with css as core_css, body as core_body, js as core_js, js_init as core_js_init %}
|
||||||
|
{% use "header.html" with css as header_css, body as header_body, js as header_js, js_init as header_js_init %}
|
||||||
|
{% use "aside.html" with css as aside_css, body as aside_body, js as aside_js, js_init as aside_js_init %}
|
||||||
|
{% use 'graph/index.html' with css as graph_css, main as graph_main, js as graph_js, js_init as graph_js_init %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ block('core_css') }}
|
||||||
|
{{ block('header_css') }}
|
||||||
|
{{ block('aside_css') }}
|
||||||
|
{{ block('graph_css') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{{ block('core_body') }}
|
||||||
|
{{ block('aside_body') }}
|
||||||
|
{{ block('header_body') }}
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<noscript>К сожалению мой сайт ещё пока не готов для работы без javascript</noscript>
|
||||||
|
{% block main %}
|
||||||
|
{{ block('graph_main') }}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{# {% include 'footer.html' %} #}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ block('core_js') }}
|
||||||
|
{{ block('header_js') }}
|
||||||
|
{{ block('aside_js') }}
|
||||||
|
{{ block('graph_js') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_init %}
|
||||||
|
{{ block('core_js_init') }}
|
||||||
|
{{ block('header_js_init') }}
|
||||||
|
{{ block('aside_js_init') }}
|
||||||
|
{{ block('graph_js_init') }}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript" src="/js/js.cookie.min.js" defer></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\views;
|
||||||
|
|
||||||
|
use mirzaev\minimal\controller;
|
||||||
|
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
use Twig\Environment as view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Менеджер представлений
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class manager extends controller
|
||||||
|
{
|
||||||
|
public function render(string $file, array $vars = []): ?string
|
||||||
|
{
|
||||||
|
// Генерация представления
|
||||||
|
return (new view(new FilesystemLoader(VIEWS)))->render($file, $vars);
|
||||||
|
}
|
||||||
|
}
|